Merge branch 'develop' into comany-name-field-issue-develop
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 455ab86..ac623e9 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -4,11 +4,7 @@
 
 cd ~ || exit
 
-sudo apt-get install redis-server
-
-sudo apt install nodejs
-
-sudo apt install npm
+sudo apt-get install redis-server libcups2-dev
 
 pip install frappe-bench
 
@@ -32,7 +28,6 @@
 tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
 sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
 sudo chmod o+x /usr/local/bin/wkhtmltopdf
-sudo apt-get install libcups2-dev
 
 cd ~/frappe-bench || exit
 
diff --git a/.github/helper/semgrep_rules/report.py b/.github/helper/semgrep_rules/report.py
new file mode 100644
index 0000000..ff27840
--- /dev/null
+++ b/.github/helper/semgrep_rules/report.py
@@ -0,0 +1,15 @@
+from frappe import _
+
+
+# ruleid: frappe-missing-translate-function-in-report-python
+{"label": "Field Label"}
+
+# ruleid: frappe-missing-translate-function-in-report-python
+dict(label="Field Label")
+
+
+# ok: frappe-missing-translate-function-in-report-python
+{"label": _("Field Label")}
+
+# ok: frappe-missing-translate-function-in-report-python
+dict(label=_("Field Label"))
diff --git a/.github/helper/semgrep_rules/report.yml b/.github/helper/semgrep_rules/report.yml
new file mode 100644
index 0000000..7f3dd01
--- /dev/null
+++ b/.github/helper/semgrep_rules/report.yml
@@ -0,0 +1,21 @@
+rules:
+- id: frappe-missing-translate-function-in-report-python
+  paths:
+    include:
+    - "**/report"
+    exclude:
+    - "**/regional"
+  pattern-either:
+  - patterns:
+      - pattern: |
+          {..., "label": "...", ...}
+      - pattern-not: |
+          {..., "label": _("..."), ...}
+  - patterns:
+      - pattern: dict(..., label="...", ...)
+      - pattern-not: dict(..., label=_("..."), ...)
+  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]
+  severity: ERROR
+
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index 72d4028..92a1962 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -7,10 +7,13 @@
       - '**.md'
   workflow_dispatch:
 
+concurrency:
+  group: patch-develop-${{ github.event.number }}
+  cancel-in-progress: true
 
 jobs:
   test:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-latest
     timeout-minutes: 60
 
     name: Patch Test
@@ -31,7 +34,13 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.6
+          python-version: 3.7
+
+      - name: Setup Node
+        uses: actions/setup-node@v2
+        with:
+          node-version: 14
+          check-latest: true
 
       - name: Add to Hosts
         run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml
index 3a1ecd3..71e9c2c 100644
--- a/.github/workflows/server-tests.yml
+++ b/.github/workflows/server-tests.yml
@@ -12,9 +12,13 @@
       - '**.js'
       - '**.md'
 
+concurrency:
+  group: server-develop-${{ github.event.number }}
+  cancel-in-progress: true
+
 jobs:
   test:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-latest
     timeout-minutes: 60
 
     strategy:
@@ -43,6 +47,12 @@
         with:
           python-version: 3.7
 
+      - name: Setup Node
+        uses: actions/setup-node@v2
+        with:
+          node-version: 14
+          check-latest: true
+
       - name: Add to Hosts
         run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
 
@@ -107,7 +117,7 @@
     name: Coverage Wrap Up
     needs: test
     container: python:3-slim
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-latest
     steps:
       - name: Clone
         uses: actions/checkout@v2
diff --git a/.github/workflows/translation_linter.yml b/.github/workflows/translation_linter.yml
deleted file mode 100644
index 4becaeb..0000000
--- a/.github/workflows/translation_linter.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: Frappe Linter
-on:
-  pull_request:
-    branches:
-      - develop
-      - version-12-hotfix
-      - version-11-hotfix
-jobs:
-  check_translation:
-    name: Translation Syntax Check
-    runs-on: ubuntu-18.04
-    steps:
-    - uses: actions/checkout@v2
-    - name: Setup python3
-      uses: actions/setup-python@v1
-      with:
-        python-version: 3.6
-    - name: Validating Translation Syntax
-      run: |
-        git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
-        files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
-        python $GITHUB_WORKSPACE/.github/helper/translation.py $files
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 0be9bd8..658892c 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -6,9 +6,13 @@
       - '**.md'
   workflow_dispatch:
 
+concurrency:
+  group: ui-develop-${{ github.event.number }}
+  cancel-in-progress: true
+
 jobs:
   test:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-latest
     timeout-minutes: 60
 
     strategy:
@@ -95,7 +99,7 @@
         run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
 
       - name: cypress pre-requisites
-        run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 --no-lockfile
+        run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile
 
 
       - name: Build Assets
diff --git a/cypress/integration/test_organizational_chart_desktop.js b/cypress/integration/test_organizational_chart_desktop.js
index 820a23a..39b00d3 100644
--- a/cypress/integration/test_organizational_chart_desktop.js
+++ b/cypress/integration/test_organizational_chart_desktop.js
@@ -2,8 +2,11 @@
 	before(() => {
 		cy.login();
 		cy.visit('/app/website');
+	});
+
+	it('navigates to org chart', () => {
+		cy.visit('/app');
 		cy.awesomebar('Organizational Chart');
-		cy.wait(500);
 		cy.url().should('include', '/organizational-chart');
 
 		cy.window().its('frappe.csrf_token').then(csrf_token => {
diff --git a/cypress/integration/test_organizational_chart_mobile.js b/cypress/integration/test_organizational_chart_mobile.js
index df90dbf..6e75151 100644
--- a/cypress/integration/test_organizational_chart_mobile.js
+++ b/cypress/integration/test_organizational_chart_mobile.js
@@ -1,9 +1,14 @@
 context('Organizational Chart Mobile', () => {
 	before(() => {
 		cy.login();
-		cy.viewport(375, 667);
 		cy.visit('/app/website');
+	});
+
+	it('navigates to org chart', () => {
+		cy.viewport(375, 667);
+		cy.visit('/app');
 		cy.awesomebar('Organizational Chart');
+		cy.url().should('include', '/organizational-chart');
 
 		cy.window().its('frappe.csrf_token').then(csrf_token => {
 			return cy.request({
diff --git a/erpnext/accounts/custom/address.py b/erpnext/accounts/custom/address.py
index c417a49..834227b 100644
--- a/erpnext/accounts/custom/address.py
+++ b/erpnext/accounts/custom/address.py
@@ -1,7 +1,7 @@
 import frappe
 from frappe import _
 from frappe.contacts.doctype.address.address import Address
-from frappe.contacts.doctype.address.address import get_address_templates
+from frappe.contacts.doctype.address.address import get_address_templates, get_address_display
 
 class ERPNextAddress(Address):
 	def validate(self):
@@ -22,6 +22,16 @@
 			frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
 				title=_("Company Not Linked"))
 
+	def on_update(self):
+		"""
+		After Address is updated, update the related 'Primary Address' on Customer.
+		"""
+		address_display = get_address_display(self.as_dict())
+		filters = { "customer_primary_address": self.name }
+		customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
+		for customer_name in customers:
+			frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
+			
 @frappe.whitelist()
 def get_shipping_address(company, address = None):
 	filters = [
diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js
index f7f1a5f..7a1d735 100644
--- a/erpnext/accounts/doctype/account/account.js
+++ b/erpnext/accounts/doctype/account/account.js
@@ -74,7 +74,7 @@
 			});
 		} else if (cint(frm.doc.is_group) == 0
 			&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
-			cur_frm.add_custom_button(__('Ledger'), function () {
+			frm.add_custom_button(__('Ledger'), function () {
 				frappe.route_options = {
 					"account": frm.doc.name,
 					"from_date": frappe.sys_defaults.year_start_date,
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.js b/erpnext/accounts/doctype/accounting_period/test_accounting_period.js
deleted file mode 100644
index 71ce5b8..0000000
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Accounting Period", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Accounting Period
-		() => frappe.tests.make('Accounting Period', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js
index e44af3a..0627675 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.js
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.js
@@ -6,46 +6,3 @@
 
 	}
 });
-
-frappe.tour['Accounts Settings'] = [
-	{
-		fieldname: "acc_frozen_upto",
-		title: "Accounts Frozen Upto",
-		description: __("Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role."),
-	},
-	{
-		fieldname: "frozen_accounts_modifier",
-		title: "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
-		description: __("Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.")
-	},
-	{
-		fieldname: "determine_address_tax_category_from",
-		title: "Determine Address Tax Category From",
-		description: __("Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.")
-	},
-	{
-		fieldname: "over_billing_allowance",
-		title: "Over Billing Allowance Percentage",
-		description: __("The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.")
-	},
-	{
-		fieldname: "credit_controller",
-		title: "Credit Controller",
-		description: __("Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.")
-	},
-	{
-		fieldname: "make_payment_via_journal_entry",
-		title: "Make Payment via Journal Entry",
-		description: __("When checked, if user proceeds to make payment from an invoice, the system will open a Journal Entry instead of a Payment Entry.")
-	},
-	{
-		fieldname: "unlink_payment_on_cancellation_of_invoice",
-		title: "Unlink Payment on Cancellation of Invoice",
-		description: __("If checked, system will unlink the payment against the respective invoice.")
-	},
-	{
-		fieldname: "unlink_advance_payment_on_cancelation_of_order",
-		title: "Unlink Advance Payment on Cancellation of Order",
-		description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.")
-	}
-];
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index a246ae5..7d0ecfb 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -19,6 +19,7 @@
   "delete_linked_ledger_entries",
   "book_asset_depreciation_entry_automatically",
   "unlink_advance_payment_on_cancelation_of_order",
+  "enable_common_party_accounting",
   "post_change_gl_entries",
   "enable_discount_accounting",
   "tax_settings_section",
@@ -268,6 +269,12 @@
    "fieldname": "enable_discount_accounting",
    "fieldtype": "Check",
    "label": "Enable Discount Accounting"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_common_party_accounting",
+   "fieldtype": "Check",
+   "label": "Enable Common Party Accounting"
   }
  ],
  "icon": "icon-cog",
@@ -275,7 +282,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-08-09 13:08:04.335416",
+ "modified": "2021-08-19 11:17:38.788054",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank/test_bank.js b/erpnext/accounts/doctype/bank/test_bank.js
deleted file mode 100644
index 9ec2644..0000000
--- a/erpnext/accounts/doctype/bank/test_bank.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Bank
-		() => frappe.tests.make('Bank', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_account/test_bank_account.js b/erpnext/accounts/doctype/bank_account/test_bank_account.js
deleted file mode 100644
index c20a799..0000000
--- a/erpnext/accounts/doctype/bank_account/test_bank_account.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Account", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Bank Account
-		() => frappe.tests.make('Bank Account', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js b/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js
deleted file mode 100644
index f599998..0000000
--- a/erpnext/accounts/doctype/bank_account_subtype/test_bank_account_subtype.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Account Subtype", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Bank Account Subtype
-		() => frappe.tests.make('Bank Account Subtype', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js b/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js
deleted file mode 100644
index 0c60920..0000000
--- a/erpnext/accounts/doctype/bank_guarantee/test_bank_guarantee.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Guarantee", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Bank Guarantee
-		() => frappe.tests.make('Bank Guarantee', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js
deleted file mode 100644
index 305119e..0000000
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Bank Transaction", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Bank Transaction
-		() => frappe.tests.make('Bank Transaction', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js b/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js
deleted file mode 100644
index 12ca254..0000000
--- a/erpnext/accounts/doctype/cash_flow_mapper/test_cash_flow_mapper.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapper", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Cash Flow Mapper
-		() => frappe.tests.make('Cash Flow Mapper', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js b/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js
deleted file mode 100644
index 1970ca8..0000000
--- a/erpnext/accounts/doctype/cash_flow_mapping/test_cash_flow_mapping.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapping", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Cash Flow Mapping
-		() => frappe.tests.make('Cash Flow Mapping', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js b/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js
deleted file mode 100644
index 12546ce..0000000
--- a/erpnext/accounts/doctype/cash_flow_mapping_template/test_cash_flow_mapping_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapping Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Cash Flow Mapping Template
-		() => frappe.tests.make('Cash Flow Mapping Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js b/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js
deleted file mode 100644
index eecabda..0000000
--- a/erpnext/accounts/doctype/cash_flow_mapping_template_details/test_cash_flow_mapping_template_details.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cash Flow Mapping Template Details", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Cash Flow Mapping Template Details
-		() => frappe.tests.make('Cash Flow Mapping Template Details', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js b/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js
deleted file mode 100644
index a7fcc8d..0000000
--- a/erpnext/accounts/doctype/cashier_closing/test_cashier_closing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Cashier Closing", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Cashier Closing
-		() => frappe.tests.make('Cashier Closing', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js b/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js
deleted file mode 100644
index b075a01..0000000
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/test_chart_of_accounts_importer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Chart of Accounts Importer", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Chart of Accounts Importer
-		() => frappe.tests.make('Chart of Accounts Importer', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.js b/erpnext/accounts/doctype/coupon_code/test_coupon_code.js
deleted file mode 100644
index 460fedc..0000000
--- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Coupon Code", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Coupon Code
-		() => frappe.tests.make('Coupon Code', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js b/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js
deleted file mode 100644
index 57c6a78..0000000
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/test_exchange_rate_revaluation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Exchange Rate Revaluation", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Exchange Rate Revaluation
-		() => frappe.tests.make('Exchange Rate Revaluation', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/finance_book/test_finance_book.js b/erpnext/accounts/doctype/finance_book/test_finance_book.js
deleted file mode 100644
index 9fb7d4f..0000000
--- a/erpnext/accounts/doctype/finance_book/test_finance_book.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Finance Book", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Finance Book
-		() => frappe.tests.make('Finance Book', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/gl_entry/test_gl_entry.js b/erpnext/accounts/doctype/gl_entry/test_gl_entry.js
deleted file mode 100644
index 2986e5e..0000000
--- a/erpnext/accounts/doctype/gl_entry/test_gl_entry.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GL Entry", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('GL Entry', [
-		// insert a new GL Entry
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js b/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js
deleted file mode 100644
index 6893499..0000000
--- a/erpnext/accounts/doctype/item_tax_template/test_item_tax_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Item Tax Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Item Tax Template
-		() => frappe.tests.make('Item Tax Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 937597b..dc341d7 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -66,6 +66,7 @@
 		self.update_expense_claim()
 		self.update_inter_company_jv()
 		self.update_invoice_discounting()
+		self.update_status_for_full_and_final_statement()
 		check_if_stock_and_account_balance_synced(self.posting_date,
 			self.company, self.doctype, self.name)
 
@@ -83,6 +84,7 @@
 		self.unlink_inter_company_jv()
 		self.unlink_asset_adjustment_entry()
 		self.update_invoice_discounting()
+		self.update_status_for_full_and_final_statement()
 
 	def get_title(self):
 		return self.pay_to_recd_from or self.accounts[0].account
@@ -98,6 +100,15 @@
 			for voucher_no in list(set(order_list)):
 				frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
 
+	def update_status_for_full_and_final_statement(self):
+		for entry in self.accounts:
+			if entry.reference_type == "Full and Final Statement":
+				if self.docstatus == 1:
+					frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Paid")
+				elif self.docstatus == 2:
+					frappe.db.set_value("Full and Final Statement", entry.reference_name, "status", "Unpaid")
+
+
 	def validate_inter_company_accounts(self):
 		if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
 			doc = frappe.get_doc("Journal Entry", self.inter_company_journal_entry_reference)
@@ -643,7 +654,10 @@
 		for d in self.accounts:
 			if d.reference_type=="Expense Claim" and d.reference_name:
 				doc = frappe.get_doc("Expense Claim", d.reference_name)
-				update_reimbursed_amount(doc, jv=self.name)
+				if self.docstatus == 2:
+					update_reimbursed_amount(doc, -1 * d.debit)
+				else:
+					update_reimbursed_amount(doc, d.debit)
 
 
 	def validate_expense_claim(self):
diff --git a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
index a89fefd..dff883a 100644
--- a/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
+++ b/erpnext/accounts/doctype/journal_entry_account/journal_entry_account.json
@@ -202,7 +202,7 @@
    "fieldname": "reference_type",
    "fieldtype": "Select",
    "label": "Reference Type",
-   "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees"
+   "options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement"
   },
   {
    "fieldname": "reference_name",
@@ -280,7 +280,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-06-26 14:06:54.833738",
+ "modified": "2021-08-30 21:27:32.200299",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry Account",
diff --git a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js b/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js
deleted file mode 100644
index a916b67..0000000
--- a/erpnext/accounts/doctype/loyalty_point_entry/test_loyalty_point_entry.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Loyalty Point Entry", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Loyalty Point Entry
-		() => frappe.tests.make('Loyalty Point Entry', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js b/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js
deleted file mode 100644
index 9321c14..0000000
--- a/erpnext/accounts/doctype/loyalty_program/test_loyalty_program.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Loyalty Program", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Loyalty Program
-		() => frappe.tests.make('Loyalty Program', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js
deleted file mode 100644
index f95d0d8..0000000
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Opening Invoice Creation Tool", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Opening Invoice Creation Tool
-		() => frappe.tests.make('Opening Invoice Creation Tool', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/accounts/doctype/party_link/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/accounts/doctype/party_link/__init__.py
diff --git a/erpnext/accounts/doctype/party_link/party_link.js b/erpnext/accounts/doctype/party_link/party_link.js
new file mode 100644
index 0000000..6da9291
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/party_link.js
@@ -0,0 +1,33 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Party Link', {
+	refresh: function(frm) {
+		frm.set_query('primary_role', () => {
+			return {
+				filters: {
+					name: ['in', ['Customer', 'Supplier']]
+				}
+			};
+		});
+
+		frm.set_query('secondary_role', () => {
+			let party_types = Object.keys(frappe.boot.party_account_types)
+				.filter(p => p != frm.doc.primary_role);
+			return {
+				filters: {
+					name: ['in', party_types]
+				}
+			};
+		});
+	},
+
+	primary_role(frm) {
+		frm.set_value('primary_party', '');
+		frm.set_value('secondary_role', '');
+	},
+
+	secondary_role(frm) {
+		frm.set_value('secondary_party', '');
+	}
+});
diff --git a/erpnext/accounts/doctype/party_link/party_link.json b/erpnext/accounts/doctype/party_link/party_link.json
new file mode 100644
index 0000000..a1bb15f
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/party_link.json
@@ -0,0 +1,102 @@
+{
+ "actions": [],
+ "autoname": "ACC-PT-LNK-.###.",
+ "creation": "2021-08-18 21:06:53.027695",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "primary_role",
+  "secondary_role",
+  "column_break_2",
+  "primary_party",
+  "secondary_party"
+ ],
+ "fields": [
+  {
+   "fieldname": "primary_role",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Primary Role",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "primary_role",
+   "fieldname": "secondary_role",
+   "fieldtype": "Link",
+   "label": "Secondary Role",
+   "mandatory_depends_on": "primary_role",
+   "options": "DocType"
+  },
+  {
+   "depends_on": "primary_role",
+   "fieldname": "primary_party",
+   "fieldtype": "Dynamic Link",
+   "label": "Primary Party",
+   "mandatory_depends_on": "primary_role",
+   "options": "primary_role"
+  },
+  {
+   "depends_on": "secondary_role",
+   "fieldname": "secondary_party",
+   "fieldtype": "Dynamic Link",
+   "label": "Secondary Party",
+   "mandatory_depends_on": "secondary_role",
+   "options": "secondary_role"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-25 20:08:56.761150",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Party Link",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "primary_party",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/party_link/party_link.py b/erpnext/accounts/doctype/party_link/party_link.py
new file mode 100644
index 0000000..7d58506
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/party_link.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class PartyLink(Document):
+	def validate(self):
+		if self.primary_role not in ['Customer', 'Supplier']:
+			frappe.throw(_("Allowed primary roles are 'Customer' and 'Supplier'. Please select one of these roles only."),
+				title=_("Invalid Primary Role"))
+		
+		existing_party_link = frappe.get_all('Party Link', {
+			'primary_party': self.secondary_party
+		}, pluck="primary_role")
+		if existing_party_link:
+			frappe.throw(_('{} {} is already linked with another {}')
+				.format(self.secondary_role, self.secondary_party, existing_party_link[0]))
+		
+		existing_party_link = frappe.get_all('Party Link', {
+			'secondary_party': self.primary_party
+		}, pluck="primary_role")
+		if existing_party_link:
+			frappe.throw(_('{} {} is already linked with another {}')
+				.format(self.primary_role, self.primary_party, existing_party_link[0]))
diff --git a/erpnext/accounts/doctype/party_link/test_party_link.py b/erpnext/accounts/doctype/party_link/test_party_link.py
new file mode 100644
index 0000000..a3ea395
--- /dev/null
+++ b/erpnext/accounts/doctype/party_link/test_party_link.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestPartyLink(unittest.TestCase):
+	pass
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index d96bc27..3be3925 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -872,7 +872,7 @@
 				&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
 				&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
 					unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
-						+ frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
+						- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
 			} else if (frm.doc.payment_type == "Pay"
 				&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
 				&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index abacee9..a5fcad4 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -75,9 +75,9 @@
 		if self.difference_amount:
 			frappe.throw(_("Difference Amount must be zero"))
 		self.make_gl_entries()
+		self.update_expense_claim()
 		self.update_outstanding_amounts()
 		self.update_advance_paid()
-		self.update_expense_claim()
 		self.update_donation()
 		self.update_payment_schedule()
 		self.set_status()
@@ -85,9 +85,9 @@
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
 		self.make_gl_entries(cancel=1)
+		self.update_expense_claim()
 		self.update_outstanding_amounts()
 		self.update_advance_paid()
-		self.update_expense_claim()
 		self.update_donation(cancel=1)
 		self.delink_advance_entry_references()
 		self.update_payment_schedule(cancel=1)
@@ -755,9 +755,11 @@
 
 			if self.payment_type in ('Pay', 'Internal Transfer'):
 				dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
+				rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
 				against = self.party or self.paid_from
 			elif self.payment_type == 'Receive':
 				dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
+				rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
 				against = self.party or self.paid_to
 
 			payment_or_advance_account = self.get_party_account_for_taxes()
@@ -779,14 +781,13 @@
 					"cost_center": d.cost_center
 				}, account_currency, item=d))
 
-			#Intentionally use -1 to get net values in party account
 			if not d.included_in_paid_amount or self.advance_tax_account:
 				gl_entries.append(
 					self.get_gl_dict({
 						"account": payment_or_advance_account,
 						"against": against,
-						dr_or_cr: -1 * tax_amount,
-						dr_or_cr + "_in_account_currency": -1 * base_tax_amount
+						rev_dr_or_cr: tax_amount,
+						rev_dr_or_cr + "_in_account_currency": base_tax_amount
 						if account_currency==self.company_currency
 						else d.tax_amount,
 						"cost_center": self.cost_center,
@@ -830,7 +831,10 @@
 			for d in self.get("references"):
 				if d.reference_doctype=="Expense Claim" and d.reference_name:
 					doc = frappe.get_doc("Expense Claim", d.reference_name)
-					update_reimbursed_amount(doc, self.name)
+					if self.docstatus == 2:
+						update_reimbursed_amount(doc, -1 * d.allocated_amount)
+					else:
+						update_reimbursed_amount(doc, d.allocated_amount)
 
 	def update_donation(self, cancel=0):
 		if self.payment_type == "Receive" and self.party_type == "Donor" and self.party:
diff --git a/erpnext/accounts/doctype/payment_order/test_payment_order.js b/erpnext/accounts/doctype/payment_order/test_payment_order.js
deleted file mode 100644
index f63fc54..0000000
--- a/erpnext/accounts/doctype/payment_order/test_payment_order.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Order", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Payment Order
-		() => frappe.tests.make('Payment Order', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index c71a62d..b1f3e6f 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -2,46 +2,10 @@
 // For license information, please see license.txt
 
 frappe.provide("erpnext.accounts");
-
-frappe.ui.form.on("Payment Reconciliation Payment", {
-	invoice_number: function(frm, cdt, cdn) {
-		var row = locals[cdt][cdn];
-		if(row.invoice_number) {
-			var parts = row.invoice_number.split(' | ');
-			var invoice_type = parts[0];
-			var invoice_number = parts[1];
-
-			var invoice_amount = frm.doc.invoices.filter(function(d) {
-				return d.invoice_type === invoice_type && d.invoice_number === invoice_number;
-			})[0].outstanding_amount;
-
-			frappe.model.set_value(cdt, cdn, "allocated_amount", Math.min(invoice_amount, row.amount));
-
-			frm.call({
-				doc: frm.doc,
-				method: 'get_difference_amount',
-				args: {
-					child_row: row
-				},
-				callback: function(r, rt) {
-					if(r.message) {
-						frappe.model.set_value(cdt, cdn,
-							"difference_amount", r.message);
-					}
-				}
-			});
-		}
-	}
-});
-
 erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationController extends frappe.ui.form.Controller {
 	onload() {
 		var me = this;
 
-		this.frm.set_query("party", function() {
-			check_mandatory(me.frm);
-		});
-
 		this.frm.set_query("party_type", function() {
 			return {
 				"filters": {
@@ -88,15 +52,36 @@
 
 	refresh() {
 		this.frm.disable_save();
-		this.toggle_primary_action();
+
+		if (this.frm.doc.receivable_payable_account) {
+			this.frm.add_custom_button(__('Get Unreconciled Entries'), () =>
+				this.frm.trigger("get_unreconciled_entries")
+			);
+		}
+		if (this.frm.doc.invoices.length && this.frm.doc.payments.length) {
+			this.frm.add_custom_button(__('Allocate'), () =>
+				this.frm.trigger("allocate")
+			);
+		}
+		if (this.frm.doc.allocation.length) {
+			this.frm.add_custom_button(__('Reconcile'), () =>
+				this.frm.trigger("reconcile")
+			);
+		}
 	}
 
-	onload_post_render() {
-		this.toggle_primary_action();
+	company() {
+		var me = this;
+		this.frm.set_value('receivable_payable_account', '');
+		me.frm.clear_table("allocation");
+		me.frm.clear_table("invoices");
+		me.frm.clear_table("payments");
+		me.frm.refresh_fields();
+		me.frm.trigger('party');
 	}
 
 	party() {
-		var me = this
+		var me = this;
 		if (!me.frm.doc.receivable_payable_account && me.frm.doc.party_type && me.frm.doc.party) {
 			return frappe.call({
 				method: "erpnext.accounts.party.get_party_account",
@@ -109,6 +94,7 @@
 					if (!r.exc && r.message) {
 						me.frm.set_value("receivable_payable_account", r.message);
 					}
+					me.frm.refresh();
 				}
 			});
 		}
@@ -120,16 +106,41 @@
 			doc: me.frm.doc,
 			method: 'get_unreconciled_entries',
 			callback: function(r, rt) {
-				me.set_invoice_options();
-				me.toggle_primary_action();
+				if (!(me.frm.doc.payments.length || me.frm.doc.invoices.length)) {
+					frappe.throw({message: __("No invoice and payment records found for this party")});
+				}
+				me.frm.refresh();
 			}
 		});
 
 	}
 
+	allocate() {
+		var me = this;
+		let payments = me.frm.fields_dict.payments.grid.get_selected_children();
+		if (!(payments.length)) {
+			payments = me.frm.doc.payments;
+		}
+		let invoices = me.frm.fields_dict.invoices.grid.get_selected_children();
+		if (!(invoices.length)) {
+			invoices = me.frm.doc.invoices;
+		}
+		return me.frm.call({
+			doc: me.frm.doc,
+			method: 'allocate_entries',
+			args: {
+				payments: payments,
+				invoices: invoices
+			},
+			callback: function() {
+				me.frm.refresh();
+			}
+		});
+	}
+
 	reconcile() {
 		var me = this;
-		var show_dialog = me.frm.doc.payments.filter(d => d.difference_amount && !d.difference_account);
+		var show_dialog = me.frm.doc.allocation.filter(d => d.difference_amount && !d.difference_account);
 
 		if (show_dialog && show_dialog.length) {
 
@@ -138,7 +149,7 @@
 				title: __("Select Difference Account"),
 				fields: [
 					{
-						fieldname: "payments", fieldtype: "Table", label: __("Payments"),
+						fieldname: "allocation", fieldtype: "Table", label: __("Allocation"),
 						data: this.data, in_place_edit: true,
 						get_data: () => {
 							return this.data;
@@ -179,10 +190,10 @@
 					},
 				],
 				primary_action: function() {
-					const args = dialog.get_values()["payments"];
+					const args = dialog.get_values()["allocation"];
 
 					args.forEach(d => {
-						frappe.model.set_value("Payment Reconciliation Payment", d.docname,
+						frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
 							"difference_account", d.difference_account);
 					});
 
@@ -192,9 +203,9 @@
 				primary_action_label: __('Reconcile Entries')
 			});
 
-			this.frm.doc.payments.forEach(d => {
+			this.frm.doc.allocation.forEach(d => {
 				if (d.difference_amount && !d.difference_account) {
-					dialog.fields_dict.payments.df.data.push({
+					dialog.fields_dict.allocation.df.data.push({
 						'docname': d.name,
 						'reference_name': d.reference_name,
 						'difference_amount': d.difference_amount,
@@ -203,8 +214,8 @@
 				}
 			});
 
-			this.data = dialog.fields_dict.payments.df.data;
-			dialog.fields_dict.payments.grid.refresh();
+			this.data = dialog.fields_dict.allocation.df.data;
+			dialog.fields_dict.allocation.grid.refresh();
 			dialog.show();
 		} else {
 			this.reconcile_payment_entries();
@@ -218,48 +229,12 @@
 			doc: me.frm.doc,
 			method: 'reconcile',
 			callback: function(r, rt) {
-				me.set_invoice_options();
-				me.toggle_primary_action();
+				me.frm.clear_table("allocation");
+				me.frm.refresh_fields();
+				me.frm.refresh();
 			}
 		});
 	}
-
-	set_invoice_options() {
-		var me = this;
-		var invoices = [];
-
-		$.each(me.frm.doc.invoices || [], function(i, row) {
-			if (row.invoice_number && !in_list(invoices, row.invoice_number))
-				invoices.push(row.invoice_type + " | " + row.invoice_number);
-		});
-
-		if (invoices) {
-			this.frm.fields_dict.payments.grid.update_docfield_property(
-				'invoice_number', 'options', "\n" + invoices.join("\n")
-			);
-
-			$.each(me.frm.doc.payments || [], function(i, p) {
-				if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
-			});
-		}
-
-		refresh_field("payments");
-	}
-
-	toggle_primary_action() {
-		if ((this.frm.doc.payments || []).length) {
-			this.frm.fields_dict.reconcile.$input
-				&& this.frm.fields_dict.reconcile.$input.addClass("btn-primary");
-			this.frm.fields_dict.get_unreconciled_entries.$input
-				&& this.frm.fields_dict.get_unreconciled_entries.$input.removeClass("btn-primary");
-		} else {
-			this.frm.fields_dict.reconcile.$input
-				&& this.frm.fields_dict.reconcile.$input.removeClass("btn-primary");
-			this.frm.fields_dict.get_unreconciled_entries.$input
-				&& this.frm.fields_dict.get_unreconciled_entries.$input.addClass("btn-primary");
-		}
-	}
-
 };
 
 extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index cfb24c3..9023b36 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -1,622 +1,206 @@
 {
- "allow_copy": 1, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2014-07-09 12:04:51.681583", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 0, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_copy": 1,
+ "creation": "2014-07-09 12:04:51.681583",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+  "company",
+  "party_type",
+  "column_break_4",
+  "party",
+  "receivable_payable_account",
+  "col_break1",
+  "from_invoice_date",
+  "to_invoice_date",
+  "minimum_invoice_amount",
+  "maximum_invoice_amount",
+  "invoice_limit",
+  "column_break_13",
+  "from_payment_date",
+  "to_payment_date",
+  "minimum_payment_amount",
+  "maximum_payment_amount",
+  "payment_limit",
+  "bank_cash_account",
+  "sec_break1",
+  "invoices",
+  "column_break_15",
+  "payments",
+  "sec_break2",
+  "allocation"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Company", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "party_type", 
-   "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": "Party Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "DocType", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "party_type",
+   "fieldtype": "Link",
+   "label": "Party Type",
+   "options": "DocType",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "", 
-   "fieldname": "party", 
-   "fieldtype": "Dynamic 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": "Party", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "party_type", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "eval:doc.party_type",
+   "fieldname": "party",
+   "fieldtype": "Dynamic Link",
+   "label": "Party",
+   "options": "party_type",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "receivable_payable_account", 
-   "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": "Receivable / Payable Account", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Account", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "eval:doc.company && doc.party",
+   "fieldname": "receivable_payable_account",
+   "fieldtype": "Link",
+   "label": "Receivable / Payable Account",
+   "options": "Account",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "bank_cash_account", 
-   "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": "Bank / Cash Account", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Account", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "description": "This filter will be applied to Journal Entry.",
+   "fieldname": "bank_cash_account",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Bank / Cash Account",
+   "options": "Account"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "col_break1", 
-   "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, 
-   "label": "", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "collapsible": 1,
+   "collapsible_depends_on": "eval: doc.invoices.length == 0",
+   "depends_on": "eval:doc.receivable_payable_account",
+   "fieldname": "col_break1",
+   "fieldtype": "Section Break",
+   "label": "Filters"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "from_date", 
-   "fieldtype": "Date", 
-   "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": "From Invoice Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "eval:(doc.payments).length || (doc.invoices).length",
+   "fieldname": "sec_break1",
+   "fieldtype": "Section Break",
+   "label": "Unreconciled Entries"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "to_date", 
-   "fieldtype": "Date", 
-   "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": "To Invoice Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "payments",
+   "fieldtype": "Table",
+   "label": "Payments",
+   "options": "Payment Reconciliation Payment"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "minimum_amount", 
-   "fieldtype": "Currency", 
-   "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": "Minimum Invoice Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "allocation",
+   "fieldname": "sec_break2",
+   "fieldtype": "Section Break",
+   "label": "Allocated Entries"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "maximum_amount", 
-   "fieldtype": "Currency", 
-   "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": "Maximum Invoice Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "invoices",
+   "fieldtype": "Table",
+   "label": "Invoices",
+   "options": "Payment Reconciliation Invoice"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "System will fetch all the entries if limit value is zero.", 
-   "fieldname": "limit", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Limit", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_15",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "get_unreconciled_entries", 
-   "fieldtype": "Button", 
-   "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": "Get Unreconciled Entries", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "allocation",
+   "fieldtype": "Table",
+   "label": "Allocation",
+   "options": "Payment Reconciliation Allocation"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sec_break1", 
-   "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, 
-   "label": "Unreconciled Payment Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "payments", 
-   "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": "Payments", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Payment Reconciliation Payment", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "from_invoice_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "From Invoice Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reconcile", 
-   "fieldtype": "Button", 
-   "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": "Reconcile", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "to_invoice_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "To Invoice Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "sec_break2", 
-   "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, 
-   "label": "Invoice/Journal Entry Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "minimum_invoice_amount",
+   "fieldtype": "Currency",
+   "label": "Minimum Invoice Amount"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "invoices", 
-   "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": "Invoices", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Payment Reconciliation Invoice", 
-   "permlevel": 0, 
-   "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
+   "description": "System will fetch all the entries if limit value is zero.",
+   "fieldname": "invoice_limit",
+   "fieldtype": "Int",
+   "label": "Invoice Limit"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "from_payment_date",
+   "fieldtype": "Date",
+   "label": "From Payment Date"
+  },
+  {
+   "fieldname": "to_payment_date",
+   "fieldtype": "Date",
+   "label": "To Payment Date"
+  },
+  {
+   "fieldname": "minimum_payment_amount",
+   "fieldtype": "Currency",
+   "label": "Minimum Payment Amount"
+  },
+  {
+   "fieldname": "maximum_payment_amount",
+   "fieldtype": "Currency",
+   "label": "Maximum Payment Amount"
+  },
+  {
+   "fieldname": "payment_limit",
+   "fieldtype": "Int",
+   "label": "Payment Limit"
+  },
+  {
+   "fieldname": "maximum_invoice_amount",
+   "fieldtype": "Currency",
+   "label": "Maximum Invoice Amount"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 1, 
- "icon": "icon-resize-horizontal", 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 1, 
- "istable": 0, 
- "max_attachments": 0, 
- "menu_index": 0, 
- "modified": "2019-01-15 17:42:21.135214", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Payment Reconciliation", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "hide_toolbar": 1,
+ "icon": "icon-resize-horizontal",
+ "issingle": 1,
+ "links": [],
+ "modified": "2021-08-30 13:05:51.977861",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Reconciliation",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 0, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 0, 
-   "read": 1, 
-   "report": 0, 
-   "role": "Accounts Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "read": 1,
+   "role": "Accounts Manager",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 0, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 0, 
-   "read": 1, 
-   "report": 0, 
-   "role": "Accounts User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "read": 1,
+   "role": "Accounts User",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 0, 
- "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
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index acfe1fe..1286bf0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -3,7 +3,7 @@
 
 from __future__ import unicode_literals
 import frappe, erpnext
-from frappe.utils import flt, today
+from frappe.utils import flt, today, getdate, nowdate
 from frappe import msgprint, _
 from frappe.model.document import Document
 from erpnext.accounts.utils import (get_outstanding_invoices,
@@ -27,24 +27,32 @@
 		else:
 			dr_or_cr_notes = []
 
-		self.add_payment_entries(payment_entries + journal_entries + dr_or_cr_notes)
+		non_reconciled_payments = payment_entries + journal_entries + dr_or_cr_notes
+
+		if self.payment_limit:
+			non_reconciled_payments = non_reconciled_payments[:self.payment_limit]
+
+		non_reconciled_payments = sorted(non_reconciled_payments, key=lambda k: k['posting_date'] or getdate(nowdate()))
+
+		self.add_payment_entries(non_reconciled_payments)
 
 	def get_payment_entries(self):
 		order_doctype = "Sales Order" if self.party_type=="Customer" else "Purchase Order"
+		condition = self.get_conditions(get_payments=True)
 		payment_entries = get_advance_payment_entries(self.party_type, self.party,
-			self.receivable_payable_account, order_doctype, against_all_orders=True, limit=self.limit)
+			self.receivable_payable_account, order_doctype, against_all_orders=True, limit=self.payment_limit,
+			condition=condition)
 
 		return payment_entries
 
 	def get_jv_entries(self):
+		condition = self.get_conditions()
 		dr_or_cr = ("credit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
 			else "debit_in_account_currency")
 
 		bank_account_condition = "t2.against_account like %(bank_cash_account)s" \
 				if self.bank_cash_account else "1=1"
 
-		limit_cond = "limit %s" % self.limit if self.limit else ""
-
 		journal_entries = frappe.db.sql("""
 			select
 				"Journal Entry" as reference_type, t1.name as reference_name,
@@ -56,7 +64,7 @@
 			where
 				t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
 				and t2.party_type = %(party_type)s and t2.party = %(party)s
-				and t2.account = %(account)s and {dr_or_cr} > 0
+				and t2.account = %(account)s and {dr_or_cr} > 0 {condition}
 				and (t2.reference_type is null or t2.reference_type = '' or
 					(t2.reference_type in ('Sales Order', 'Purchase Order')
 						and t2.reference_name is not null and t2.reference_name != ''))
@@ -65,11 +73,11 @@
 					THEN 1=1
 					ELSE {bank_account_condition}
 				END)
-			order by t1.posting_date {limit_cond}
+			order by t1.posting_date
 			""".format(**{
 				"dr_or_cr": dr_or_cr,
 				"bank_account_condition": bank_account_condition,
-				"limit_cond": limit_cond
+				"condition": condition
 			}), {
 				"party_type": self.party_type,
 				"party": self.party,
@@ -80,6 +88,7 @@
 		return list(journal_entries)
 
 	def get_dr_or_cr_notes(self):
+		condition = self.get_conditions(get_return_invoices=True)
 		dr_or_cr = ("credit_in_account_currency"
 			if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
 
@@ -90,7 +99,7 @@
 			if self.party_type == 'Customer' else "Purchase Invoice")
 
 		return frappe.db.sql(""" SELECT doc.name as reference_name, %(voucher_type)s as reference_type,
-				(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount,
+				(sum(gl.{dr_or_cr}) - sum(gl.{reconciled_dr_or_cr})) as amount, doc.posting_date,
 				account_currency as currency
 			FROM `tab{doc}` doc, `tabGL Entry` gl
 			WHERE
@@ -100,15 +109,17 @@
 				and gl.against_voucher_type = %(voucher_type)s
 				and doc.docstatus = 1 and gl.party = %(party)s
 				and gl.party_type = %(party_type)s and gl.account = %(account)s
-				and gl.is_cancelled = 0
+				and gl.is_cancelled = 0 {condition}
 			GROUP BY doc.name
 			Having
 				amount > 0
+			ORDER BY doc.posting_date
 		""".format(
 			doc=voucher_type,
 			dr_or_cr=dr_or_cr,
 			reconciled_dr_or_cr=reconciled_dr_or_cr,
-			party_type_field=frappe.scrub(self.party_type)),
+			party_type_field=frappe.scrub(self.party_type),
+			condition=condition or ""),
 			{
 				'party': self.party,
 				'party_type': self.party_type,
@@ -116,22 +127,23 @@
 				'account': self.receivable_payable_account
 			}, as_dict=1)
 
-	def add_payment_entries(self, entries):
+	def add_payment_entries(self, non_reconciled_payments):
 		self.set('payments', [])
-		for e in entries:
+
+		for payment in non_reconciled_payments:
 			row = self.append('payments', {})
-			row.update(e)
+			row.update(payment)
 
 	def get_invoice_entries(self):
 		#Fetch JVs, Sales and Purchase Invoices for 'invoices' to reconcile against
 
-		condition = self.check_condition()
+		condition = self.get_conditions(get_invoices=True)
 
 		non_reconciled_invoices = get_outstanding_invoices(self.party_type, self.party,
 			self.receivable_payable_account, condition=condition)
 
-		if self.limit:
-			non_reconciled_invoices = non_reconciled_invoices[:self.limit]
+		if self.invoice_limit:
+			non_reconciled_invoices = non_reconciled_invoices[:self.invoice_limit]
 
 		self.add_invoice_entries(non_reconciled_invoices)
 
@@ -139,41 +151,78 @@
 		#Populate 'invoices' with JVs and Invoices to reconcile against
 		self.set('invoices', [])
 
-		for e in non_reconciled_invoices:
-			ent = self.append('invoices', {})
-			ent.invoice_type = e.get('voucher_type')
-			ent.invoice_number = e.get('voucher_no')
-			ent.invoice_date = e.get('posting_date')
-			ent.amount = flt(e.get('invoice_amount'))
-			ent.currency = e.get('currency')
-			ent.outstanding_amount = e.get('outstanding_amount')
+		for entry in non_reconciled_invoices:
+			inv = self.append('invoices', {})
+			inv.invoice_type = entry.get('voucher_type')
+			inv.invoice_number = entry.get('voucher_no')
+			inv.invoice_date = entry.get('posting_date')
+			inv.amount = flt(entry.get('invoice_amount'))
+			inv.currency = entry.get('currency')
+			inv.outstanding_amount = flt(entry.get('outstanding_amount'))
 
 	@frappe.whitelist()
-	def reconcile(self, args):
-		for e in self.get('payments'):
-			e.invoice_type = None
-			if e.invoice_number and " | " in e.invoice_number:
-				e.invoice_type, e.invoice_number = e.invoice_number.split(" | ")
+	def allocate_entries(self, args):
+		self.validate_entries()
+		entries = []
+		for pay in args.get('payments'):
+			pay.update({'unreconciled_amount': pay.get('amount')})
+			for inv in args.get('invoices'):
+				if pay.get('amount') >= inv.get('outstanding_amount'):
+					res = self.get_allocated_entry(pay, inv, inv['outstanding_amount'])
+					pay['amount'] = flt(pay.get('amount')) - flt(inv.get('outstanding_amount'))
+					inv['outstanding_amount'] = 0
+				else:
+					res = self.get_allocated_entry(pay, inv, pay['amount'])
+					inv['outstanding_amount'] = flt(inv.get('outstanding_amount')) - flt(pay.get('amount'))
+					pay['amount'] = 0
+				if pay.get('amount') == 0:
+					entries.append(res)
+					break
+				elif inv.get('outstanding_amount') == 0:
+					entries.append(res)
+					continue
+			else:
+				break
 
-		self.get_invoice_entries()
-		self.validate_invoice()
+		self.set('allocation', [])
+		for entry in entries:
+			if entry['allocated_amount'] != 0:
+				row = self.append('allocation', {})
+				row.update(entry)
+
+	def get_allocated_entry(self, pay, inv, allocated_amount):
+		return frappe._dict({
+			'reference_type': pay.get('reference_type'),
+			'reference_name': pay.get('reference_name'),
+			'reference_row': pay.get('reference_row'),
+			'invoice_type': inv.get('invoice_type'),
+			'invoice_number': inv.get('invoice_number'),
+			'unreconciled_amount': pay.get('unreconciled_amount'),
+			'amount': pay.get('amount'),
+			'allocated_amount': allocated_amount,
+			'difference_amount': pay.get('difference_amount')
+		})
+
+	@frappe.whitelist()
+	def reconcile(self):
+		self.validate_allocation()
 		dr_or_cr = ("credit_in_account_currency"
 			if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
 
-		lst = []
+		entry_list = []
 		dr_or_cr_notes = []
-		for e in self.get('payments'):
+		for row in self.get('allocation'):
 			reconciled_entry = []
-			if e.invoice_number and e.allocated_amount:
-				if e.reference_type in ['Sales Invoice', 'Purchase Invoice']:
+			if row.invoice_number and row.allocated_amount:
+				if row.reference_type in ['Sales Invoice', 'Purchase Invoice']:
 					reconciled_entry = dr_or_cr_notes
 				else:
-					reconciled_entry = lst
+					reconciled_entry = entry_list
 
-				reconciled_entry.append(self.get_payment_details(e, dr_or_cr))
+				reconciled_entry.append(self.get_payment_details(row, dr_or_cr))
 
-		if lst:
-			reconcile_against_document(lst)
+		if entry_list:
+			reconcile_against_document(entry_list)
 
 		if dr_or_cr_notes:
 			reconcile_dr_cr_note(dr_or_cr_notes, self.company)
@@ -183,98 +232,104 @@
 
 	def get_payment_details(self, row, dr_or_cr):
 		return frappe._dict({
-			'voucher_type': row.reference_type,
-			'voucher_no' : row.reference_name,
-			'voucher_detail_no' : row.reference_row,
-			'against_voucher_type' : row.invoice_type,
-			'against_voucher'  : row.invoice_number,
+			'voucher_type': row.get('reference_type'),
+			'voucher_no' : row.get('reference_name'),
+			'voucher_detail_no' : row.get('reference_row'),
+			'against_voucher_type' : row.get('invoice_type'),
+			'against_voucher'  : row.get('invoice_number'),
 			'account' : self.receivable_payable_account,
 			'party_type': self.party_type,
 			'party': self.party,
-			'is_advance' : row.is_advance,
+			'is_advance' : row.get('is_advance'),
 			'dr_or_cr' : dr_or_cr,
-			'unadjusted_amount' : flt(row.amount),
-			'allocated_amount' : flt(row.allocated_amount),
-			'difference_amount': row.difference_amount,
-			'difference_account': row.difference_account
+			'unreconciled_amount': flt(row.get('unreconciled_amount')),
+			'unadjusted_amount' : flt(row.get('amount')),
+			'allocated_amount' : flt(row.get('allocated_amount')),
+			'difference_amount': flt(row.get('difference_amount')),
+			'difference_account': row.get('difference_account')
 		})
 
-	@frappe.whitelist()
-	def get_difference_amount(self, child_row):
-		if child_row.get("reference_type") != 'Payment Entry': return
-
-		child_row = frappe._dict(child_row)
-
-		if child_row.invoice_number and " | " in child_row.invoice_number:
-			child_row.invoice_type, child_row.invoice_number = child_row.invoice_number.split(" | ")
-
-		dr_or_cr = ("credit_in_account_currency"
-			if erpnext.get_party_account_type(self.party_type) == 'Receivable' else "debit_in_account_currency")
-
-		row = self.get_payment_details(child_row, dr_or_cr)
-
-		doc = frappe.get_doc(row.voucher_type, row.voucher_no)
-		update_reference_in_payment_entry(row, doc, do_not_save=True)
-
-		return doc.difference_amount
-
 	def check_mandatory_to_fetch(self):
 		for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
 			if not self.get(fieldname):
 				frappe.throw(_("Please select {0} first").format(self.meta.get_label(fieldname)))
 
-	def validate_invoice(self):
+	def validate_entries(self):
 		if not self.get("invoices"):
-			frappe.throw(_("No records found in the Invoice table"))
+			frappe.throw(_("No records found in the Invoices table"))
 
 		if not self.get("payments"):
-			frappe.throw(_("No records found in the Payment table"))
+			frappe.throw(_("No records found in the Payments table"))
 
+	def validate_allocation(self):
 		unreconciled_invoices = frappe._dict()
-		for d in self.get("invoices"):
-			unreconciled_invoices.setdefault(d.invoice_type, {}).setdefault(d.invoice_number, d.outstanding_amount)
+
+		for inv in self.get("invoices"):
+			unreconciled_invoices.setdefault(inv.invoice_type, {}).setdefault(inv.invoice_number, inv.outstanding_amount)
 
 		invoices_to_reconcile = []
-		for p in self.get("payments"):
-			if p.invoice_type and p.invoice_number and p.allocated_amount:
-				invoices_to_reconcile.append(p.invoice_number)
+		for row in self.get("allocation"):
+			if row.invoice_type and row.invoice_number and row.allocated_amount:
+				invoices_to_reconcile.append(row.invoice_number)
 
-				if p.invoice_number not in unreconciled_invoices.get(p.invoice_type, {}):
-					frappe.throw(_("{0}: {1} not found in Invoice Details table")
-						.format(p.invoice_type, p.invoice_number))
+				if flt(row.amount) - flt(row.allocated_amount) < 0:
+					frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equal to remaining payment amount {2}")
+						.format(row.idx, row.allocated_amount, row.amount))
 
-				if flt(p.allocated_amount) > flt(p.amount):
-					frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to Payment Entry amount {2}")
-						.format(p.idx, p.allocated_amount, p.amount))
-
-				invoice_outstanding = unreconciled_invoices.get(p.invoice_type, {}).get(p.invoice_number)
-				if flt(p.allocated_amount) - invoice_outstanding > 0.009:
-					frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equals to invoice outstanding amount {2}")
-						.format(p.idx, p.allocated_amount, invoice_outstanding))
+				invoice_outstanding = unreconciled_invoices.get(row.invoice_type, {}).get(row.invoice_number)
+				if flt(row.allocated_amount) - invoice_outstanding > 0.009:
+					frappe.throw(_("Row {0}: Allocated amount {1} must be less than or equal to invoice outstanding amount {2}")
+						.format(row.idx, row.allocated_amount, invoice_outstanding))
 
 		if not invoices_to_reconcile:
-			frappe.throw(_("Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row"))
+			frappe.throw(_("No records found in Allocation table"))
 
-	def check_condition(self):
-		cond = " and posting_date >= {0}".format(frappe.db.escape(self.from_date)) if self.from_date else ""
-		cond += " and posting_date <= {0}".format(frappe.db.escape(self.to_date)) if self.to_date else ""
-		dr_or_cr = ("debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
-			else "credit_in_account_currency")
+	def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
+		condition = " and company = '{0}' ".format(self.company)
 
-		if self.minimum_amount:
-			cond += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_amount))
-		if self.maximum_amount:
-			cond += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_amount))
+		if get_invoices:
+			condition += " and posting_date >= {0}".format(frappe.db.escape(self.from_invoice_date)) if self.from_invoice_date else ""
+			condition += " and posting_date <= {0}".format(frappe.db.escape(self.to_invoice_date)) if self.to_invoice_date else ""
+			dr_or_cr = ("debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
+				else "credit_in_account_currency")
 
-		return cond
+			if self.minimum_invoice_amount:
+				condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_invoice_amount))
+			if self.maximum_invoice_amount:
+				condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_invoice_amount))
+
+		elif get_return_invoices:
+			condition = " and doc.company = '{0}' ".format(self.company)
+			condition += " and doc.posting_date >= {0}".format(frappe.db.escape(self.from_payment_date)) if self.from_payment_date else ""
+			condition += " and doc.posting_date <= {0}".format(frappe.db.escape(self.to_payment_date)) if self.to_payment_date else ""
+			dr_or_cr = ("gl.debit_in_account_currency" if erpnext.get_party_account_type(self.party_type) == 'Receivable'
+				else "gl.credit_in_account_currency")
+
+			if self.minimum_invoice_amount:
+				condition += " and `{0}` >= {1}".format(dr_or_cr, flt(self.minimum_payment_amount))
+			if self.maximum_invoice_amount:
+				condition += " and `{0}` <= {1}".format(dr_or_cr, flt(self.maximum_payment_amount))
+
+		else:
+			condition += " and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date)) if self.from_payment_date else ""
+			condition += " and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date)) if self.to_payment_date else ""
+
+			if self.minimum_payment_amount:
+				condition += " and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount)) if get_payments \
+					else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
+			if self.maximum_payment_amount:
+				condition += " and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount)) if get_payments \
+					else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
+
+		return condition
 
 def reconcile_dr_cr_note(dr_cr_notes, company):
-	for d in dr_cr_notes:
+	for inv in dr_cr_notes:
 		voucher_type = ('Credit Note'
-			if d.voucher_type == 'Sales Invoice' else 'Debit Note')
+			if inv.voucher_type == 'Sales Invoice' else 'Debit Note')
 
 		reconcile_dr_or_cr = ('debit_in_account_currency'
-			if d.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
+			if inv.dr_or_cr == 'credit_in_account_currency' else 'credit_in_account_currency')
 
 		company_currency = erpnext.get_company_currency(company)
 
@@ -283,25 +338,25 @@
 			"voucher_type": voucher_type,
 			"posting_date": today(),
 			"company": company,
-			"multi_currency": 1 if d.currency != company_currency else 0,
+			"multi_currency": 1 if inv.currency != company_currency else 0,
 			"accounts": [
 				{
-					'account': d.account,
-					'party': d.party,
-					'party_type': d.party_type,
-					d.dr_or_cr: abs(d.allocated_amount),
-					'reference_type': d.against_voucher_type,
-					'reference_name': d.against_voucher,
+					'account': inv.account,
+					'party': inv.party,
+					'party_type': inv.party_type,
+					inv.dr_or_cr: abs(inv.allocated_amount),
+					'reference_type': inv.against_voucher_type,
+					'reference_name': inv.against_voucher,
 					'cost_center': erpnext.get_default_cost_center(company)
 				},
 				{
-					'account': d.account,
-					'party': d.party,
-					'party_type': d.party_type,
-					reconcile_dr_or_cr: (abs(d.allocated_amount)
-						if abs(d.unadjusted_amount) > abs(d.allocated_amount) else abs(d.unadjusted_amount)),
-					'reference_type': d.voucher_type,
-					'reference_name': d.voucher_no,
+					'account': inv.account,
+					'party': inv.party,
+					'party_type': inv.party_type,
+					reconcile_dr_or_cr: (abs(inv.allocated_amount)
+						if abs(inv.unadjusted_amount) > abs(inv.allocated_amount) else abs(inv.unadjusted_amount)),
+					'reference_type': inv.voucher_type,
+					'reference_name': inv.voucher_no,
 					'cost_center': erpnext.get_default_cost_center(company)
 				}
 			]
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
new file mode 100644
index 0000000..87eaaee
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestPaymentReconciliation(unittest.TestCase):
+	pass
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/accounts/doctype/payment_reconciliation_allocation/__init__.py
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
new file mode 100644
index 0000000..3653501
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
@@ -0,0 +1,137 @@
+{
+ "actions": [],
+ "creation": "2021-08-16 17:04:40.185167",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "reference_type",
+  "reference_name",
+  "column_break_3",
+  "invoice_type",
+  "invoice_number",
+  "section_break_6",
+  "allocated_amount",
+  "unreconciled_amount",
+  "amount",
+  "column_break_8",
+  "is_advance",
+  "section_break_5",
+  "difference_amount",
+  "column_break_7",
+  "difference_account"
+ ],
+ "fields": [
+  {
+   "fieldname": "invoice_number",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Invoice Number",
+   "options": "invoice_type",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "allocated_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Allocated Amount",
+   "options": "Currency",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "difference_account",
+   "fieldtype": "Link",
+   "label": "Difference Account",
+   "options": "Account",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "difference_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Difference Amount",
+   "options": "Currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "reference_name",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Reference Name",
+   "options": "reference_type",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "is_advance",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "label": "Is Advance",
+   "read_only": 1
+  },
+  {
+   "fieldname": "reference_type",
+   "fieldtype": "Link",
+   "label": "Reference Type",
+   "options": "DocType",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "invoice_type",
+   "fieldtype": "Link",
+   "label": "Invoice Type",
+   "options": "DocType",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_8",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "unreconciled_amount",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Unreconciled Amount",
+   "options": "Currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Amount",
+   "options": "Currency",
+   "read_only": 1
+  }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-30 10:58:42.665107",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Reconciliation Allocation",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
new file mode 100644
index 0000000..0fb63b1
--- /dev/null
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class PaymentReconciliationAllocation(Document):
+	pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
index 6a79a85..00c9e12 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.json
@@ -44,7 +44,6 @@
   {
    "fieldname": "amount",
    "fieldtype": "Currency",
-   "in_list_view": 1,
    "label": "Amount",
    "options": "currency",
    "read_only": 1
@@ -67,7 +66,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-07-19 18:12:27.964073",
+ "modified": "2021-08-24 22:42:40.923179",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Reconciliation Invoice",
diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
index 925a6f1..add07e8 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
@@ -11,11 +11,7 @@
   "is_advance",
   "reference_row",
   "col_break1",
-  "invoice_number",
   "amount",
-  "allocated_amount",
-  "section_break_10",
-  "difference_account",
   "difference_amount",
   "sec_break1",
   "remark",
@@ -41,6 +37,7 @@
   {
    "fieldname": "posting_date",
    "fieldtype": "Date",
+   "in_list_view": 1,
    "label": "Posting Date",
    "read_only": 1
   },
@@ -64,14 +61,6 @@
   },
   {
    "columns": 2,
-   "fieldname": "invoice_number",
-   "fieldtype": "Select",
-   "in_list_view": 1,
-   "label": "Invoice Number",
-   "reqd": 1
-  },
-  {
-   "columns": 2,
    "fieldname": "amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
@@ -80,56 +69,33 @@
    "read_only": 1
   },
   {
-   "columns": 2,
-   "fieldname": "allocated_amount",
-   "fieldtype": "Currency",
-   "in_list_view": 1,
-   "label": "Allocated amount",
-   "options": "currency",
-   "reqd": 1
-  },
-  {
    "fieldname": "sec_break1",
    "fieldtype": "Section Break"
   },
   {
    "fieldname": "remark",
    "fieldtype": "Small Text",
-   "in_list_view": 1,
    "label": "Remark",
    "read_only": 1
   },
   {
-   "columns": 2,
-   "fieldname": "difference_account",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Difference Account",
-   "options": "Account"
-  },
-  {
-   "fieldname": "difference_amount",
-   "fieldtype": "Currency",
-   "label": "Difference Amount",
-   "options": "currency",
-   "print_hide": 1,
-   "read_only": 1
-  },
-  {
-   "fieldname": "section_break_10",
-   "fieldtype": "Section Break"
-  },
-  {
    "fieldname": "currency",
    "fieldtype": "Link",
    "hidden": 1,
    "label": "Currency",
    "options": "Currency"
+  },
+  {
+   "fieldname": "difference_amount",
+   "fieldtype": "Currency",
+   "label": "Difference Amount",
+   "options": "currency",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-07-19 18:12:41.682347",
+ "modified": "2021-08-30 10:51:48.140062",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Reconciliation Payment",
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.js b/erpnext/accounts/doctype/payment_request/test_payment_request.js
deleted file mode 100644
index 070b595..0000000
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Request", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Payment Request
-		() => frappe.tests.make('Payment Request', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/payment_term/test_payment_term.js b/erpnext/accounts/doctype/payment_term/test_payment_term.js
deleted file mode 100644
index b26e42a..0000000
--- a/erpnext/accounts/doctype/payment_term/test_payment_term.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Term", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Payment Term
-		() => frappe.tests.make('Payment Term', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js
deleted file mode 100644
index 494a0ed..0000000
--- a/erpnext/accounts/doctype/payment_terms_template/test_payment_terms_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payment Terms Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Payment Terms Template
-		() => frappe.tests.make('Payment Terms Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index 2d19391..2a636bb 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -13,59 +13,49 @@
 
 class TestPeriodClosingVoucher(unittest.TestCase):
 	def test_closing_entry(self):
-		year_start_date = get_fiscal_year(today(), company="_Test Company")[1]
+		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
 
-		make_journal_entry("_Test Bank - _TC", "Sales - _TC", 400,
-			"_Test Cost Center - _TC", posting_date=now(), submit=True)
+		company = create_company()
+		cost_center = create_cost_center('Test Cost Center 1')
 
-		make_journal_entry("_Test Account Cost for Goods Sold - _TC",
-			"_Test Bank - _TC", 600, "_Test Cost Center - _TC", posting_date=now(), submit=True)
+		jv1 = make_journal_entry(
+			amount=400,
+			account1="Cash - TPC",
+			account2="Sales - TPC",
+			cost_center=cost_center,
+			posting_date=now(),
+			save=False
+		)
+		jv1.company = company
+		jv1.save()
+		jv1.submit()
 
-		random_expense_account = frappe.db.sql("""
-			select t1.account,
-				sum(t1.debit) - sum(t1.credit) as balance,
-				sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) \
-					as balance_in_account_currency
-			from `tabGL Entry` t1, `tabAccount` t2
-			where t1.account = t2.name and t2.root_type = 'Expense'
-				and t2.docstatus < 2 and t2.company = '_Test Company'
-				and t1.posting_date between %s and %s
-			group by t1.account
-			having sum(t1.debit) > sum(t1.credit)
-			limit 1""", (year_start_date, today()), as_dict=True)
-
-		profit_or_loss = frappe.db.sql("""select sum(t1.debit) - sum(t1.credit) as balance
-			from `tabGL Entry` t1, `tabAccount` t2
-			where t1.account = t2.name and t2.report_type = 'Profit and Loss'
-			and t2.docstatus < 2 and t2.company = '_Test Company'
-			and t1.posting_date between %s and %s""", (year_start_date, today()))
-
-		profit_or_loss = flt(profit_or_loss[0][0]) if profit_or_loss else 0
+		jv2 = make_journal_entry(
+			amount=600,
+			account1="Cost of Goods Sold - TPC",
+			account2="Cash - TPC",
+			cost_center=cost_center,
+			posting_date=now(),
+			save=False
+		)
+		jv2.company = company
+		jv2.save()
+		jv2.submit()
 
 		pcv = self.make_period_closing_voucher()
+		surplus_account = pcv.closing_account_head
 
-		# Check value for closing account
-		gle_amount_for_closing_account = frappe.db.sql("""select debit - credit
-			from `tabGL Entry` where voucher_type='Period Closing Voucher' and voucher_no=%s
-			and account = '_Test Account Reserves and Surplus - _TC'""", pcv.name)
+		expected_gle = (
+			('Cost of Goods Sold - TPC', 0.0, 600.0),
+			(surplus_account, 600.0, 400.0),
+			('Sales - TPC', 400.0, 0.0)
+		)
 
-		gle_amount_for_closing_account = flt(gle_amount_for_closing_account[0][0]) \
-			if gle_amount_for_closing_account else 0
+		pcv_gle = frappe.db.sql("""
+			select account, debit, credit from `tabGL Entry` where voucher_no=%s order by account
+		""", (pcv.name))
 
-		self.assertEqual(gle_amount_for_closing_account, profit_or_loss)
-
-		if random_expense_account:
-			# Check posted value for teh above random_expense_account
-			gle_for_random_expense_account = frappe.db.sql("""
-				select sum(debit - credit) as amount,
-					sum(debit_in_account_currency - credit_in_account_currency) as amount_in_account_currency
-				from `tabGL Entry`
-				where voucher_type='Period Closing Voucher' and voucher_no=%s and account =%s""",
-				(pcv.name, random_expense_account[0].account), as_dict=True)
-
-			self.assertEqual(gle_for_random_expense_account[0].amount, -1*random_expense_account[0].balance)
-			self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
-				-1*random_expense_account[0].balance_in_account_currency)
+		self.assertEqual(pcv_gle, expected_gle)
 
 	def test_cost_center_wise_posting(self):
 		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
@@ -93,31 +83,23 @@
 			debit_to="Debtors - TPC"
 		)
 
-		pcv = frappe.get_doc({
-			"transaction_date": today(),
-			"posting_date": today(),
-			"fiscal_year": get_fiscal_year(today())[0],
-			"company": "Test PCV Company",
-			"cost_center_wise_pnl": 1,
-			"closing_account_head": surplus_account,
-			"remarks": "Test",
-			"doctype": "Period Closing Voucher"
-		})
-		pcv.insert()
-		pcv.submit()
+		pcv = self.make_period_closing_voucher()
+		surplus_account = pcv.closing_account_head
 
 		expected_gle = (
-			('Sales - TPC', 200.0, 0.0, cost_center2),
+			(surplus_account, 0.0, 400.0, cost_center1),
 			(surplus_account, 0.0, 200.0, cost_center2),
 			('Sales - TPC', 400.0, 0.0, cost_center1),
-			(surplus_account, 0.0, 400.0, cost_center1)
+			('Sales - TPC', 200.0, 0.0, cost_center2),
 		)
 
 		pcv_gle = frappe.db.sql("""
-			select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s
+			select account, debit, credit, cost_center
+			from `tabGL Entry` where voucher_no=%s
+			order by account, cost_center
 		""", (pcv.name))
 
-		self.assertTrue(pcv_gle, expected_gle)
+		self.assertEqual(pcv_gle, expected_gle)
 
 	def test_period_closing_with_finance_book_entries(self):
 		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
@@ -146,39 +128,35 @@
 		jv.save()
 		jv.submit()
 
-		pcv = frappe.get_doc({
-			"transaction_date": today(),
-			"posting_date": today(),
-			"fiscal_year": get_fiscal_year(today())[0],
-			"company": company,
-			"closing_account_head": surplus_account,
-			"remarks": "Test",
-			"doctype": "Period Closing Voucher"
-		})
-		pcv.insert()
-		pcv.submit()
+		pcv = self.make_period_closing_voucher()
+		surplus_account = pcv.closing_account_head
 
 		expected_gle = (
-			(surplus_account, 0.0, 400.0, ''),
+			(surplus_account, 0.0, 400.0, None),
 			(surplus_account, 0.0, 400.0, jv.finance_book),
-			('Sales - TPC', 400.0, 0.0, ''),
+			('Sales - TPC', 400.0, 0.0, None),
 			('Sales - TPC', 400.0, 0.0, jv.finance_book)
 		)
 
 		pcv_gle = frappe.db.sql("""
-			select account, debit, credit, finance_book from `tabGL Entry` where voucher_no=%s
+			select account, debit, credit, finance_book
+			from `tabGL Entry` where voucher_no=%s
+			order by account, finance_book
 		""", (pcv.name))
 
-		self.assertTrue(pcv_gle, expected_gle)
+		self.assertEqual(pcv_gle, expected_gle)
 
 	def make_period_closing_voucher(self):
+		surplus_account = create_account()
+		cost_center = create_cost_center("Test Cost Center 1")
 		pcv = frappe.get_doc({
 			"doctype": "Period Closing Voucher",
-			"closing_account_head": "_Test Account Reserves and Surplus - _TC",
-			"company": "_Test Company",
-			"fiscal_year": get_fiscal_year(today(), company="_Test Company")[0],
+			"transaction_date": today(),
 			"posting_date": today(),
-			"cost_center": "_Test Cost Center - _TC",
+			"company": "Test PCV Company",
+			"fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
+			"cost_center": cost_center,
+			"closing_account_head": surplus_account,
 			"remarks": "test"
 		})
 		pcv.insert()
diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
deleted file mode 100644
index 48109b1..0000000
--- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Closing Entry", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new POS Closing Entry
-		() => frappe.tests.make('POS Closing Entry', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.js b/erpnext/accounts/doctype/pos_profile/test_pos_profile.js
deleted file mode 100644
index 42e5b7f..0000000
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Profile", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('POS Profile', [
-		// insert a new POS Profile
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js b/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js
deleted file mode 100644
index 5449ab7..0000000
--- a/erpnext/accounts/doctype/pos_profile_user/test_pos_profile_user.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Profile User", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new POS Profile User
-		() => frappe.tests.make('POS Profile User', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js b/erpnext/accounts/doctype/pos_settings/test_pos_settings.js
deleted file mode 100644
index 639c94e..0000000
--- a/erpnext/accounts/doctype/pos_settings/test_pos_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: POS Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new POS Settings
-		() => frappe.tests.make('POS Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 94abf3b..5467cb0 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -475,7 +475,20 @@
 						frappe.msgprint(_("User has not applied rule on the invoice {0}")
 							.format(doc.name))
 					else:
-						doc.set(field, d.get(pr_field))
+						if not d.coupon_code_based:
+							doc.set(field, d.get(pr_field))
+						elif doc.get('coupon_code'):
+							# coupon code based pricing rule
+							coupon_code_pricing_rule = frappe.db.get_value('Coupon Code', doc.get('coupon_code'), 'pricing_rule')
+							if coupon_code_pricing_rule == d.name:
+								# if selected coupon code is linked with pricing rule
+								doc.set(field, d.get(pr_field))
+							else:
+								# reset discount if not linked
+								doc.set(field, 0)
+						else:
+							# if coupon code based but no coupon code selected
+							doc.set(field, 0)
 
 				doc.calculate_taxes_and_totals()
 			elif d.price_or_product_discount == 'Product':
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index a16795e..e2f02f3 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -415,6 +415,8 @@
 		self.update_project()
 		update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
 
+		self.process_common_party_accounting()
+
 	def make_gl_entries(self, gl_entries=None, from_repost=False):
 		if not gl_entries:
 			gl_entries = self.get_gl_entries()
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 1cf0df0..fe3ed16 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -253,6 +253,8 @@
 		if "Healthcare" in active_domains:
 			manage_invoice_submit_cancel(self, "on_submit")
 
+		self.process_common_party_accounting()
+
 	def validate_pos_return(self):
 
 		if self.is_pos and self.is_return:
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c3d83c7..e06a3bb 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -151,7 +151,7 @@
 		si1 = create_sales_invoice(rate=1000)
 		si2 = create_sales_invoice(rate=300)
 		si3 = create_sales_invoice(qty=-1, rate=300, is_return=1)
-		
+
 
 		pe = get_payment_entry("Sales Invoice", si1.name, bank_account="_Test Bank - _TC")
 		pe.append('references', {
@@ -1140,6 +1140,18 @@
 		self.assertEqual(loss_for_si['credit'], loss_for_return_si['debit'])
 		self.assertEqual(loss_for_si['debit'], loss_for_return_si['credit'])
 
+	def test_incoming_rate_for_stand_alone_credit_note(self):
+		return_si = create_sales_invoice(is_return=1, update_stock=1, qty=-1, rate=90000, incoming_rate=10,
+			company='_Test Company with perpetual inventory', warehouse='Stores - TCP1', debit_to='Debtors - TCP1',
+			income_account='Sales - TCP1', expense_account='Cost of Goods Sold - TCP1', cost_center='Main - TCP1')
+
+		incoming_rate = frappe.db.get_value('Stock Ledger Entry', {'voucher_no': return_si.name}, 'incoming_rate')
+		debit_amount = frappe.db.get_value('GL Entry',
+			{'voucher_no': return_si.name, 'account': 'Stock In Hand - TCP1'}, 'debit')
+
+		self.assertEqual(debit_amount, 10.0)
+		self.assertEqual(incoming_rate, 10.0)
+
 	def test_discount_on_net_total(self):
 		si = frappe.copy_doc(test_records[2])
 		si.apply_discount_on = "Net Total"
@@ -1816,23 +1828,13 @@
 		acc_settings.save()
 
 	def test_inter_company_transaction(self):
+		from erpnext.selling.doctype.customer.test_customer import create_internal_customer
 
-		if not frappe.db.exists("Customer", "_Test Internal Customer"):
-			customer = frappe.get_doc({
-				"customer_group": "_Test Customer Group",
-				"customer_name": "_Test Internal Customer",
-				"customer_type": "Individual",
-				"doctype": "Customer",
-				"territory": "_Test Territory",
-				"is_internal_customer": 1,
-				"represents_company": "_Test Company 1"
-			})
-
-			customer.append("companies", {
-				"company": "Wind Power LLC"
-			})
-
-			customer.insert()
+		create_internal_customer(
+			customer_name="_Test Internal Customer",
+			represents_company="_Test Company 1",
+			allowed_to_interact_with="Wind Power LLC"
+		)
 
 		if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
 			supplier = frappe.get_doc({
@@ -1958,8 +1960,43 @@
 		frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
 
+	def test_sle_if_target_warehouse_exists_accidentally(self):
+		"""
+			Check if inward entry exists if Target Warehouse accidentally exists
+			but Customer is not an internal customer.
+		"""
+		se = make_stock_entry(
+			item_code="138-CMS Shoe",
+			target="Finished Goods - _TC",
+			company = "_Test Company",
+			qty=1,
+			basic_rate=500
+		)
+
+		si = frappe.copy_doc(test_records[0])
+		si.update_stock = 1
+		si.set_warehouse = "Finished Goods - _TC"
+		si.set_target_warehouse = "Stores - _TC"
+		si.get("items")[0].warehouse = "Finished Goods - _TC"
+		si.get("items")[0].target_warehouse = "Stores - _TC"
+		si.insert()
+		si.submit()
+
+		sles = frappe.get_all("Stock Ledger Entry", filters={"voucher_no": si.name},
+			fields=["name", "actual_qty"])
+
+		# check if only one SLE for outward entry is created
+		self.assertEqual(len(sles), 1)
+		self.assertEqual(sles[0].actual_qty, -1)
+
+		# tear down
+		si.cancel()
+		se.cancel()
+
 	def test_internal_transfer_gl_entry(self):
 		## Create internal transfer account
+		from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+
 		account = create_account(account_name="Unrealized Profit",
 			parent_account="Current Liabilities - TCP1", company="_Test Company with perpetual inventory")
 
@@ -2163,6 +2200,50 @@
 			self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
 			self.assertTrue(schedule.journal_entry)
 
+	def test_sales_invoice_against_supplier(self):
+		from erpnext.accounts.doctype.opening_invoice_creation_tool.test_opening_invoice_creation_tool import make_customer
+		from erpnext.buying.doctype.supplier.test_supplier import create_supplier
+
+		# create a customer
+		customer = make_customer(customer="_Test Common Supplier")
+		# create a supplier
+		supplier = create_supplier(supplier_name="_Test Common Supplier").name
+
+		# create a party link between customer & supplier
+		# set primary role as supplier
+		party_link = frappe.new_doc("Party Link")
+		party_link.primary_role = "Supplier"
+		party_link.primary_party = supplier
+		party_link.secondary_role = "Customer"
+		party_link.secondary_party = customer
+		party_link.save()
+
+		# enable common party accounting
+		frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 1)
+
+		# create a sales invoice
+		si = create_sales_invoice(customer=customer, parent_cost_center="_Test Cost Center - _TC")
+
+		# check outstanding of sales invoice
+		si.reload()
+		self.assertEqual(si.status, 'Paid')
+		self.assertEqual(flt(si.outstanding_amount), 0.0)
+
+		# check creation of journal entry
+		jv = frappe.get_all('Journal Entry Account', {
+			'account': si.debit_to,
+			'party_type': 'Customer',
+			'party': si.customer,
+			'reference_type': si.doctype,
+			'reference_name': si.name
+		}, pluck='credit_in_account_currency')
+
+		self.assertTrue(jv)
+		self.assertEqual(jv[0], si.grand_total)
+
+		party_link.delete()
+		frappe.db.set_value('Accounts Settings', None, 'enable_common_party_accounting', 0)
+
 def get_sales_invoice_for_e_invoice():
 	si = make_sales_invoice_for_ewaybill()
 	si.naming_series = 'INV-2020-.#####'
@@ -2375,7 +2456,8 @@
 		"asset": args.asset or None,
 		"cost_center": args.cost_center or "_Test Cost Center - _TC",
 		"serial_no": args.serial_no,
-		"conversion_factor": 1
+		"conversion_factor": 1,
+		"incoming_rate": args.incoming_rate or 0
 	})
 
 	if not args.do_not_save:
@@ -2472,29 +2554,6 @@
 	"row_id": 1
 	}]
 
-def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
-	if not frappe.db.exists("Customer", customer_name):
-		customer = frappe.get_doc({
-			"customer_group": "_Test Customer Group",
-			"customer_name": customer_name,
-			"customer_type": "Individual",
-			"doctype": "Customer",
-			"territory": "_Test Territory",
-			"is_internal_customer": 1,
-			"represents_company": represents_company
-		})
-
-		customer.append("companies", {
-			"company": allowed_to_interact_with
-		})
-
-		customer.insert()
-		customer_name = customer.name
-	else:
-		customer_name = frappe.db.get_value("Customer", customer_name)
-
-	return customer_name
-
 def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
 	if not frappe.db.exists("Supplier", supplier_name):
 		supplier = frappe.get_doc({
diff --git a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
index c77076c..b90f3f0 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -53,7 +53,6 @@
   "column_break_24",
   "base_net_rate",
   "base_net_amount",
-  "incoming_rate",
   "drop_ship",
   "delivered_by_supplier",
   "accounting",
@@ -81,6 +80,7 @@
   "target_warehouse",
   "quality_inspection",
   "batch_no",
+  "incoming_rate",
   "col_break5",
   "allow_zero_valuation_rate",
   "serial_no",
@@ -807,12 +807,12 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval:parent.is_return && parent.update_stock && !parent.return_against",
    "fieldname": "incoming_rate",
    "fieldtype": "Currency",
-   "label": "Incoming Rate",
+   "label": "Incoming Rate (Costing)",
    "no_copy": 1,
-   "print_hide": 1,
-   "read_only": 1
+   "print_hide": 1
   },
   {
    "depends_on": "eval: doc.uom != doc.stock_uom",
@@ -833,7 +833,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-08-12 20:15:47.668399",
+ "modified": "2021-08-19 13:41:53.435827",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
index 0e01188..97a6fdd 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
@@ -5,25 +5,3 @@
 
 {% include "erpnext/public/js/controllers/accounts.js" %}
 
-frappe.tour['Sales Taxes and Charges Template'] = [
-	{
-		fieldname: "title",
-		title: __("Title"),
-		description: __("A name by which you will identify this template. You can change this later."),
-	},
-	{
-		fieldname: "company",
-		title: __("Company"),
-		description: __("Company for which this tax template will be applicable"),
-	},
-	{
-		fieldname: "is_default",
-		title: __("Is this Default?"),
-		description: __("Set this template as the default for all sales transactions"),
-	},
-	{
-		fieldname: "taxes",
-		title: __("Taxes Table"),
-		description: __("You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount."),
-	}
-];
diff --git a/erpnext/accounts/doctype/share_transfer/test_share_transfer.js b/erpnext/accounts/doctype/share_transfer/test_share_transfer.js
deleted file mode 100644
index e5530fa..0000000
--- a/erpnext/accounts/doctype/share_transfer/test_share_transfer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Share Transfer", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Share Transfer
-		() => frappe.tests.make('Share Transfer', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/share_type/test_share_type.js b/erpnext/accounts/doctype/share_type/test_share_type.js
deleted file mode 100644
index 620afa2..0000000
--- a/erpnext/accounts/doctype/share_type/test_share_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Share Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Share Type
-		() => frappe.tests.make('Share Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/shareholder/test_shareholder.js b/erpnext/accounts/doctype/shareholder/test_shareholder.js
deleted file mode 100644
index 61c5312..0000000
--- a/erpnext/accounts/doctype/shareholder/test_shareholder.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shareholder", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shareholder
-		() => frappe.tests.make('Shareholder', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js b/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js
deleted file mode 100644
index 15d3df2..0000000
--- a/erpnext/accounts/doctype/subscription_invoice/test_subscription_invoice.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Subscription Invoice", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Subscription Invoice
-		() => frappe.tests.make('Subscription Invoice', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
index 771611a..878ae09 100644
--- a/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
+++ b/erpnext/accounts/doctype/subscription_plan/subscription_plan.json
@@ -21,7 +21,7 @@
   "column_break_13",
   "billing_interval_count",
   "payment_plan_section",
-  "payment_plan_id",
+  "product_price_id",
   "column_break_16",
   "payment_gateway",
   "accounting_dimensions_section",
@@ -115,11 +115,6 @@
    "label": "Payment Plan"
   },
   {
-   "fieldname": "payment_plan_id",
-   "fieldtype": "Data",
-   "label": "Payment Plan"
-  },
-  {
    "fieldname": "column_break_16",
    "fieldtype": "Column Break"
   },
@@ -144,10 +139,15 @@
    "fieldtype": "Link",
    "label": "Cost Center",
    "options": "Cost Center"
+  },
+  {
+   "fieldname": "product_price_id",
+   "fieldtype": "Data",
+   "label": "Product Price ID"
   }
  ],
  "links": [],
- "modified": "2021-08-09 10:53:44.205774",
+ "modified": "2021-08-13 10:53:44.205774",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Subscription Plan",
diff --git a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js b/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js
deleted file mode 100644
index 3ceb9a6..0000000
--- a/erpnext/accounts/doctype/subscription_plan/test_subscription_plan.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Subscription Plan", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Subscription Plan
-		() => frappe.tests.make('Subscription Plan', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js b/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js
deleted file mode 100644
index 5a751ea..0000000
--- a/erpnext/accounts/doctype/subscription_settings/test_subscription_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Subscription Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Subscription Settings
-		() => frappe.tests.make('Subscription Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/tax_category/test_tax_category.js b/erpnext/accounts/doctype/tax_category/test_tax_category.js
deleted file mode 100644
index 5142456..0000000
--- a/erpnext/accounts/doctype/tax_category/test_tax_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tax Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Tax Category
-		() => frappe.tests.make('Tax Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.js b/erpnext/accounts/doctype/tax_rule/test_tax_rule.js
deleted file mode 100644
index 72d177d..0000000
--- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tax Rule", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Tax Rule
-		() => frappe.tests.make('Tax Rule', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js
deleted file mode 100644
index eab98d4..0000000
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tax Withholding Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Tax Withholding Category
-		() => frappe.tests.make('Tax Withholding Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json b/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json
new file mode 100644
index 0000000..e2bf50d
--- /dev/null
+++ b/erpnext/accounts/form_tour/accounts_settings/accounts_settings.json
@@ -0,0 +1,113 @@
+{
+ "creation": "2021-06-29 17:00:18.273054",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-06-29 17:00:26.145996",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Accounts Settings",
+ "owner": "Administrator",
+ "reference_doctype": "Accounts Settings",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.",
+   "field": "",
+   "fieldname": "over_billing_allowance",
+   "fieldtype": "Currency",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Over Billing Allowance (%)",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Over Billing Allowance Percentage"
+  },
+  {
+   "description": "Select the role that is allowed to overbill a transactions.",
+   "field": "",
+   "fieldname": "role_allowed_to_over_bill",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Role Allowed to Over Bill ",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Role Allowed to Over Bill"
+  },
+  {
+   "description": "If checked, system will unlink the payment against the respective invoice.",
+   "field": "",
+   "fieldname": "unlink_payment_on_cancellation_of_invoice",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Unlink Payment on Cancellation of Invoice",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Unlink Payment on Cancellation of Invoice"
+  },
+  {
+   "description": "Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.",
+   "field": "",
+   "fieldname": "unlink_advance_payment_on_cancelation_of_order",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Unlink Advance Payment on Cancellation of Order",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Unlink Advance Payment on Cancellation of Order"
+  },
+  {
+   "description": "Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.",
+   "field": "",
+   "fieldname": "determine_address_tax_category_from",
+   "fieldtype": "Select",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Determine Address Tax Category From",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Determine Address Tax Category From"
+  },
+  {
+   "description": "Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role.",
+   "field": "",
+   "fieldname": "acc_frozen_upto",
+   "fieldtype": "Date",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Accounts Frozen Till Date",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Accounts Frozen Upto"
+  },
+  {
+   "description": "Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.",
+   "field": "",
+   "fieldname": "frozen_accounts_modifier",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries"
+  },
+  {
+   "description": "Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.",
+   "field": "",
+   "fieldname": "credit_controller",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Credit Controller",
+   "parent_field": "",
+   "position": "Left",
+   "title": "Credit Controller"
+  }
+ ],
+ "title": "Accounts Settings"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json b/erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json
new file mode 100644
index 0000000..2dffcd1
--- /dev/null
+++ b/erpnext/accounts/form_tour/purchase_invoice/purchase_invoice.json
@@ -0,0 +1,96 @@
+{
+ "creation": "2021-06-29 16:31:48.558826",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-06-29 16:31:48.558826",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Purchase Invoice",
+ "owner": "Administrator",
+ "reference_doctype": "Purchase Invoice",
+ "save_on_complete": 1,
+ "steps": [
+  {
+   "description": "Select Supplier",
+   "field": "",
+   "fieldname": "supplier",
+   "fieldtype": "Link",
+   "has_next_condition": 1,
+   "is_table_field": 0,
+   "label": "Supplier",
+   "next_step_condition": "supplier",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Select Supplier"
+  },
+  {
+   "description": "Add items in the table",
+   "field": "",
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Items",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "List of Items"
+  },
+  {
+   "child_doctype": "Purchase Invoice Item",
+   "description": "Select an item",
+   "field": "",
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 1,
+   "label": "Item",
+   "parent_field": "",
+   "parent_fieldname": "items",
+   "position": "Right",
+   "title": "Select Item"
+  },
+  {
+   "child_doctype": "Purchase Invoice Item",
+   "description": "Enter the quantity",
+   "field": "",
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "has_next_condition": 0,
+   "is_table_field": 1,
+   "label": "Accepted Qty",
+   "parent_field": "",
+   "parent_fieldname": "items",
+   "position": "Right",
+   "title": "Enter Quantity"
+  },
+  {
+   "child_doctype": "Purchase Invoice Item",
+   "description": "Enter rate of the item",
+   "field": "",
+   "fieldname": "rate",
+   "fieldtype": "Currency",
+   "has_next_condition": 0,
+   "is_table_field": 1,
+   "label": "Rate",
+   "parent_field": "",
+   "parent_fieldname": "items",
+   "position": "Right",
+   "title": "Enter Rate"
+  },
+  {
+   "description": "You can add taxes here",
+   "field": "",
+   "fieldname": "taxes",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Purchase Taxes and Charges",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Select taxes"
+  }
+ ],
+ "title": "Purchase Invoice"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
new file mode 100644
index 0000000..7de9ae1
--- /dev/null
+++ b/erpnext/accounts/form_tour/sales_taxes_and_charges_template/sales_taxes_and_charges_template.json
@@ -0,0 +1,65 @@
+{
+ "creation": "2021-08-24 12:28:18.044902",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-24 12:28:18.044902",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Sales Taxes and Charges Template",
+ "owner": "Administrator",
+ "reference_doctype": "Sales Taxes and Charges Template",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "A name by which you will identify this template. You can change this later.",
+   "field": "",
+   "fieldname": "title",
+   "fieldtype": "Data",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Title",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Title"
+  },
+  {
+   "description": "Company for which this tax template will be applicable",
+   "field": "",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Company",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Company"
+  },
+  {
+   "description": "Set this template as the default for all sales transactions",
+   "field": "",
+   "fieldname": "is_default",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Default",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Is this Default Tax Template?"
+  },
+  {
+   "description": "You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount.",
+   "field": "",
+   "fieldname": "taxes",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Sales Taxes and Charges",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Taxes Table"
+  }
+ ],
+ "title": "Sales Taxes and Charges Template"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
index 6b5c5a1..2e0ab43 100644
--- a/erpnext/accounts/module_onboarding/accounts/accounts.json
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -13,35 +13,35 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
  "idx": 0,
  "is_complete": 0,
- "modified": "2020-10-30 15:41:15.547225",
+ "modified": "2021-08-13 11:59:35.690443",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts",
  "owner": "Administrator",
  "steps": [
   {
+   "step": "Company"
+  },
+  {
    "step": "Chart of Accounts"
   },
   {
    "step": "Setup Taxes"
   },
   {
-   "step": "Create a Product"
+   "step": "Accounts Settings"
   },
   {
-   "step": "Create a Supplier"
+   "step": "Cost Centers for Report and Budgeting"
   },
   {
    "step": "Create Your First Purchase Invoice"
   },
   {
-   "step": "Create a Customer"
+   "step": "Updating Opening Balances"
   },
   {
-   "step": "Create Your First Sales Invoice"
-  },
-  {
-   "step": "Configure Account Settings"
+   "step": "Financial Statements"
   }
  ],
  "subtitle": "Accounts, Invoices, Taxation, and more.",
diff --git a/erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json b/erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json
new file mode 100644
index 0000000..3f44a73
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/accounts_settings/accounts_settings.json
@@ -0,0 +1,21 @@
+{
+ "action": "Show Form Tour",
+ "action_label": "Take a quick walk-through of Accounts Settings",
+ "creation": "2021-06-29 16:42:03.400731",
+ "description": "# Account Settings\n\nIn ERPNext, Accounting features are configurable as per your business needs. Accounts Settings is the place to define some of your accounting preferences like:\n\n - Credit Limit and over billing settings\n - Taxation preferences\n - Deferred accounting preferences\n",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2021-08-13 11:50:06.227835",
+ "modified_by": "Administrator",
+ "name": "Accounts Settings",
+ "owner": "Administrator",
+ "reference_document": "Accounts Settings",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Accounts Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
index fc49bd6..67553ba 100644
--- a/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
+++ b/erpnext/accounts/onboarding_step/chart_of_accounts/chart_of_accounts.json
@@ -1,10 +1,10 @@
 {
- "action": "Go to Page",
- "action_label": "View Chart of Accounts",
+ "action": "Watch Video",
+ "action_label": "Learn more about Chart of Accounts",
  "callback_message": "You can continue with the onboarding after exploring this page",
  "callback_title": "Awesome Work",
  "creation": "2020-05-13 19:58:20.928127",
- "description": "# Chart Of Accounts\n\nThe Chart of Accounts is the blueprint of the accounts in your organization.\nIt is a tree view of the names of the Accounts (Ledgers and Groups) that a Company requires to manage its books of accounts. ERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to your needs and legal requirements.\n\nFor each company, Chart of Accounts signifies the way to classify the accounting entries, mostly\nbased on statutory (tax, compliance to government regulations) requirements.\n\nThere's a brief video tutorial about chart of accounts in the next step.",
+ "description": "# Chart Of Accounts\n\nERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
@@ -12,7 +12,7 @@
  "is_complete": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-10-30 14:35:59.474920",
+ "modified": "2021-08-13 11:46:25.878506",
  "modified_by": "Administrator",
  "name": "Chart of Accounts",
  "owner": "Administrator",
@@ -21,5 +21,6 @@
  "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Review Chart of Accounts",
- "validate_action": 0
+ "validate_action": 0,
+ "video_url": "https://www.youtube.com/embed/AcfMCT7wLLo"
 }
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/company/company.json b/erpnext/accounts/onboarding_step/company/company.json
new file mode 100644
index 0000000..4992e4d
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/company/company.json
@@ -0,0 +1,22 @@
+{
+ "action": "Go to Page",
+ "action_label": "Let's Review your Company",
+ "creation": "2021-06-29 14:47:42.497318",
+ "description": "# Company\n\nIn ERPNext, you can also create multiple companies, and establish relationships (group/subsidiary) among them.\n\nWithin the company master, you can capture various default accounts for that Company and set crucial settings related to the accounting methodology followed for a company. \n",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 11:43:35.767341",
+ "modified_by": "Administrator",
+ "name": "Company",
+ "owner": "Administrator",
+ "path": "app/company",
+ "reference_document": "Company",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Review Company",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json b/erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json
new file mode 100644
index 0000000..252b075
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/cost_centers_for_report_and_budgeting/cost_centers_for_report_and_budgeting.json
@@ -0,0 +1,21 @@
+{
+ "action": "Go to Page",
+ "action_label": "View Cost Center Tree",
+ "creation": "2021-07-12 12:02:05.726608",
+ "description": "# Cost Centers for Budgeting and Analysis\n\nWhile your Books of Accounts are framed to fulfill statutory requirements, you can set up Cost Center and Accounting Dimensions to address your companies reporting and budgeting requirements.\n\nClick here to learn more about how  <b>[Cost Center](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/cost-center)</b> and <b> [Dimensions](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/accounting-dimensions)</b> allow you to get advanced financial analytics reports from ERPNext.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 11:55:08.510366",
+ "modified_by": "Administrator",
+ "name": "Cost Centers for Report and Budgeting",
+ "owner": "Administrator",
+ "path": "cost-center/view/tree",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Cost Centers for Budgeting and Analysis",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
index ddbc89e..f4e298e 100644
--- a/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
+++ b/erpnext/accounts/onboarding_step/create_your_first_purchase_invoice/create_your_first_purchase_invoice.json
@@ -1,14 +1,15 @@
 {
- "action": "Create Entry",
+ "action": "Show Form Tour",
+ "action_label": "Let\u2019s create your first Purchase Invoice",
  "creation": "2020-05-14 22:10:07.049704",
- "description": "# What's a Purchase Invoice?\n\nA Purchase Invoice is a bill you receive from your Suppliers against which you need to make the payment.\nPurchase Invoice is the exact opposite of your Sales Invoice. Here you accrue expenses to your Supplier. \n\nThe following is what a typical purchase cycle looks like, however you can create a purchase invoice directly as well.\n\n![Purchase Flow](https://docs.erpnext.com/docs/assets/img/accounts/pi-flow.png)\n\n",
+ "description": "# Create your first Purchase Invoice\n\nA Purchase Invoice is a bill received from a Supplier for a product(s) or service(s) delivery to your company. You can track payables through Purchase Invoice and process Payment Entries against it.\n\nPurchase Invoices can also be created against a Purchase Order or Purchase Receipt.",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-10-30 15:30:26.337773",
+ "modified": "2021-08-13 11:56:11.677253",
  "modified_by": "Administrator",
  "name": "Create Your First Purchase Invoice",
  "owner": "Administrator",
diff --git a/erpnext/accounts/onboarding_step/financial_statements/financial_statements.json b/erpnext/accounts/onboarding_step/financial_statements/financial_statements.json
new file mode 100644
index 0000000..85cf9cd
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/financial_statements/financial_statements.json
@@ -0,0 +1,23 @@
+{
+ "action": "View Report",
+ "creation": "2021-07-12 12:08:47.026115",
+ "description": "# Financial Statements\n\nIn ERPNext, you can get crucial financial reports like [Balance Sheet] and [Profit and Loss] statements with a click of a button. You can run in the report for a different period and plot analytics charts premised on statement data. For more reports, check sections like Financial Statements, General Ledger, and Profitability reports.\n\n<b>[Check Accounting reports](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/accounting-reports)</b>",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 11:59:18.767407",
+ "modified_by": "Administrator",
+ "name": "Financial Statements",
+ "owner": "Administrator",
+ "reference_report": "General Ledger",
+ "report_description": "General Ledger",
+ "report_reference_doctype": "GL Entry",
+ "report_type": "Script Report",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Financial Statements",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
index a492201..9f4c873 100644
--- a/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
+++ b/erpnext/accounts/onboarding_step/setup_taxes/setup_taxes.json
@@ -1,21 +1,21 @@
 {
  "action": "Create Entry",
- "action_label": "Make a Sales Tax Template",
+ "action_label": "Manage Sales Tax Templates",
  "creation": "2020-05-13 19:29:43.844463",
- "description": "# Setting up Taxes\n\nAny sophisticated accounting system, including ERPNext will have automatic tax calculations for your transactions. These calculations are based on user defined rules in compliance to local rules and regulations.\n\nERPNext allows this via *Tax Templates*. These templates can be used in Sales Orders and Sales Invoices. Other types of charges that may apply to your invoices (like shipping, insurance etc.) can also be configured as taxes.\n\nFor Tax Accounts that you want to use in the tax templates, go to:\n\n`> Accounting > Taxes > Sales Taxes and Charges Template`\n\nYou can read more about these templates in our documentation [here](https://docs.erpnext.com/docs/user/manual/en/selling/sales-taxes-and-charges-template)\n",
+ "description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.\n",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-10-30 14:54:18.087383",
+ "modified": "2021-08-13 11:48:37.238610",
  "modified_by": "Administrator",
  "name": "Setup Taxes",
  "owner": "Administrator",
  "reference_document": "Sales Taxes and Charges Template",
  "show_form_tour": 1,
  "show_full_form": 1,
- "title": "Lets create a Tax Template for Sales ",
+ "title": "Setting up Taxes",
  "validate_action": 0
 }
\ No newline at end of file
diff --git a/erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json b/erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json
new file mode 100644
index 0000000..c0849a4
--- /dev/null
+++ b/erpnext/accounts/onboarding_step/updating_opening_balances/updating_opening_balances.json
@@ -0,0 +1,22 @@
+{
+ "action": "Watch Video",
+ "action_label": "Learn how to update opening balances",
+ "creation": "2021-07-12 11:53:50.525030",
+ "description": "# Updating Opening Balances\n\nOnce you close the financial statement in previous accounting software, you can update the same as opening in your ERPNext's Balance Sheet accounts. This will allow you to get complete financial statements from ERPNext in the coming years, and discontinue the parallel accounting system right away.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "intro_video_url": "https://www.youtube.com/embed/U5wPIvEn-0c",
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 11:56:45.483418",
+ "modified_by": "Administrator",
+ "name": "Updating Opening Balances",
+ "owner": "Administrator",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Updating Opening Balances",
+ "validate_action": 1,
+ "video_url": "https://www.youtube.com/embed/U5wPIvEn-0c"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 118f628..c46eb7e 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -341,31 +341,42 @@
 
 def reconcile_against_document(args):
 	"""
-		Cancel JV, Update aginst document, split if required and resubmit jv
+		Cancel PE or JV, Update against document, split if required and resubmit
 	"""
-	for d in args:
+	# To optimize making GL Entry for PE or JV with multiple references
+	reconciled_entries = {}
+	for row in args:
+		if not reconciled_entries.get((row.voucher_type, row.voucher_no)):
+			reconciled_entries[(row.voucher_type, row.voucher_no)] = []
 
-		check_if_advance_entry_modified(d)
-		validate_allocated_amount(d)
+		reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
+
+	for key, entries in reconciled_entries.items():
+		voucher_type = key[0]
+		voucher_no = key[1]
 
 		# cancel advance entry
-		doc = frappe.get_doc(d.voucher_type, d.voucher_no)
-
+		doc = frappe.get_doc(voucher_type, voucher_no)
 		frappe.flags.ignore_party_validation = True
 		doc.make_gl_entries(cancel=1, adv_adj=1)
 
-		# update ref in advance entry
-		if d.voucher_type == "Journal Entry":
-			update_reference_in_journal_entry(d, doc)
-		else:
-			update_reference_in_payment_entry(d, doc)
+		for entry in entries:
+			check_if_advance_entry_modified(entry)
+			validate_allocated_amount(entry)
 
+			# update ref in advance entry
+			if voucher_type == "Journal Entry":
+				update_reference_in_journal_entry(entry, doc, do_not_save=True)
+			else:
+				update_reference_in_payment_entry(entry, doc, do_not_save=True)
+
+		doc.save(ignore_permissions=True)
 		# re-submit advance entry
-		doc = frappe.get_doc(d.voucher_type, d.voucher_no)
+		doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
 		doc.make_gl_entries(cancel = 0, adv_adj =1)
 		frappe.flags.ignore_party_validation = False
 
-		if d.voucher_type in ('Payment Entry', 'Journal Entry'):
+		if entry.voucher_type in ('Payment Entry', 'Journal Entry'):
 			doc.update_expense_claim()
 
 def check_if_advance_entry_modified(args):
@@ -374,6 +385,9 @@
 		check if amount is same
 		check if jv is submitted
 	"""
+	if not args.get('unreconciled_amount'):
+		args.update({'unreconciled_amount': args.get('unadjusted_amount')})
+
 	ret = None
 	if args.voucher_type == "Journal Entry":
 		ret = frappe.db.sql("""
@@ -395,14 +409,14 @@
 					and t1.name = %(voucher_no)s and t2.name = %(voucher_detail_no)s
 					and t1.party_type = %(party_type)s and t1.party = %(party)s and t1.{0} = %(account)s
 					and t2.reference_doctype in ("", "Sales Order", "Purchase Order")
-					and t2.allocated_amount = %(unadjusted_amount)s
+					and t2.allocated_amount = %(unreconciled_amount)s
 			""".format(party_account_field), args)
 		else:
 			ret = frappe.db.sql("""select name from `tabPayment Entry`
 				where
 					name = %(voucher_no)s and docstatus = 1
 					and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
-					and unallocated_amount = %(unadjusted_amount)s
+					and unallocated_amount = %(unreconciled_amount)s
 			""".format(party_account_field), args)
 
 	if not ret:
@@ -415,58 +429,44 @@
 	elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
 		throw(_("Allocated amount cannot be greater than unadjusted amount"))
 
-def update_reference_in_journal_entry(d, jv_obj):
+def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
 	"""
 		Updates against document, if partial amount splits into rows
 	"""
-	jv_detail = jv_obj.get("accounts", {"name": d["voucher_detail_no"]})[0]
-	jv_detail.set(d["dr_or_cr"], d["allocated_amount"])
-	jv_detail.set('debit' if d['dr_or_cr']=='debit_in_account_currency' else 'credit',
-		d["allocated_amount"]*flt(jv_detail.exchange_rate))
+	jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
 
-	original_reference_type = jv_detail.reference_type
-	original_reference_name = jv_detail.reference_name
-
-	jv_detail.set("reference_type", d["against_voucher_type"])
-	jv_detail.set("reference_name", d["against_voucher"])
-
-	if d['allocated_amount'] < d['unadjusted_amount']:
-		jvd = frappe.db.sql("""
-			select cost_center, balance, against_account, is_advance,
-				account_type, exchange_rate, account_currency
-			from `tabJournal Entry Account` where name = %s
-		""", d['voucher_detail_no'], as_dict=True)
-
+	if flt(d['unadjusted_amount']) - flt(d['allocated_amount']) != 0:
+		# adjust the unreconciled balance
 		amount_in_account_currency = flt(d['unadjusted_amount']) - flt(d['allocated_amount'])
-		amount_in_company_currency = amount_in_account_currency * flt(jvd[0]['exchange_rate'])
+		amount_in_company_currency = amount_in_account_currency * flt(jv_detail.exchange_rate)
+		jv_detail.set(d['dr_or_cr'], amount_in_account_currency)
+		jv_detail.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit', amount_in_company_currency)
+	else:
+		journal_entry.remove(jv_detail)
 
-		# new entry with balance amount
-		ch = jv_obj.append("accounts")
-		ch.account = d['account']
-		ch.account_type = jvd[0]['account_type']
-		ch.account_currency = jvd[0]['account_currency']
-		ch.exchange_rate = jvd[0]['exchange_rate']
-		ch.party_type = d["party_type"]
-		ch.party = d["party"]
-		ch.cost_center = cstr(jvd[0]["cost_center"])
-		ch.balance = flt(jvd[0]["balance"])
+	# new row with references
+	new_row = journal_entry.append("accounts")
+	new_row.update(jv_detail.as_dict().copy())
 
-		ch.set(d['dr_or_cr'], amount_in_account_currency)
-		ch.set('debit' if d['dr_or_cr']=='debit_in_account_currency' else 'credit', amount_in_company_currency)
+	new_row.set(d["dr_or_cr"], d["allocated_amount"])
+	new_row.set('debit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'credit',
+		d["allocated_amount"] * flt(jv_detail.exchange_rate))
 
-		ch.set('credit_in_account_currency' if d['dr_or_cr']== 'debit_in_account_currency'
-			else 'debit_in_account_currency', 0)
-		ch.set('credit' if d['dr_or_cr']== 'debit_in_account_currency' else 'debit', 0)
+	new_row.set('credit_in_account_currency' if d['dr_or_cr'] == 'debit_in_account_currency'
+		else 'debit_in_account_currency', 0)
+	new_row.set('credit' if d['dr_or_cr'] == 'debit_in_account_currency' else 'debit', 0)
 
-		ch.against_account = cstr(jvd[0]["against_account"])
-		ch.reference_type = original_reference_type
-		ch.reference_name = original_reference_name
-		ch.is_advance = cstr(jvd[0]["is_advance"])
-		ch.docstatus = 1
+	new_row.set("reference_type", d["against_voucher_type"])
+	new_row.set("reference_name", d["against_voucher"])
+
+	new_row.against_account = cstr(jv_detail.against_account)
+	new_row.is_advance = cstr(jv_detail.is_advance)
+	new_row.docstatus = 1
 
 	# will work as update after submit
-	jv_obj.flags.ignore_validate_update_after_submit = True
-	jv_obj.save(ignore_permissions=True)
+	journal_entry.flags.ignore_validate_update_after_submit = True
+	if not do_not_save:
+		journal_entry.save(ignore_permissions=True)
 
 def update_reference_in_payment_entry(d, payment_entry, do_not_save=False):
 	reference_details = {
@@ -576,7 +576,7 @@
 
 @frappe.whitelist()
 def get_company_default(company, fieldname, ignore_validation=False):
-	value = frappe.get_cached_value('Company', company, fieldname)
+	value = frappe.get_cached_value('Company',  company,  fieldname)
 
 	if not ignore_validation and not value:
 		throw(_("Please set default {0} in Company {1}")
@@ -1086,3 +1086,14 @@
 			db_or_cr_stock_adjustment_account : abs(amount)
 		}]
 	}
+
+def check_and_delete_linked_reports(report):
+	""" Check if reports are referenced in Desktop Icon """
+	icons = frappe.get_all("Desktop Icon",
+						fields = ['name'],
+						filters = {
+							"_report": report
+						})
+	if icons:
+		for icon in icons:
+			frappe.delete_doc("Desktop Icon", icon)
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index b5bd14d..2b26ac5 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -234,6 +234,15 @@
    "type": "Link"
   },
   {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Payment Reconciliation",
+   "link_to": "Payment Reconciliation",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "Sales Invoice",
    "hidden": 0,
    "is_query_report": 1,
@@ -341,6 +350,15 @@
    "type": "Link"
   },
   {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Payment Reconciliation",
+   "link_to": "Payment Reconciliation",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "Purchase Invoice",
    "hidden": 0,
    "is_query_report": 1,
@@ -1188,7 +1206,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-08-05 12:15:52.872470",
+ "modified": "2021-08-27 12:15:52.872470",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounting",
@@ -1249,4 +1267,4 @@
   }
  ],
  "title": "Accounting"
-}
\ No newline at end of file
+}
diff --git a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js b/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js
deleted file mode 100644
index f70dcd2..0000000
--- a/erpnext/agriculture/doctype/agriculture_analysis_criteria/test_agriculture_analysis_criteria.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Agriculture Analysis Criteria", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Agriculture Analysis Criteria
-		() => frappe.tests.make('Agriculture Analysis Criteria', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js b/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js
deleted file mode 100644
index a012c4b..0000000
--- a/erpnext/agriculture/doctype/agriculture_task/test_agriculture_task.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Agriculture Task", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Agriculture Task
-		() => frappe.tests.make('Agriculture Task', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js b/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js
deleted file mode 100644
index 786c047..0000000
--- a/erpnext/agriculture/doctype/plant_analysis/test_plant_analysis.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Plant Analysis", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Plant Analysis
-		() => frappe.tests.make('Plant Analysis', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js b/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js
deleted file mode 100644
index 29128eb..0000000
--- a/erpnext/agriculture/doctype/soil_analysis/test_soil_analysis.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Soil Analysis", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Soil Analysis
-		() => frappe.tests.make('Soil Analysis', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/agriculture/doctype/weather/test_weather.js b/erpnext/agriculture/doctype/weather/test_weather.js
deleted file mode 100644
index b5009a4..0000000
--- a/erpnext/agriculture/doctype/weather/test_weather.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Weather", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Weather
-		() => frappe.tests.make('Weather', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset/test_asset.js b/erpnext/assets/doctype/asset/test_asset.js
deleted file mode 100644
index 6119e38..0000000
--- a/erpnext/assets/doctype/asset/test_asset.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset
-		() => frappe.tests.make('Asset', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.js b/erpnext/assets/doctype/asset_category/test_asset_category.js
deleted file mode 100644
index 7e343b7..0000000
--- a/erpnext/assets/doctype/asset_category/test_asset_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Category
-		() => frappe.tests.make('Asset Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js b/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js
deleted file mode 100644
index f9b38a1..0000000
--- a/erpnext/assets/doctype/asset_maintenance/test_asset_maintenance.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Maintenance", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Maintenance
-		() => frappe.tests.make('Asset Maintenance', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js b/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js
deleted file mode 100644
index 4e80184..0000000
--- a/erpnext/assets/doctype/asset_maintenance_log/test_asset_maintenance_log.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Maintenance Log", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Maintenance Log
-		() => frappe.tests.make('Asset Maintenance Log', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js b/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js
deleted file mode 100644
index 41bf696..0000000
--- a/erpnext/assets/doctype/asset_maintenance_team/test_asset_maintenance_team.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Maintenance Team", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Maintenance Team
-		() => frappe.tests.make('Asset Maintenance Team', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_movement/test_asset_movement.js b/erpnext/assets/doctype/asset_movement/test_asset_movement.js
deleted file mode 100644
index b951576..0000000
--- a/erpnext/assets/doctype/asset_movement/test_asset_movement.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Movement", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Movement
-		() => frappe.tests.make('Asset Movement', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_repair/test_asset_repair.js b/erpnext/assets/doctype/asset_repair/test_asset_repair.js
deleted file mode 100644
index 7424ffe..0000000
--- a/erpnext/assets/doctype/asset_repair/test_asset_repair.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Repair", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Repair
-		() => frappe.tests.make('Asset Repair', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js
deleted file mode 100644
index 32831c6..0000000
--- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Asset Value Adjustment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Asset Value Adjustment
-		() => frappe.tests.make('Asset Value Adjustment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/location/test_location.js b/erpnext/assets/doctype/location/test_location.js
deleted file mode 100644
index 3c06b63..0000000
--- a/erpnext/assets/doctype/location/test_location.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Location", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Location
-		() => frappe.tests.make('Location', [
-			// values to be set
-			{ location_name: 'Basil Farm' }
-		]),
-		() => {
-			assert.equal(cur_frm.doc.name, 'Basil Farm');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js b/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js
deleted file mode 100644
index d942e2a..0000000
--- a/erpnext/assets/doctype/maintenance_team_member/test_maintenance_team_member.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Maintenance Team Member", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Maintenance Team Member
-		() => frappe.tests.make('Maintenance Team Member', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/assets/form_tour/asset/asset.json b/erpnext/assets/form_tour/asset/asset.json
new file mode 100644
index 0000000..7c47a38
--- /dev/null
+++ b/erpnext/assets/form_tour/asset/asset.json
@@ -0,0 +1,125 @@
+{
+ "creation": "2021-08-24 16:55:10.923434",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-24 16:55:10.923434",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset",
+ "owner": "Administrator",
+ "reference_doctype": "Asset",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "Select Naming Series based on which Asset ID will be generated",
+   "field": "",
+   "fieldname": "naming_series",
+   "fieldtype": "Select",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Naming Series",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Naming Series"
+  },
+  {
+   "description": "Select an Asset Item",
+   "field": "",
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Item Code",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Item Code"
+  },
+  {
+   "description": "Select a Location",
+   "field": "",
+   "fieldname": "location",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Location",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Location"
+  },
+  {
+   "description": "Check Is Existing Asset",
+   "field": "",
+   "fieldname": "is_existing_asset",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Is Existing Asset",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Is Existing Asset?"
+  },
+  {
+   "description": "Set Available for use date",
+   "field": "",
+   "fieldname": "available_for_use_date",
+   "fieldtype": "Date",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Available-for-use Date",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Available For Use Date"
+  },
+  {
+   "description": "Set Gross purchase amount",
+   "field": "",
+   "fieldname": "gross_purchase_amount",
+   "fieldtype": "Currency",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Gross Purchase Amount",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Gross Purchase Amount"
+  },
+  {
+   "description": "Set Purchase Date",
+   "field": "",
+   "fieldname": "purchase_date",
+   "fieldtype": "Date",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Purchase Date",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Purchase Date"
+  },
+  {
+   "description": "Check Calculate Depreciation",
+   "field": "",
+   "fieldname": "calculate_depreciation",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Calculate Depreciation",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Calculate Depreciation"
+  },
+  {
+   "description": "Enter depreciation which has already been booked for this asset",
+   "field": "",
+   "fieldname": "opening_accumulated_depreciation",
+   "fieldtype": "Currency",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Opening Accumulated Depreciation",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Accumulated Depreciation"
+  }
+ ],
+ "title": "Asset"
+}
\ No newline at end of file
diff --git a/erpnext/assets/form_tour/asset_category/asset_category.json b/erpnext/assets/form_tour/asset_category/asset_category.json
new file mode 100644
index 0000000..0283444
--- /dev/null
+++ b/erpnext/assets/form_tour/asset_category/asset_category.json
@@ -0,0 +1,65 @@
+{
+ "creation": "2021-08-24 12:48:20.763173",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-24 12:48:20.763173",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Category",
+ "owner": "Administrator",
+ "reference_doctype": "Asset Category",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "Name Asset category. You can create categories based on Asset Types like Furniture, Property, Electronics etc.",
+   "field": "",
+   "fieldname": "asset_category_name",
+   "fieldtype": "Data",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Asset Category Name",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Asset Category Name"
+  },
+  {
+   "description": "Check to enable Capital Work in Progress accounting",
+   "field": "",
+   "fieldname": "enable_cwip_accounting",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Enable Capital Work in Progress Accounting",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Enable CWIP Accounting"
+  },
+  {
+   "description": "Add a row to define Depreciation Method and other details. Note that you can leave Finance Book blank to have it's accounting done in the primary books of accounts.",
+   "field": "",
+   "fieldname": "finance_books",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Finance Books",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Finance Book Detail"
+  },
+  {
+   "description": "Select the Fixed Asset and Depreciation accounts applicable for this Asset Category type",
+   "field": "",
+   "fieldname": "accounts",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Accounts",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Accounts"
+  }
+ ],
+ "title": "Asset Category"
+}
\ No newline at end of file
diff --git a/erpnext/assets/module_onboarding/assets/assets.json b/erpnext/assets/module_onboarding/assets/assets.json
index 1086ab4..e6df88b 100644
--- a/erpnext/assets/module_onboarding/assets/assets.json
+++ b/erpnext/assets/module_onboarding/assets/assets.json
@@ -13,26 +13,26 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
  "idx": 0,
  "is_complete": 0,
- "modified": "2020-07-08 14:05:51.828497",
+ "modified": "2021-08-24 17:50:41.573281",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Assets",
  "owner": "Administrator",
  "steps": [
   {
-   "step": "Introduction to Assets"
+   "step": "Fixed Asset Accounts"
   },
   {
-   "step": "Create a Fixed Asset Item"
+   "step": "Asset Category"
   },
   {
-   "step": "Create an Asset Category"
+   "step": "Asset Item"
   },
   {
-   "step": "Purchase an Asset Item"
+   "step": "Asset Purchase"
   },
   {
-   "step": "Create an Asset"
+   "step": "Existing Asset"
   }
  ],
  "subtitle": "Assets, Depreciations, Repairs, and more.",
diff --git a/erpnext/assets/onboarding_step/asset_category/asset_category.json b/erpnext/assets/onboarding_step/asset_category/asset_category.json
new file mode 100644
index 0000000..033e866
--- /dev/null
+++ b/erpnext/assets/onboarding_step/asset_category/asset_category.json
@@ -0,0 +1,21 @@
+{
+ "action": "Show Form Tour",
+ "action_label": "Let's review existing Asset Category",
+ "creation": "2021-08-13 14:26:18.656303",
+ "description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipments\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-24 12:49:37.665239",
+ "modified_by": "Administrator",
+ "name": "Asset Category",
+ "owner": "Administrator",
+ "reference_document": "Asset Category",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Define Asset Category",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/asset_item/asset_item.json b/erpnext/assets/onboarding_step/asset_item/asset_item.json
new file mode 100644
index 0000000..8a174c5
--- /dev/null
+++ b/erpnext/assets/onboarding_step/asset_item/asset_item.json
@@ -0,0 +1,21 @@
+{
+ "action": "Show Form Tour",
+ "action_label": "Let's create a new Asset item",
+ "creation": "2021-08-13 14:27:07.277167",
+ "description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-16 13:59:18.362233",
+ "modified_by": "Administrator",
+ "name": "Asset Item",
+ "owner": "Administrator",
+ "reference_document": "Item",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Create an Asset Item",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json b/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json
new file mode 100644
index 0000000..54611ed
--- /dev/null
+++ b/erpnext/assets/onboarding_step/asset_purchase/asset_purchase.json
@@ -0,0 +1,21 @@
+{
+ "action": "Show Form Tour",
+ "action_label": "Let's create a Purchase Receipt",
+ "creation": "2021-08-13 14:27:53.678621",
+ "description": "# Purchase an Asset\n\nAssets purchases process if done following the standard Purchase cycle. If capital work in progress is enabled in Asset Category, Asset will be created as soon as Purchase Receipt is created for it. You can quickly create a Purchase Receipt for Asset and see its impact on books of accounts.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-24 17:26:57.180637",
+ "modified_by": "Administrator",
+ "name": "Asset Purchase",
+ "owner": "Administrator",
+ "reference_document": "Purchase Receipt",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Purchase an Asset",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/existing_asset/existing_asset.json b/erpnext/assets/onboarding_step/existing_asset/existing_asset.json
new file mode 100644
index 0000000..052d5e8
--- /dev/null
+++ b/erpnext/assets/onboarding_step/existing_asset/existing_asset.json
@@ -0,0 +1,21 @@
+{
+ "action": "Show Form Tour",
+ "action_label": "Add an existing Asset",
+ "creation": "2021-08-13 14:28:30.650459",
+ "description": "# Add an Existing Asset\n\nIf you are just starting with ERPNext, you will need to enter Assets you already possess. You can add them as existing fixed assets in ERPNext. Please note that you will have to make a Journal Entry separately updating the opening balance in the fixed asset account.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-16 14:03:48.850471",
+ "modified_by": "Administrator",
+ "name": "Existing Asset",
+ "owner": "Administrator",
+ "reference_document": "Asset",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Add an Existing Asset",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json b/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json
new file mode 100644
index 0000000..cebee7a
--- /dev/null
+++ b/erpnext/assets/onboarding_step/fixed_asset_accounts/fixed_asset_accounts.json
@@ -0,0 +1,21 @@
+{
+ "action": "Go to Page",
+ "action_label": "Let's walk-through Chart of Accounts to review setup",
+ "creation": "2021-08-13 14:23:09.297765",
+ "description": "# Fixed Asset Accounts\n\nWith the company, a host of fixed asset accounts are pre-configured. To ensure your asset transactions are leading to correct accounting entries, you can review and set up following asset accounts as per your business  requirements.\n - Fixed asset accounts (Asset account)\n - Accumulated depreciation\n - Capital Work in progress (CWIP) account\n - Asset Depreciation account (Expense account)",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-24 17:46:37.646174",
+ "modified_by": "Administrator",
+ "name": "Fixed Asset Accounts",
+ "owner": "Administrator",
+ "path": "app/account/view/tree",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Review Fixed Asset Accounts",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index 1766c2c..7ee9196 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -24,7 +24,26 @@
 				}
 			}
 		});
+
+		frm.set_query("supplier_primary_contact", function(doc) {
+			return {
+				query: "erpnext.buying.doctype.supplier.supplier.get_supplier_primary_contact",
+				filters: {
+					"supplier": doc.name
+				}
+			};
+		});
+
+		frm.set_query("supplier_primary_address", function(doc) {
+			return {
+				filters: {
+					"link_doctype": "Supplier",
+					"link_name": doc.name
+				}
+			};
+		});
 	},
+
 	refresh: function (frm) {
 		frappe.dynamic_link = { doc: frm.doc, fieldname: 'name', doctype: 'Supplier' }
 
@@ -78,6 +97,30 @@
 		});
 	},
 
+	supplier_primary_address: function(frm) {
+		if (frm.doc.supplier_primary_address) {
+			frappe.call({
+				method: 'frappe.contacts.doctype.address.address.get_address_display',
+				args: {
+					"address_dict": frm.doc.supplier_primary_address
+				},
+				callback: function(r) {
+					frm.set_value("primary_address", r.message);
+				}
+			});
+		}
+		if (!frm.doc.supplier_primary_address) {
+			frm.set_value("primary_address", "");
+		}
+	},
+
+	supplier_primary_contact: function(frm) {
+		if (!frm.doc.supplier_primary_contact) {
+			frm.set_value("mobile_no", "");
+			frm.set_value("email_id", "");
+		}
+	},
+
 	is_internal_supplier: function(frm) {
 		if (frm.doc.is_internal_supplier == 1) {
 			frm.toggle_reqd("represents_company", true);
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 38b8dfd..c7a5db5 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -49,6 +49,13 @@
   "address_html",
   "column_break1",
   "contact_html",
+  "primary_address_and_contact_detail_section",
+  "supplier_primary_contact",
+  "mobile_no",
+  "email_id",
+  "column_break_44",
+  "supplier_primary_address",
+  "primary_address",
   "default_payable_accounts",
   "accounts",
   "default_tax_withholding_config",
@@ -378,6 +385,47 @@
    "fieldname": "allow_purchase_invoice_creation_without_purchase_receipt",
    "fieldtype": "Check",
    "label": "Allow Purchase Invoice Creation Without Purchase Receipt"
+  },
+  {
+   "fieldname": "primary_address_and_contact_detail_section",
+   "fieldtype": "Section Break",
+   "label": "Primary Address and Contact Detail"
+  },
+  {
+   "description": "Reselect, if the chosen contact is edited after save",
+   "fieldname": "supplier_primary_contact",
+   "fieldtype": "Link",
+   "label": "Supplier Primary Contact",
+   "options": "Contact"
+  },
+  {
+   "fetch_from": "supplier_primary_contact.mobile_no",
+   "fieldname": "mobile_no",
+   "fieldtype": "Read Only",
+   "label": "Mobile No"
+  },
+  {
+   "fetch_from": "supplier_primary_contact.email_id",
+   "fieldname": "email_id",
+   "fieldtype": "Read Only",
+   "label": "Email Id"
+  },
+  {
+   "fieldname": "column_break_44",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "primary_address",
+   "fieldtype": "Text",
+   "label": "Primary Address",
+   "read_only": 1
+  },
+  {
+   "description": "Reselect, if the chosen address is edited after save",
+   "fieldname": "supplier_primary_address",
+   "fieldtype": "Link",
+   "label": "Supplier Primary Address",
+   "options": "Address"
   }
  ],
  "icon": "fa fa-user",
@@ -390,7 +438,7 @@
    "link_fieldname": "supplier"
   }
  ],
- "modified": "2021-05-18 15:10:11.087191",
+ "modified": "2021-08-27 18:02:44.314077",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Supplier",
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index fd16b23..c9750ca 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -42,7 +42,12 @@
 		if not self.naming_series:
 			self.naming_series = ''
 
+		self.create_primary_contact()
+		self.create_primary_address()
+
 	def validate(self):
+		self.flags.is_new_doc = self.is_new()
+
 		# validation for Naming Series mandatory field...
 		if frappe.defaults.get_global_default('supp_master_name') == 'Naming Series':
 			if not self.naming_series:
@@ -76,7 +81,39 @@
 			frappe.throw(_("Internal Supplier for company {0} already exists").format(
 				frappe.bold(self.represents_company)))
 
+	def create_primary_contact(self):
+		from erpnext.selling.doctype.customer.customer import make_contact
+
+		if not self.supplier_primary_contact:
+			if self.mobile_no or self.email_id:
+				contact = make_contact(self)
+				self.db_set('supplier_primary_contact', contact.name)
+				self.db_set('mobile_no', self.mobile_no)
+				self.db_set('email_id', self.email_id)
+
+	def create_primary_address(self):
+		from erpnext.selling.doctype.customer.customer import make_address
+		from frappe.contacts.doctype.address.address import get_address_display
+
+		if self.flags.is_new_doc and self.get('address_line1'):
+			address = make_address(self)
+			address_display = get_address_display(address.name)
+
+			self.db_set("supplier_primary_address", address.name)
+			self.db_set("primary_address", address_display)
+
 	def on_trash(self):
+		if self.supplier_primary_contact:
+			frappe.db.sql("""
+				UPDATE `tabSupplier`
+				SET
+					supplier_primary_contact=null,
+					supplier_primary_address=null,
+					mobile_no=null,
+					email_id=null,
+					primary_address=null
+				WHERE name=%(name)s""", {"name": self.name})
+
 		delete_contact_and_address('Supplier', self.name)
 
 	def after_rename(self, olddn, newdn, merge=False):
@@ -104,3 +141,21 @@
 							doc.name, args.get('supplier_email_' + str(i)))
 				except frappe.NameError:
 					pass
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def get_supplier_primary_contact(doctype, txt, searchfield, start, page_len, filters):
+	supplier = filters.get("supplier")
+	return frappe.db.sql("""
+		SELECT
+			`tabContact`.name from `tabContact`,
+			`tabDynamic Link`
+		WHERE
+			`tabContact`.name = `tabDynamic Link`.parent
+			and `tabDynamic Link`.link_name = %(supplier)s
+			and `tabDynamic Link`.link_doctype = 'Supplier'
+			and `tabContact`.name like %(txt)s
+		""", {
+			'supplier': supplier,
+			'txt': '%%%s%%' % txt
+		})
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 9f82af9..fc5dc09 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -14,7 +14,7 @@
 from erpnext.utilities.transaction_base import TransactionBase
 from erpnext.buying.utils import update_last_purchase_rate
 from erpnext.controllers.sales_and_purchase_return import validate_return
-from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled
+from erpnext.accounts.party import get_party_account_currency, validate_party_frozen_disabled, get_party_account
 from erpnext.accounts.doctype.pricing_rule.utils import (apply_pricing_rule_on_transaction,
 	apply_pricing_rule_for_free_items, get_applied_pricing_rules)
 from erpnext.exceptions import InvalidCurrency
@@ -1206,7 +1206,7 @@
 				d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
 				d.outstanding = d.payment_amount
 			elif not d.invoice_portion:
-				d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
+				d.base_payment_amount = flt(d.payment_amount * self.get("conversion_rate"), d.precision('base_payment_amount'))
 
 
 	def get_order_details(self):
@@ -1363,6 +1363,67 @@
 
 		return False
 
+	def process_common_party_accounting(self):
+		is_invoice = self.doctype in ['Sales Invoice', 'Purchase Invoice']
+		if not is_invoice:
+			return
+
+		if frappe.db.get_single_value('Accounts Settings', 'enable_common_party_accounting'):
+			party_link = self.get_common_party_link()
+			if party_link and self.outstanding_amount:
+				self.create_advance_and_reconcile(party_link)
+
+	def get_common_party_link(self):
+		party_type, party = self.get_party()
+		return frappe.db.get_value(
+			doctype='Party Link',
+			filters={'secondary_role': party_type, 'secondary_party': party},
+			fieldname=['primary_role', 'primary_party'],
+			as_dict=True
+		)
+
+	def create_advance_and_reconcile(self, party_link):
+		secondary_party_type, secondary_party = self.get_party()
+		primary_party_type, primary_party = party_link.primary_role, party_link.primary_party
+
+		primary_account = get_party_account(primary_party_type, primary_party, self.company)
+		secondary_account = get_party_account(secondary_party_type, secondary_party, self.company)
+
+		jv = frappe.new_doc('Journal Entry')
+		jv.voucher_type = 'Journal Entry'
+		jv.posting_date = self.posting_date
+		jv.company = self.company
+		jv.remark = 'Adjustment for {} {}'.format(self.doctype, self.name)
+
+		reconcilation_entry = frappe._dict()
+		advance_entry = frappe._dict()
+
+		reconcilation_entry.account = secondary_account
+		reconcilation_entry.party_type = secondary_party_type
+		reconcilation_entry.party = secondary_party
+		reconcilation_entry.reference_type = self.doctype
+		reconcilation_entry.reference_name = self.name
+		reconcilation_entry.cost_center = self.cost_center
+
+		advance_entry.account = primary_account
+		advance_entry.party_type = primary_party_type
+		advance_entry.party = primary_party
+		advance_entry.cost_center = self.cost_center
+		advance_entry.is_advance = 'Yes'
+
+		if self.doctype == 'Sales Invoice':
+			reconcilation_entry.credit_in_account_currency = self.outstanding_amount
+			advance_entry.debit_in_account_currency = self.outstanding_amount
+		else:
+			advance_entry.credit_in_account_currency = self.outstanding_amount
+			reconcilation_entry.debit_in_account_currency = self.outstanding_amount
+
+		jv.append('accounts', reconcilation_entry)
+		jv.append('accounts', advance_entry)
+
+		jv.save()
+		jv.submit()
+
 @frappe.whitelist()
 def get_tax_rate(account_head):
 	return frappe.db.get_value("Account", account_head, ["tax_rate", "account_name"], as_dict=True)
@@ -1526,7 +1587,7 @@
 
 
 def get_advance_payment_entries(party_type, party, party_account, order_doctype,
-		order_list=None, include_unallocated=True, against_all_orders=False, limit=None):
+		order_list=None, include_unallocated=True, against_all_orders=False, limit=None, condition=None):
 	party_account_field = "paid_from" if party_type == "Customer" else "paid_to"
 	currency_field = "paid_from_account_currency" if party_type == "Customer" else "paid_to_account_currency"
 	payment_type = "Receive" if party_type == "Customer" else "Pay"
@@ -1561,14 +1622,14 @@
 
 	if include_unallocated:
 		unallocated_payment_entries = frappe.db.sql("""
-				select "Payment Entry" as reference_type, name as reference_name,
-				remarks, unallocated_amount as amount, {2} as exchange_rate
+				select "Payment Entry" as reference_type, name as reference_name, posting_date,
+				remarks, unallocated_amount as amount, {2} as exchange_rate, {3} as currency
 				from `tabPayment Entry`
 				where
 					{0} = %s and party_type = %s and party = %s and payment_type = %s
-					and docstatus = 1 and unallocated_amount > 0
+					and docstatus = 1 and unallocated_amount > 0 {condition}
 				order by posting_date {1}
-			""".format(party_account_field, limit_cond, exchange_rate_field),
+			""".format(party_account_field, limit_cond, exchange_rate_field, currency_field, condition=condition or ""),
 			(party_account, party_type, party, payment_type), as_dict=1)
 
 	return list(payment_entries_against_order) + list(unallocated_payment_entries)
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
index 1898222..f43c804 100644
--- a/erpnext/controllers/employee_boarding_controller.py
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -5,7 +5,9 @@
 from frappe import _
 from frappe.desk.form import assign_to
 from frappe.model.document import Document
-from frappe.utils import flt, unique
+from frappe.utils import flt, unique, add_days
+from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
+from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 
 class EmployeeBoardingController(Document):
 	'''
@@ -41,10 +43,14 @@
 
 	def create_task_and_notify_user(self):
 		# create the task for the given project and assign to the concerned person
+		holiday_list = self.get_holiday_list()
+
 		for activity in self.activities:
 			if activity.task:
 				continue
 
+			dates = self.get_task_dates(activity, holiday_list)
+
 			task = frappe.get_doc({
 				'doctype': 'Task',
 				'project': self.project,
@@ -52,7 +58,9 @@
 				'description': activity.description,
 				'department': self.department,
 				'company': self.company,
-				'task_weight': activity.task_weight
+				'task_weight': activity.task_weight,
+				'exp_start_date': dates[0],
+				'exp_end_date': dates[1]
 			}).insert(ignore_permissions=True)
 			activity.db_set('task', task.name)
 
@@ -79,6 +87,36 @@
 			if users:
 				self.assign_task_to_users(task, users)
 
+	def get_holiday_list(self):
+		if self.doctype == 'Employee Separation':
+			return get_holiday_list_for_employee(self.employee)
+		else:
+			if self.employee:
+				return get_holiday_list_for_employee(self.employee)
+			else:
+				if not self.holiday_list:
+					frappe.throw(_('Please set the Holiday List.'), frappe.MandatoryError)
+				else:
+					return self.holiday_list
+
+	def get_task_dates(self, activity, holiday_list):
+		start_date = end_date = None
+
+		if activity.begin_on:
+			start_date = add_days(self.boarding_begins_on, activity.begin_on)
+			start_date = self.update_if_holiday(start_date, holiday_list)
+
+			if activity.duration:
+				end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration)
+				end_date = self.update_if_holiday(end_date, holiday_list)
+
+		return [start_date, end_date]
+
+	def update_if_holiday(self, date, holiday_list):
+		while is_holiday(holiday_list, date):
+			date = add_days(date, 1)
+		return date
+
 	def assign_task_to_users(self, task, users):
 		for user in users:
 			args = {
@@ -103,7 +141,8 @@
 @frappe.whitelist()
 def get_onboarding_details(parent, parenttype):
 	return frappe.get_all('Employee Boarding Activity',
-		fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'],
+		fields=['activity_name', 'role', 'user', 'required_for_employee_creation',
+			'description', 'task_weight', 'begin_on', 'duration'],
 		filters={'parent': parent, 'parenttype': parenttype},
 		order_by= 'idx')
 
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 5ee1f2f..01486fc 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -394,19 +394,6 @@
 	if not return_against:
 		return_against = frappe.get_cached_value(voucher_type, voucher_no, "return_against")
 
-	if not return_against and voucher_type == 'Sales Invoice' and sle:
-		return get_incoming_rate({
-			"item_code": sle.item_code,
-			"warehouse": sle.warehouse,
-			"posting_date": sle.get('posting_date'),
-			"posting_time": sle.get('posting_time'),
-			"qty": sle.actual_qty,
-			"serial_no": sle.get('serial_no'),
-			"company": sle.company,
-			"voucher_type": sle.voucher_type,
-			"voucher_no": sle.voucher_no
-		}, raise_error_if_no_rate=False)
-
 	return_against_item_field = get_return_against_item_fields(voucher_type)
 
 	filters = get_filters(voucher_type, voucher_no, voucher_detail_no,
@@ -417,7 +404,24 @@
 	else:
 		select_field = "abs(stock_value_difference / actual_qty)"
 
-	return flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+	rate = flt(frappe.db.get_value("Stock Ledger Entry", filters, select_field))
+	if not (rate and return_against) and voucher_type in ['Sales Invoice', 'Delivery Note']:
+		rate = frappe.db.get_value(f'{voucher_type} Item', voucher_detail_no, 'incoming_rate')
+
+		if not rate and sle:
+			rate = get_incoming_rate({
+				"item_code": sle.item_code,
+				"warehouse": sle.warehouse,
+				"posting_date": sle.get('posting_date'),
+				"posting_time": sle.get('posting_time'),
+				"qty": sle.actual_qty,
+				"serial_no": sle.get('serial_no'),
+				"company": sle.company,
+				"voucher_type": sle.voucher_type,
+				"voucher_no": sle.voucher_no
+			}, raise_error_if_no_rate=False)
+
+	return rate
 
 def get_return_against_item_fields(voucher_type):
 	return_against_item_fields = {
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index fc2cc97..844c40c 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -362,7 +362,7 @@
 				sales_order.update_reserved_qty(so_item_rows)
 
 	def set_incoming_rate(self):
-		if self.doctype not in ("Delivery Note", "Sales Invoice", "Sales Order"):
+		if self.doctype not in ("Delivery Note", "Sales Invoice"):
 			return
 
 		items = self.get("items") + (self.get("packed_items") or [])
@@ -371,18 +371,19 @@
 				# Get incoming rate based on original item cost based on valuation method
 				qty = flt(d.get('stock_qty') or d.get('actual_qty'))
 
-				d.incoming_rate = get_incoming_rate({
-					"item_code": d.item_code,
-					"warehouse": d.warehouse,
-					"posting_date": self.get('posting_date') or self.get('transaction_date'),
-					"posting_time": self.get('posting_time') or nowtime(),
-					"qty": qty if cint(self.get("is_return")) else (-1 * qty),
-					"serial_no": d.get('serial_no'),
-					"company": self.company,
-					"voucher_type": self.doctype,
-					"voucher_no": self.name,
-					"allow_zero_valuation": d.get("allow_zero_valuation")
-				}, raise_error_if_no_rate=False)
+				if not d.incoming_rate:
+					d.incoming_rate = get_incoming_rate({
+						"item_code": d.item_code,
+						"warehouse": d.warehouse,
+						"posting_date": self.get('posting_date') or self.get('transaction_date'),
+						"posting_time": self.get('posting_time') or nowtime(),
+						"qty": qty if cint(self.get("is_return")) else (-1 * qty),
+						"serial_no": d.get('serial_no'),
+						"company": self.company,
+						"voucher_type": self.doctype,
+						"voucher_no": self.name,
+						"allow_zero_valuation": d.get("allow_zero_valuation")
+					}, raise_error_if_no_rate=False)
 
 				# For internal transfers use incoming rate as the valuation rate
 				if self.is_internal_transfer():
@@ -422,7 +423,7 @@
 					or (cint(self.is_return) and self.docstatus==2)):
 						sl_entries.append(self.get_sle_for_source_warehouse(d))
 
-				if d.target_warehouse:
+				if d.target_warehouse and self.get("is_internal_customer"):
 					sl_entries.append(self.get_sle_for_target_warehouse(d))
 
 				if d.warehouse and ((not cint(self.is_return) and self.docstatus==2)
diff --git a/erpnext/crm/doctype/contract/test_contract.js b/erpnext/crm/doctype/contract/test_contract.js
deleted file mode 100644
index 4c77c3d..0000000
--- a/erpnext/crm/doctype/contract/test_contract.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Contract", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Contract
-		() => frappe.tests.make('Contract', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js b/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js
deleted file mode 100644
index 2a2d5e1..0000000
--- a/erpnext/crm/doctype/contract_fulfilment_checklist/test_contract_fulfilment_checklist.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Contract Fulfilment Checklist", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Contract Fulfilment Checklist
-		() => frappe.tests.make('Contract Fulfilment Checklist', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/crm/doctype/contract_template/test_contract_template.js b/erpnext/crm/doctype/contract_template/test_contract_template.js
deleted file mode 100644
index 6aaddd7..0000000
--- a/erpnext/crm/doctype/contract_template/test_contract_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Contract Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Contract Template
-		() => frappe.tests.make('Contract Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 75af937..95cf032 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -39,6 +39,8 @@
 			this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
 			this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create"));
 			this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
+			this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create"));
+			this.frm.add_custom_button(__('Add to Prospect'), this.add_lead_to_prospect, __('Action'));
 		}
 
 		if (!this.frm.is_new()) {
@@ -49,27 +51,74 @@
 		}
 	}
 
-	make_customer () {
+	add_lead_to_prospect (frm) {
+		frappe.prompt([
+			{
+				fieldname: 'prospect',
+				label: __('Prospect'),
+				fieldtype: 'Link',
+				options: 'Prospect',
+				reqd: 1
+			}
+		],
+		function(data) {
+			frappe.call({
+				method: 'erpnext.crm.doctype.lead.lead.add_lead_to_prospect',
+				args: {
+					'lead': frm.doc.name,
+					'prospect': data.prospect
+				},
+				callback: function(r) {
+					if (!r.exc) {
+						frm.reload_doc();
+					}
+				},
+				freeze: true,
+				freeze_message: __('...Adding Lead to Prospect')
+			});
+		}, __('Add Lead to Prospect'), __('Add'));
+	}
+
+	make_customer (frm) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_customer",
-			frm: cur_frm
+			frm: frm
 		})
 	}
 
-	make_opportunity () {
+	make_opportunity (frm) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_opportunity",
-			frm: cur_frm
+			frm: frm
 		})
 	}
 
-	make_quotation () {
+	make_quotation (frm) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_quotation",
-			frm: cur_frm
+			frm: frm
 		})
 	}
 
+	make_prospect (frm) {
+		frappe.model.with_doctype("Prospect", function() {
+			let prospect = frappe.model.get_new_doc("Prospect");
+			prospect.company_name = frm.doc.company_name;
+			prospect.no_of_employees = frm.doc.no_of_employees;
+			prospect.industry = frm.doc.industry;
+			prospect.market_segment = frm.doc.market_segment;
+			prospect.territory = frm.doc.territory;
+			prospect.fax = frm.doc.fax;
+			prospect.website = frm.doc.website;
+			prospect.prospect_owner = frm.doc.lead_owner;
+
+			let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead');
+			lead_prospect_row.lead = frm.doc.name;
+
+			frappe.set_route("Form", "Prospect", prospect.name);
+		});
+	}
+
 	company_name () {
 		if (!this.frm.doc.lead_name) {
 			this.frm.set_value("lead_name", this.frm.doc.company_name);
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index ebec699..09dbdb9 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -63,6 +63,7 @@
 
 	def on_update(self):
 		self.add_calendar_event()
+		self.update_prospects()
 
 	def before_insert(self):
 		self.contact_doc = self.create_contact()
@@ -89,6 +90,12 @@
 			"description": ('Contact ' + cstr(self.lead_name)) + (self.contact_by and ('. By : ' + cstr(self.contact_by)) or '')
 		}, force)
 
+	def update_prospects(self):
+		prospects = frappe.get_all('Prospect Lead', filters={'lead': self.name}, fields=['parent'])
+		for row in prospects:
+			prospect = frappe.get_doc('Prospect', row.parent)
+			prospect.save(ignore_permissions=True)
+
 	def check_email_id_is_unique(self):
 		if self.email_id:
 			# validate email is unique
@@ -355,3 +362,14 @@
 	leads = frappe.get_all("Lead", filters = [["contact_date", "Between", [nowdate(), nowdate()]]])
 	for lead in leads:
 		frappe.db.set_value("Lead", lead.name, "status", "Open")
+
+@frappe.whitelist()
+def add_lead_to_prospect(lead, prospect):
+	prospect = frappe.get_doc('Prospect', prospect)
+	prospect.append('prospect_lead', {
+		'lead': lead
+	})
+	prospect.save(ignore_permissions=True)
+	frappe.msgprint(_('Lead {0} has been added to prospect {1}.').format(frappe.bold(lead), frappe.bold(prospect.name)),
+		title=_('Lead Added'), indicator='green')
+	
\ No newline at end of file
diff --git a/erpnext/crm/doctype/lead/lead_dashboard.py b/erpnext/crm/doctype/lead/lead_dashboard.py
index 3950d06..50e88a5 100644
--- a/erpnext/crm/doctype/lead/lead_dashboard.py
+++ b/erpnext/crm/doctype/lead/lead_dashboard.py
@@ -13,7 +13,7 @@
 		},
 		'transactions': [
 			{
-				'items': ['Opportunity', 'Quotation']
+				'items': ['Opportunity', 'Quotation', 'Prospect']
 			},
 		]
 	}
diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js
new file mode 100644
index 0000000..75208fa
--- /dev/null
+++ b/erpnext/crm/doctype/lead/lead_list.js
@@ -0,0 +1,28 @@
+frappe.listview_settings['Lead'] = {
+	onload: function(listview) {
+		if (frappe.boot.user.can_create.includes("Prospect")) {
+			listview.page.add_action_item(__("Create Prospect"), function() {
+				frappe.model.with_doctype("Prospect", function() {
+					let prospect = frappe.model.get_new_doc("Prospect");
+					let leads = listview.get_checked_items();
+					frappe.db.get_value("Lead", leads[0].name, ["company_name", "no_of_employees", "industry", "market_segment", "territory", "fax", "website", "lead_owner"], (r) => {
+						prospect.company_name = r.company_name;
+						prospect.no_of_employees = r.no_of_employees;
+						prospect.industry = r.industry;
+						prospect.market_segment = r.market_segment;
+						prospect.territory = r.territory;
+						prospect.fax = r.fax;
+						prospect.website = r.website;
+						prospect.prospect_owner = r.lead_owner;
+
+						leads.forEach(function(lead) {
+							let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead');
+							lead_prospect_row.lead = lead.name;
+						});
+						frappe.set_route("Form", "Prospect", prospect.name);
+					});
+				});
+			});
+		}
+	}
+};
diff --git a/erpnext/crm/doctype/market_segment/test_market_segment.js b/erpnext/crm/doctype/market_segment/test_market_segment.js
deleted file mode 100644
index aa4b868..0000000
--- a/erpnext/crm/doctype/market_segment/test_market_segment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Market Segment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Market Segment
-		() => frappe.tests.make('Market Segment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index cb95881..3866fc2 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -10,12 +10,12 @@
 		frm.custom_make_buttons = {
 			'Quotation': 'Quotation',
 			'Supplier Quotation': 'Supplier Quotation'
-		},
+		};
 
 		frm.set_query("opportunity_from", function() {
 			return{
 				"filters": {
-					"name": ["in", ["Customer", "Lead"]],
+					"name": ["in", ["Customer", "Lead", "Prospect"]],
 				}
 			}
 		});
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 4ba4140..12a564a9 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -430,7 +430,7 @@
  "icon": "fa fa-info-sign",
  "idx": 195,
  "links": [],
- "modified": "2021-06-04 10:11:22.831139",
+ "modified": "2021-08-25 10:28:24.923543",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Opportunity",
diff --git a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js b/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js
deleted file mode 100644
index 3a1ede9..0000000
--- a/erpnext/crm/doctype/opportunity_type/test_opportunity_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Opportunity Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Opportunity Type
-		() => frappe.tests.make('Opportunity Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/crm/doctype/prospect/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/crm/doctype/prospect/__init__.py
diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js
new file mode 100644
index 0000000..67018e1
--- /dev/null
+++ b/erpnext/crm/doctype/prospect/prospect.js
@@ -0,0 +1,29 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Prospect', {
+	refresh (frm) {
+		if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
+			frm.add_custom_button(__("Customer"), function() {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.crm.doctype.prospect.prospect.make_customer",
+					frm: frm
+				});
+			}, __("Create"));
+		}
+		if (!frm.is_new() && frappe.boot.user.can_create.includes("Opportunity")) {
+			frm.add_custom_button(__("Opportunity"), function() {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.crm.doctype.prospect.prospect.make_opportunity",
+					frm: frm
+				});
+			}, __("Create"));
+		}
+
+		if (!frm.is_new()) {
+			frappe.contacts.render_address_and_contact(frm);
+		} else {
+			frappe.contacts.clear_address_and_contact(frm);
+		}
+	}
+});
diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json
new file mode 100644
index 0000000..3d6fba5
--- /dev/null
+++ b/erpnext/crm/doctype/prospect/prospect.json
@@ -0,0 +1,203 @@
+{
+ "actions": [],
+ "autoname": "field:company_name",
+ "creation": "2021-08-19 00:21:06.995448",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "company_name",
+  "industry",
+  "market_segment",
+  "customer_group",
+  "territory",
+  "column_break_6",
+  "no_of_employees",
+  "currency",
+  "annual_revenue",
+  "more_details_section",
+  "fax",
+  "website",
+  "column_break_13",
+  "prospect_owner",
+  "leads_section",
+  "prospect_lead",
+  "address_and_contact_section",
+  "address_html",
+  "column_break_17",
+  "contact_html",
+  "notes_section",
+  "notes"
+ ],
+ "fields": [
+  {
+   "fieldname": "company_name",
+   "fieldtype": "Data",
+   "label": "Company Name",
+   "unique": 1
+  },
+  {
+   "fieldname": "industry",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Industry",
+   "options": "Industry Type"
+  },
+  {
+   "fieldname": "market_segment",
+   "fieldtype": "Link",
+   "label": "Market Segment",
+   "options": "Market Segment"
+  },
+  {
+   "fieldname": "customer_group",
+   "fieldtype": "Link",
+   "label": "Customer Group",
+   "options": "Customer Group"
+  },
+  {
+   "fieldname": "territory",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Territory",
+   "options": "Territory"
+  },
+  {
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "no_of_employees",
+   "fieldtype": "Int",
+   "label": "No. of Employees"
+  },
+  {
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Currency",
+   "options": "Currency"
+  },
+  {
+   "fieldname": "annual_revenue",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Annual Revenue",
+   "options": "currency"
+  },
+  {
+   "fieldname": "fax",
+   "fieldtype": "Data",
+   "label": "Fax",
+   "options": "Phone"
+  },
+  {
+   "fieldname": "website",
+   "fieldtype": "Data",
+   "label": "Website",
+   "options": "URL"
+  },
+  {
+   "fieldname": "prospect_owner",
+   "fieldtype": "Link",
+   "label": "Prospect Owner",
+   "options": "User"
+  },
+  {
+   "fieldname": "leads_section",
+   "fieldtype": "Section Break",
+   "label": "Leads"
+  },
+  {
+   "fieldname": "prospect_lead",
+   "fieldtype": "Table",
+   "options": "Prospect Lead"
+  },
+  {
+   "fieldname": "address_html",
+   "fieldtype": "HTML",
+   "label": "Address HTML"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "contact_html",
+   "fieldtype": "HTML",
+   "label": "Contact HTML"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "notes_section",
+   "fieldtype": "Section Break",
+   "label": "Notes"
+  },
+  {
+   "fieldname": "notes",
+   "fieldtype": "Text Editor"
+  },
+  {
+   "fieldname": "more_details_section",
+   "fieldtype": "Section Break",
+   "label": "More Details"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval: !doc.__islocal",
+   "fieldname": "address_and_contact_section",
+   "fieldtype": "Section Break",
+   "label": "Address and Contact"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-27 16:24:42.961967",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Prospect",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales User",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "company_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py
new file mode 100644
index 0000000..5f5815d
--- /dev/null
+++ b/erpnext/crm/doctype/prospect/prospect.py
@@ -0,0 +1,99 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+from frappe.model.mapper import get_mapped_doc
+from frappe.contacts.address_and_contact import load_address_and_contact
+
+class Prospect(Document):
+	def onload(self):
+		load_address_and_contact(self)
+
+	def validate(self):
+		self.update_lead_details()
+
+	def on_update(self):
+		self.link_with_lead_contact_and_address()
+	
+	def on_trash(self):
+		self.unlink_dynamic_links()
+
+	def update_lead_details(self):
+		for row in self.get('prospect_lead'):
+			lead = frappe.get_value('Lead', row.lead, ['lead_name', 'status', 'email_id', 'mobile_no'], as_dict=True)
+			row.lead_name = lead.lead_name
+			row.status = lead.status
+			row.email = lead.email_id
+			row.mobile_no = lead.mobile_no
+
+	def link_with_lead_contact_and_address(self):
+		for row in self.prospect_lead:
+			links = frappe.get_all('Dynamic Link', filters={'link_doctype': 'Lead', 'link_name': row.lead}, fields=['parent', 'parenttype'])
+			for link in links:
+				linked_doc = frappe.get_doc(link['parenttype'], link['parent'])
+				exists = False
+
+				for d in linked_doc.get('links'):
+					if d.link_doctype == self.doctype and d.link_name == self.name:
+						exists = True
+
+				if not exists:
+					linked_doc.append('links', {
+						'link_doctype': self.doctype,
+						'link_name': self.name
+					})
+					linked_doc.save(ignore_permissions=True)
+
+	def unlink_dynamic_links(self):
+		links = frappe.get_all('Dynamic Link', filters={'link_doctype': self.doctype, 'link_name': self.name}, fields=['parent', 'parenttype'])
+
+		for link in links:
+			linked_doc = frappe.get_doc(link['parenttype'], link['parent'])
+
+			if len(linked_doc.get('links')) == 1:
+				linked_doc.delete(ignore_permissions=True)
+			else:
+				to_remove = None
+				for d in linked_doc.get('links'):
+					if d.link_doctype == self.doctype and d.link_name == self.name:
+						to_remove = d
+				if to_remove:
+					linked_doc.remove(to_remove)
+					linked_doc.save(ignore_permissions=True)
+
+@frappe.whitelist()
+def make_customer(source_name, target_doc=None):
+	def set_missing_values(source, target):
+		target.customer_type = "Company"
+		target.company_name = source.name
+		target.customer_group = source.customer_group or frappe.db.get_default("Customer Group")
+
+	doclist = get_mapped_doc("Prospect", source_name,
+		{"Prospect": {
+			"doctype": "Customer",
+			"field_map": {
+				"company_name": "customer_name",
+				"currency": "default_currency",
+				"fax": "fax"
+			}
+		}}, target_doc, set_missing_values, ignore_permissions=False)
+
+	return doclist
+
+@frappe.whitelist()
+def make_opportunity(source_name, target_doc=None):
+	def set_missing_values(source, target):
+		target.opportunity_from = "Prospect"
+		target.customer_name = source.company_name
+		target.customer_group = source.customer_group or frappe.db.get_default("Customer Group")
+
+	doclist = get_mapped_doc("Prospect", source_name,
+		{"Prospect": {
+			"doctype": "Opportunity",
+			"field_map": {
+				"name": "party_name",
+			}
+		}}, target_doc, set_missing_values, ignore_permissions=False)
+
+	return doclist
diff --git a/erpnext/crm/doctype/prospect/test_prospect.py b/erpnext/crm/doctype/prospect/test_prospect.py
new file mode 100644
index 0000000..0fffad1
--- /dev/null
+++ b/erpnext/crm/doctype/prospect/test_prospect.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+import unittest
+from frappe.utils import random_string
+from erpnext.crm.doctype.lead.test_lead import make_lead
+from erpnext.crm.doctype.lead.lead import add_lead_to_prospect
+
+
+class TestProspect(unittest.TestCase):
+	def test_add_lead_to_prospect_and_address_linking(self):
+		lead_doc = make_lead()
+		address_doc = make_address(address_title=lead_doc.name)
+		address_doc.append('links', {
+				"link_doctype": lead_doc.doctype,
+				"link_name": lead_doc.name
+			})
+		address_doc.save()
+		prospect_doc = make_prospect()
+		add_lead_to_prospect(lead_doc.name, prospect_doc.name)
+		prospect_doc.reload()
+		lead_exists_in_prosoect = False
+		for rec in prospect_doc.get('prospect_lead'):
+			if rec.lead == lead_doc.name:
+				lead_exists_in_prosoect = True
+		self.assertEqual(lead_exists_in_prosoect, True)
+		address_doc.reload()
+		self.assertEqual(address_doc.has_link('Prospect', prospect_doc.name), True)
+
+
+def make_prospect(**args):
+	args = frappe._dict(args)
+
+	prospect_doc = frappe.get_doc({
+		"doctype": "Prospect",
+		"company_name": args.company_name or "_Test Company {}".format(random_string(3)),
+	}).insert()
+
+	return prospect_doc
+
+def make_address(**args):
+	args = frappe._dict(args)
+
+	address_doc = frappe.get_doc({
+		"doctype": "Address",
+		"address_title": args.address_title or "Address Title",
+		"address_type": args.address_type or "Billing",
+		"city": args.city or "Mumbai",
+		"address_line1": args.address_line1 or "Vidya Vihar West",
+		"country": args.country or "India"
+	}).insert()
+
+	return address_doc
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/crm/doctype/prospect_lead/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/crm/doctype/prospect_lead/__init__.py
diff --git a/erpnext/crm/doctype/prospect_lead/prospect_lead.json b/erpnext/crm/doctype/prospect_lead/prospect_lead.json
new file mode 100644
index 0000000..3c160d9
--- /dev/null
+++ b/erpnext/crm/doctype/prospect_lead/prospect_lead.json
@@ -0,0 +1,67 @@
+{
+ "actions": [],
+ "creation": "2021-08-19 00:14:14.857421",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "lead",
+  "lead_name",
+  "status",
+  "email",
+  "mobile_no"
+ ],
+ "fields": [
+  {
+   "fieldname": "lead",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Lead",
+   "options": "Lead",
+   "reqd": 1
+  },
+  {
+   "fieldname": "lead_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Lead Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact",
+   "read_only": 1
+  },
+  {
+   "fieldname": "email",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Email",
+   "options": "Email",
+   "read_only": 1
+  },
+  {
+   "fieldname": "mobile_no",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Mobile No",
+   "options": "Phone",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-25 12:58:24.638054",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Prospect Lead",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/prospect_lead/prospect_lead.py b/erpnext/crm/doctype/prospect_lead/prospect_lead.py
new file mode 100644
index 0000000..2be5a5f
--- /dev/null
+++ b/erpnext/crm/doctype/prospect_lead/prospect_lead.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class ProspectLead(Document):
+	pass
diff --git a/erpnext/crm/doctype/sales_stage/test_sales_stage.js b/erpnext/crm/doctype/sales_stage/test_sales_stage.js
deleted file mode 100644
index 807af1f..0000000
--- a/erpnext/crm/doctype/sales_stage/test_sales_stage.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sales Stage", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Sales Stage
-		() => frappe.tests.make('Sales Stage', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/academic_year/test_academic_year.js b/erpnext/education/doctype/academic_year/test_academic_year.js
deleted file mode 100644
index 51e9cf3..0000000
--- a/erpnext/education/doctype/academic_year/test_academic_year.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// Testing Setup Module in Education
-QUnit.module('education');
-
-QUnit.test('Test: Academic Year', function(assert){
-	assert.expect(3);
-	let done = assert.async();
-	frappe.run_serially([
-		() => {
-			return frappe.tests.make('Academic Year', [
-				{academic_year_name: '2016-17'},
-				{year_start_date: '2016-07-20'},
-				{year_end_date:'2017-06-20'},
-			]);
-		},
-
-		() => {
-			assert.ok(cur_frm.doc.academic_year_name=='2016-17');
-			assert.ok(cur_frm.doc.year_start_date=='2016-07-20');
-			assert.ok(cur_frm.doc.year_end_date=='2017-06-20');
-		},
-		() => done()
-	]);
-});
diff --git a/erpnext/education/doctype/article/test_article.js b/erpnext/education/doctype/article/test_article.js
deleted file mode 100644
index 9dbf063..0000000
--- a/erpnext/education/doctype/article/test_article.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Article", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Article
-		() => frappe.tests.make('Article', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/content_question/test_content_question.js b/erpnext/education/doctype/content_question/test_content_question.js
deleted file mode 100644
index cc869a8..0000000
--- a/erpnext/education/doctype/content_question/test_content_question.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Content Question", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Content Question
-		() => frappe.tests.make('Content Question', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/course_activity/test_course_activity.js b/erpnext/education/doctype/course_activity/test_course_activity.js
deleted file mode 100644
index c89c89e..0000000
--- a/erpnext/education/doctype/course_activity/test_course_activity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Activity", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Course Activity
-		() => frappe.tests.make('Course Activity', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/course_content/test_course_content.js b/erpnext/education/doctype/course_content/test_course_content.js
deleted file mode 100644
index 786e67e..0000000
--- a/erpnext/education/doctype/course_content/test_course_content.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Content", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Course Content
-		() => frappe.tests.make('Course Content', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/course_enrollment/test_course_enrollment.js b/erpnext/education/doctype/course_enrollment/test_course_enrollment.js
deleted file mode 100644
index 216cc30..0000000
--- a/erpnext/education/doctype/course_enrollment/test_course_enrollment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Enrollment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Course Enrollment
-		() => frappe.tests.make('Course Enrollment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/course_schedule/test_course_schedule.js b/erpnext/education/doctype/course_schedule/test_course_schedule.js
deleted file mode 100644
index 5cdb67b..0000000
--- a/erpnext/education/doctype/course_schedule/test_course_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Schedule", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Course Schedule
-		() => frappe.tests.make('Course Schedule', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js b/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js
deleted file mode 100644
index 4419d18..0000000
--- a/erpnext/education/doctype/course_scheduling_tool/test_course_scheduling_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Scheduling Tool", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Course Scheduling Tool
-		() => frappe.tests.make('Course Scheduling Tool', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/course_topic/test_course_topic.js b/erpnext/education/doctype/course_topic/test_course_topic.js
deleted file mode 100644
index d8d154f..0000000
--- a/erpnext/education/doctype/course_topic/test_course_topic.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Course Topic", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Course Topic
-		() => frappe.tests.make('Course Topic', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/fee_category/test_fee_category.js b/erpnext/education/doctype/fee_category/test_fee_category.js
deleted file mode 100644
index a08ed33..0000000
--- a/erpnext/education/doctype/fee_category/test_fee_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Fee Category
-		() => frappe.tests.make('Fee Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/fee_schedule/test_fee_schedule.js b/erpnext/education/doctype/fee_schedule/test_fee_schedule.js
deleted file mode 100644
index d495b4c..0000000
--- a/erpnext/education/doctype/fee_schedule/test_fee_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Schedule", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Fee Schedule', [
-		// insert a new Fee Schedule
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/fee_structure/test_fee_structure.js b/erpnext/education/doctype/fee_structure/test_fee_structure.js
deleted file mode 100644
index 61f4135..0000000
--- a/erpnext/education/doctype/fee_structure/test_fee_structure.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Structure", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Fee Structure
-		() => frappe.tests.make('Fee Structure', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/program_enrollment/test_program_enrollment.js b/erpnext/education/doctype/program_enrollment/test_program_enrollment.js
deleted file mode 100644
index aea81a0..0000000
--- a/erpnext/education/doctype/program_enrollment/test_program_enrollment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Program Enrollment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Program Enrollment
-		() => frappe.tests.make('Program Enrollment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js b/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js
deleted file mode 100644
index 8d55104..0000000
--- a/erpnext/education/doctype/program_enrollment_tool/test_program_enrollment_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Program Enrollment Tool", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Program Enrollment Tool
-		() => frappe.tests.make('Program Enrollment Tool', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/question/test_question.js b/erpnext/education/doctype/question/test_question.js
deleted file mode 100644
index 509939c..0000000
--- a/erpnext/education/doctype/question/test_question.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Question", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Question
-		() => frappe.tests.make('Question', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/quiz/test_quiz.js b/erpnext/education/doctype/quiz/test_quiz.js
deleted file mode 100644
index 147d139..0000000
--- a/erpnext/education/doctype/quiz/test_quiz.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quiz", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Quiz
-		() => frappe.tests.make('Quiz', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/quiz_activity/test_quiz_activity.js b/erpnext/education/doctype/quiz_activity/test_quiz_activity.js
deleted file mode 100644
index 94b5ab7..0000000
--- a/erpnext/education/doctype/quiz_activity/test_quiz_activity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quiz Activity", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Quiz Activity
-		() => frappe.tests.make('Quiz Activity', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/quiz_result/test_quiz_result.js b/erpnext/education/doctype/quiz_result/test_quiz_result.js
deleted file mode 100644
index 43f53a1..0000000
--- a/erpnext/education/doctype/quiz_result/test_quiz_result.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quiz Result", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Quiz Result
-		() => frappe.tests.make('Quiz Result', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/school_house/test_school_house.js b/erpnext/education/doctype/school_house/test_school_house.js
deleted file mode 100644
index dde63ec..0000000
--- a/erpnext/education/doctype/school_house/test_school_house.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: School House", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new School House
-		() => frappe.tests.make('School House', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/student/test_student.js b/erpnext/education/doctype/student/test_student.js
deleted file mode 100644
index e18d39a..0000000
--- a/erpnext/education/doctype/student/test_student.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Student", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Student
-		() => frappe.tests.make('Student', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/student_language/test_student_language.js b/erpnext/education/doctype/student_language/test_student_language.js
deleted file mode 100644
index 9b25569..0000000
--- a/erpnext/education/doctype/student_language/test_student_language.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Student Language", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Student Language
-		() => frappe.tests.make('Student Language', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js b/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js
deleted file mode 100644
index 10be092..0000000
--- a/erpnext/education/doctype/student_report_generation_tool/test_student_report_generation_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Student Report Generation Tool", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Student Report Generation Tool
-		() => frappe.tests.make('Student Report Generation Tool', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/topic/test_topic.js b/erpnext/education/doctype/topic/test_topic.js
deleted file mode 100644
index 4460b79..0000000
--- a/erpnext/education/doctype/topic/test_topic.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Topic", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Topic
-		() => frappe.tests.make('Topic', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/education/doctype/topic_content/test_topic_content.js b/erpnext/education/doctype/topic_content/test_topic_content.js
deleted file mode 100644
index bf9a62d..0000000
--- a/erpnext/education/doctype/topic_content/test_topic_content.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Topic Content", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Topic Content
-		() => frappe.tests.make('Topic Content', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js
deleted file mode 100644
index 9c89909..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Amazon MWS Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Amazon MWS Settings
-		() => frappe.tests.make('Amazon MWS Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js b/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js
deleted file mode 100644
index caa9399..0000000
--- a/erpnext/erpnext_integrations/doctype/gocardless_mandate/test_gocardless_mandate.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GoCardless Mandate", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new GoCardless Mandate
-		() => frappe.tests.make('GoCardless Mandate', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js b/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js
deleted file mode 100644
index b6daad8..0000000
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/test_gocardless_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GoCardless Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new GoCardless Settings
-		() => frappe.tests.make('GoCardless Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js
deleted file mode 100644
index dc91347..0000000
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Plaid Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Plaid Settings
-		() => frappe.tests.make('Plaid Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js
deleted file mode 100644
index b71d704..0000000
--- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/test_quickbooks_migrator.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: QuickBooks Migrator", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new QuickBooks Migrator
-		() => frappe.tests.make('QuickBooks Migrator', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js b/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js
deleted file mode 100644
index 433c5e2..0000000
--- a/erpnext/erpnext_integrations/doctype/tally_migration/test_tally_migration.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Tally Migration", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Tally Migration
-		() => frappe.tests.make('Tally Migration', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js b/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js
deleted file mode 100644
index ea06ab2..0000000
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/test_woocommerce_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Woocommerce Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Woocommerce Settings
-		() => frappe.tests.make('Woocommerce Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/stripe_integration.py b/erpnext/erpnext_integrations/stripe_integration.py
index 108b4c0..820c740 100644
--- a/erpnext/erpnext_integrations/stripe_integration.py
+++ b/erpnext/erpnext_integrations/stripe_integration.py
@@ -2,11 +2,12 @@
 # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-from __future__ import unicode_literals
+import stripe
+
 import frappe
 from frappe import _
 from frappe.integrations.utils import create_request_log
-import stripe
+
 
 def create_stripe_subscription(gateway_controller, data):
 	stripe_settings = frappe.get_doc("Stripe Settings", gateway_controller)
@@ -23,31 +24,38 @@
 	except Exception:
 		frappe.log_error(frappe.get_traceback())
 		return{
-			"redirect_to": frappe.redirect_to_message(_('Server Error'), _("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")),
+			"redirect_to": frappe.redirect_to_message(
+				_('Server Error'),
+				_("It seems that there is an issue with the server's stripe configuration. In case of failure, the amount will get refunded to your account.")
+			),
 			"status": 401
 		}
 
 
 def create_subscription_on_stripe(stripe_settings):
-		items = []
-		for payment_plan in stripe_settings.payment_plans:
-			plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "payment_plan_id")
-			items.append({"plan": plan, "quantity": payment_plan.qty})
+	items = []
+	for payment_plan in stripe_settings.payment_plans:
+		plan = frappe.db.get_value("Subscription Plan", payment_plan.plan, "product_price_id")
+		items.append({"price": plan, "quantity": payment_plan.qty})
 
-		try:
-			customer = stripe.Customer.create(description=stripe_settings.data.payer_name, email=stripe_settings.data.payer_email, source=stripe_settings.data.stripe_token_id)
-			subscription = stripe.Subscription.create(customer=customer, items=items)
+	try:
+		customer = stripe.Customer.create(
+			source=stripe_settings.data.stripe_token_id,
+			description=stripe_settings.data.payer_name,
+			email=stripe_settings.data.payer_email
+		)
 
-			if subscription.status == "active":
-				stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False)
-				stripe_settings.flags.status_changed_to = "Completed"
+		subscription = stripe.Subscription.create(customer=customer, items=items)
 
-			else:
-				stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False)
-				frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed')
+		if subscription.status == "active":
+			stripe_settings.integration_request.db_set('status', 'Completed', update_modified=False)
+			stripe_settings.flags.status_changed_to = "Completed"
 
-		except Exception:
+		else:
 			stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False)
-			frappe.log_error(frappe.get_traceback())
+			frappe.log_error('Subscription N°: ' + subscription.id, 'Stripe Payment not completed')
+	except Exception:
+		stripe_settings.integration_request.db_set('status', 'Failed', update_modified=False)
+		frappe.log_error(frappe.get_traceback())
 
-		return stripe_settings.finalize_request()
+	return stripe_settings.finalize_request()
diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py
index f960998..83764ae 100644
--- a/erpnext/erpnext_integrations/taxjar_integration.py
+++ b/erpnext/erpnext_integrations/taxjar_integration.py
@@ -1,11 +1,10 @@
 import traceback
-
-import taxjar
-
 import frappe
+import taxjar
 from erpnext import get_default_company
 from frappe import _
 from frappe.contacts.doctype.address.address import get_company_address
+from frappe.utils import cint
 
 TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
 SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
@@ -14,6 +13,10 @@
 SUPPORTED_COUNTRY_CODES = ["AT", "AU", "BE", "BG", "CA", "CY", "CZ", "DE", "DK", "EE", "ES", "FI",
 	"FR", "GB", "GR", "HR", "HU", "IE", "IT", "LT", "LU", "LV", "MT", "NL", "PL", "PT", "RO",
 	"SE", "SI", "SK", "US"]
+SUPPORTED_STATE_CODES = ['AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'DC', 'FL', 'GA', 'HI', 'ID', 'IL',
+	'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD', 'MA', 'MI', 'MN', 'MS', 'MO', 'MT', 'NE',
+	'NV', 'NH', 'NJ', 'NM', 'NY', 'NC', 'ND', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 
+	'TN', 'TX', 'UT', 'VT', 'VA', 'WA', 'WV', 'WI', 'WY']
 
 
 def get_client():
@@ -27,7 +30,11 @@
 		api_url = taxjar.SANDBOX_API_URL
 
 	if api_key and api_url:
-		return taxjar.Client(api_key=api_key, api_url=api_url)
+		client = taxjar.Client(api_key=api_key, api_url=api_url)
+		client.set_api_config('headers', {
+				'x-api-version': '2020-08-07'
+			})
+		return client
 
 
 def create_transaction(doc, method):
@@ -57,7 +64,10 @@
 	tax_dict['amount'] = doc.total + tax_dict['shipping']
 
 	try:
-		client.create_order(tax_dict)
+		if doc.is_return:
+			client.create_refund(tax_dict)
+		else:	
+			client.create_order(tax_dict)
 	except taxjar.exceptions.TaxJarResponseError as err:
 		frappe.throw(_(sanitize_error_response(err)))
 	except Exception as ex:
@@ -89,14 +99,16 @@
 	to_country_code = frappe.db.get_value("Country", to_address.country, "code")
 	to_country_code = to_country_code.upper()
 
-	if to_country_code not in SUPPORTED_COUNTRY_CODES:
-		return
-
 	shipping = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == SHIP_ACCOUNT_HEAD])
 
-	if to_shipping_state is not None:
-		to_shipping_state = get_iso_3166_2_state_code(to_address)
+	line_items = [get_line_item_dict(item) for item in doc.items]
 
+	if from_shipping_state not in SUPPORTED_STATE_CODES:
+		from_shipping_state = get_state_code(from_address, 'Company')
+
+	if to_shipping_state not in SUPPORTED_STATE_CODES:
+		to_shipping_state = get_state_code(to_address, 'Shipping')
+	
 	tax_dict = {
 		'from_country': from_country_code,
 		'from_zip': from_address.pincode,
@@ -109,11 +121,29 @@
 		'to_street': to_address.address_line1,
 		'to_state': to_shipping_state,
 		'shipping': shipping,
-		'amount': doc.net_total
+		'amount': doc.net_total,
+		'plugin': 'erpnext',
+		'line_items': line_items
 	}
+	return tax_dict	
 
-	return tax_dict
+def get_state_code(address, location):
+	if address is not None:
+		state_code = get_iso_3166_2_state_code(address)
+		if state_code not in SUPPORTED_STATE_CODES:
+			frappe.throw(_("Please enter a valid State in the {0} Address").format(location))
+	else:
+		frappe.throw(_("Please enter a valid State in the {0} Address").format(location))
+	
+	return state_code
 
+def get_line_item_dict(item):
+	return dict( 
+		id = item.get('idx'),
+		quantity = item.get('qty'),
+		unit_price = item.get('rate'),
+		product_tax_code = item.get('product_tax_category')
+	)  	
 
 def set_sales_tax(doc, method):
 	if not TAXJAR_CALCULATE_TAX:
@@ -122,17 +152,7 @@
 	if not doc.items:
 		return
 
-	# if the party is exempt from sales tax, then set all tax account heads to zero
-	sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
-		or frappe.db.has_column("Customer", "exempt_from_sales_tax") and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
-
-	if sales_tax_exempted:
-		for tax in doc.taxes:
-			if tax.account_head == TAX_ACCOUNT_HEAD:
-				tax.tax_amount = 0
-				break
-
-		doc.run_method("calculate_taxes_and_totals")
+	if check_sales_tax_exemption(doc):
 		return
 
 	tax_dict = get_tax_data(doc)
@@ -143,7 +163,6 @@
 		return
 
 	tax_data = validate_tax_request(tax_dict)
-
 	if tax_data is not None:
 		if not tax_data.amount_to_collect:
 			setattr(doc, "taxes", [tax for tax in doc.taxes if tax.account_head != TAX_ACCOUNT_HEAD])
@@ -163,9 +182,28 @@
 					"account_head": TAX_ACCOUNT_HEAD,
 					"tax_amount": tax_data.amount_to_collect
 				})
+			# Assigning values to tax_collectable and taxable_amount fields in sales item table
+			for item in tax_data.breakdown.line_items:
+				doc.get('items')[cint(item.id)-1].tax_collectable = item.tax_collectable
+				doc.get('items')[cint(item.id)-1].taxable_amount = item.taxable_amount
 
 			doc.run_method("calculate_taxes_and_totals")
 
+def check_sales_tax_exemption(doc):
+	# if the party is exempt from sales tax, then set all tax account heads to zero
+	sales_tax_exempted = hasattr(doc, "exempt_from_sales_tax") and doc.exempt_from_sales_tax \
+		or frappe.db.has_column("Customer", "exempt_from_sales_tax") \
+		and frappe.db.get_value("Customer", doc.customer, "exempt_from_sales_tax")
+
+	if sales_tax_exempted:
+		for tax in doc.taxes:
+			if tax.account_head == TAX_ACCOUNT_HEAD:
+				tax.tax_amount = 0
+				break
+		doc.run_method("calculate_taxes_and_totals")
+		return True
+	else: 
+		return False
 
 def validate_tax_request(tax_dict):
 	"""Return the sales tax that should be collected for a given order."""
@@ -200,6 +238,8 @@
 
 	if doc.shipping_address_name:
 		shipping_address = frappe.get_doc("Address", doc.shipping_address_name)
+	elif doc.customer_address:
+		shipping_address = frappe.get_doc("Address", doc.customer_address_name)
 	else:
 		shipping_address = get_company_address_details(doc)
 
diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json
index a59f149..6803528 100644
--- a/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json
+++ b/erpnext/healthcare/dashboard_chart/clinical_procedures/clinical_procedures.json
@@ -12,15 +12,15 @@
  "idx": 0,
  "is_public": 1,
  "is_standard": 1,
- "last_synced_on": "2020-07-22 13:22:47.008622",
- "modified": "2020-07-22 13:36:48.114479",
+ "last_synced_on": "2021-01-30 21:03:30.086891",
+ "modified": "2021-02-01 13:36:04.469863",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Clinical Procedures",
  "number_of_groups": 0,
  "owner": "Administrator",
  "timeseries": 0,
- "type": "Percentage",
+ "type": "Bar",
  "use_report_chart": 0,
  "y_axis": []
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json
index 6d560f7..dae9db1 100644
--- a/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json
+++ b/erpnext/healthcare/dashboard_chart/clinical_procedures_status/clinical_procedures_status.json
@@ -12,15 +12,15 @@
  "idx": 0,
  "is_public": 1,
  "is_standard": 1,
- "last_synced_on": "2020-07-22 13:22:46.691764",
- "modified": "2020-07-22 13:40:17.215775",
+ "last_synced_on": "2021-02-01 13:36:38.787783",
+ "modified": "2021-02-01 13:37:18.718275",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Clinical Procedures Status",
  "number_of_groups": 0,
  "owner": "Administrator",
  "timeseries": 0,
- "type": "Pie",
+ "type": "Bar",
  "use_report_chart": 0,
  "y_axis": []
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json
index 0195aac..82145d6 100644
--- a/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json
+++ b/erpnext/healthcare/dashboard_chart/diagnoses/diagnoses.json
@@ -5,21 +5,22 @@
  "docstatus": 0,
  "doctype": "Dashboard Chart",
  "document_type": "Patient Encounter Diagnosis",
+ "dynamic_filters_json": "",
  "filters_json": "[]",
  "group_by_based_on": "diagnosis",
  "group_by_type": "Count",
  "idx": 0,
  "is_public": 1,
  "is_standard": 1,
- "last_synced_on": "2020-07-22 13:22:47.895521",
- "modified": "2020-07-22 13:43:32.369481",
+ "last_synced_on": "2021-01-30 21:03:33.729487",
+ "modified": "2021-02-01 13:34:57.385335",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Diagnoses",
  "number_of_groups": 0,
  "owner": "Administrator",
  "timeseries": 0,
- "type": "Percentage",
+ "type": "Bar",
  "use_report_chart": 0,
  "y_axis": []
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json
index 0524835..70293b1 100644
--- a/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json
+++ b/erpnext/healthcare/dashboard_chart/lab_tests/lab_tests.json
@@ -12,15 +12,15 @@
  "idx": 0,
  "is_public": 1,
  "is_standard": 1,
- "last_synced_on": "2020-07-22 13:22:47.344055",
- "modified": "2020-07-22 13:37:34.490129",
+ "last_synced_on": "2021-01-30 21:03:28.272914",
+ "modified": "2021-02-01 13:36:08.391433",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Lab Tests",
  "number_of_groups": 0,
  "owner": "Administrator",
  "timeseries": 0,
- "type": "Percentage",
+ "type": "Bar",
  "use_report_chart": 0,
  "y_axis": []
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json
index 8fc86a1..65e5472 100644
--- a/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json
+++ b/erpnext/healthcare/dashboard_chart/symptoms/symptoms.json
@@ -12,15 +12,15 @@
  "idx": 0,
  "is_public": 1,
  "is_standard": 1,
- "last_synced_on": "2020-07-22 13:22:47.296748",
- "modified": "2020-07-22 13:40:59.655129",
+ "last_synced_on": "2021-01-30 21:03:32.067473",
+ "modified": "2021-02-01 13:35:30.953718",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Symptoms",
  "number_of_groups": 0,
  "owner": "Administrator",
  "timeseries": 0,
- "type": "Percentage",
+ "type": "Bar",
  "use_report_chart": 0,
  "y_axis": []
 }
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js b/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js
deleted file mode 100644
index b92103d..0000000
--- a/erpnext/healthcare/doctype/antibiotic/test_antibiotic.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Antibiotic", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Antibiotic
-		() => frappe.tests.make('Antibiotic', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js b/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js
deleted file mode 100644
index 93274e5..0000000
--- a/erpnext/healthcare/doctype/appointment_type/test_appointment_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Appointment Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Appointment Type
-		() => frappe.tests.make('Appointment Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js
deleted file mode 100644
index 80ef3d5..0000000
--- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Clinical Procedure", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Clinical Procedure
-		() => frappe.tests.make('Clinical Procedure', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
index 81a3982..0326e5e 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/test_clinical_procedure.py
@@ -11,7 +11,7 @@
 
 class TestClinicalProcedure(unittest.TestCase):
 	def test_procedure_template_item(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		procedure_template = create_clinical_procedure_template()
 		self.assertTrue(frappe.db.exists('Item', procedure_template.item))
 
@@ -20,7 +20,7 @@
 		self.assertEqual(frappe.db.get_value('Item', procedure_template.item, 'disabled'), 1)
 
 	def test_consumables(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		procedure_template = create_clinical_procedure_template()
 		procedure_template.allow_stock_consumption = 1
 		consumable = create_consumable()
diff --git a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js b/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js
deleted file mode 100644
index 1dde8b5..0000000
--- a/erpnext/healthcare/doctype/clinical_procedure_template/test_clinical_procedure_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Clinical Procedure Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Clinical Procedure Template
-		() => frappe.tests.make('Clinical Procedure Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/complaint/test_complaint.js b/erpnext/healthcare/doctype/complaint/test_complaint.js
deleted file mode 100644
index 9ff44d8..0000000
--- a/erpnext/healthcare/doctype/complaint/test_complaint.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Complaint", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Complaint
-		() => frappe.tests.make('Complaint', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js b/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js
deleted file mode 100644
index cacfef5..0000000
--- a/erpnext/healthcare/doctype/diagnosis/test_diagnosis.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Diagnosis", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Diagnosis
-		() => frappe.tests.make('Diagnosis', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js b/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js
deleted file mode 100644
index ba54ab1..0000000
--- a/erpnext/healthcare/doctype/dosage_form/test_dosage_form.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Dosage Form", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Dosage Form
-		() => frappe.tests.make('Dosage Form', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json
index d91e6bf..a65c566 100644
--- a/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json
+++ b/erpnext/healthcare/doctype/drug_prescription/drug_prescription.json
@@ -56,6 +56,7 @@
    "reqd": 1
   },
   {
+   "allow_in_quick_entry": 1,
    "fieldname": "dosage_form",
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
@@ -109,7 +110,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-09-30 23:32:09.495288",
+ "modified": "2021-06-11 11:53:06.343704",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Drug Prescription",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.json b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
index b001bf0..d76b42e 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.json
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.json
@@ -46,13 +46,13 @@
   {
    "fieldname": "visited",
    "fieldtype": "Int",
-   "label": "Visited yet",
+   "label": "Visits Completed",
    "read_only": 1
   },
   {
    "fieldname": "valid_till",
    "fieldtype": "Date",
-   "label": "Valid till",
+   "label": "Valid Till",
    "read_only": 1
   },
   {
@@ -106,7 +106,7 @@
  ],
  "in_create": 1,
  "links": [],
- "modified": "2020-03-17 20:25:06.487418",
+ "modified": "2021-08-26 10:51:05.609349",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Fee Validity",
diff --git a/erpnext/healthcare/doctype/fee_validity/fee_validity.py b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
index 5b9c179..59586e0 100644
--- a/erpnext/healthcare/doctype/fee_validity/fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/fee_validity.py
@@ -11,7 +11,6 @@
 class FeeValidity(Document):
 	def validate(self):
 		self.update_status()
-		self.set_start_date()
 
 	def update_status(self):
 		if self.visited >= self.max_visits:
@@ -19,13 +18,6 @@
 		else:
 			self.status = 'Pending'
 
-	def set_start_date(self):
-		self.start_date = getdate()
-		for appointment in self.ref_appointments:
-			appointment_date = frappe.db.get_value('Patient Appointment', appointment.appointment, 'appointment_date')
-			if getdate(appointment_date) < self.start_date:
-				self.start_date = getdate(appointment_date)
-
 
 def create_fee_validity(appointment):
 	if not check_is_new_patient(appointment):
@@ -36,11 +28,9 @@
 	fee_validity.patient = appointment.patient
 	fee_validity.max_visits = frappe.db.get_single_value('Healthcare Settings', 'max_visits') or 1
 	valid_days = frappe.db.get_single_value('Healthcare Settings', 'valid_days') or 1
-	fee_validity.visited = 1
+	fee_validity.visited = 0
+	fee_validity.start_date = getdate(appointment.appointment_date)
 	fee_validity.valid_till = getdate(appointment.appointment_date) + datetime.timedelta(days=int(valid_days))
-	fee_validity.append('ref_appointments', {
-		'appointment': appointment.name
-	})
 	fee_validity.save(ignore_permissions=True)
 	return fee_validity
 
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js
deleted file mode 100644
index 0ebb974..0000000
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Fee Validity", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Fee Validity
-		() => frappe.tests.make('Fee Validity', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
index 82e7136..957f852 100644
--- a/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
+++ b/erpnext/healthcare/doctype/fee_validity/test_fee_validity.py
@@ -22,14 +22,14 @@
 		item = create_healthcare_service_items()
 		healthcare_settings = frappe.get_single("Healthcare Settings")
 		healthcare_settings.enable_free_follow_ups = 1
-		healthcare_settings.max_visits = 2
+		healthcare_settings.max_visits = 1
 		healthcare_settings.valid_days = 7
 		healthcare_settings.automate_appointment_invoicing = 1
 		healthcare_settings.op_consulting_charge_item = item
 		healthcare_settings.save(ignore_permissions=True)
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 
-		# For first appointment, invoice is generated
+		# For first appointment, invoice is generated. First appointment not considered in fee validity
 		appointment = create_appointment(patient, practitioner, nowdate())
 		invoiced = frappe.db.get_value("Patient Appointment", appointment.name, "invoiced")
 		self.assertEqual(invoiced, 1)
diff --git a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js b/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js
deleted file mode 100644
index 75aa208..0000000
--- a/erpnext/healthcare/doctype/healthcare_practitioner/test_healthcare_practitioner.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Practitioner", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Healthcare Practitioner
-		() => frappe.tests.make('Healthcare Practitioner', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js
index 2cdd550..2d1caf7 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.js
@@ -7,8 +7,8 @@
 
 		// get query select healthcare service unit
 		frm.fields_dict['parent_healthcare_service_unit'].get_query = function(doc) {
-			return{
-				filters:[
+			return {
+				filters: [
 					['Healthcare Service Unit', 'is_group', '=', 1],
 					['Healthcare Service Unit', 'name', '!=', doc.healthcare_service_unit_name]
 				]
@@ -21,6 +21,14 @@
 		frm.add_custom_button(__('Healthcare Service Unit Tree'), function() {
 			frappe.set_route('Tree', 'Healthcare Service Unit');
 		});
+
+		frm.set_query('warehouse', function() {
+			return {
+				filters: {
+					'company': frm.doc.company
+				}
+			};
+		});
 	},
 	set_root_readonly: function(frm) {
 		// read-only for root healthcare service unit
@@ -43,5 +51,10 @@
 		else {
 			frm.set_df_property('service_unit_type', 'reqd', 1);
 		}
+	},
+	overlap_appointments: function(frm) {
+		if (frm.doc.overlap_appointments == 0) {
+			frm.set_value('service_unit_capacity', '');
+		}
 	}
 });
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
index 9ee865a..8935ec7 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.json
@@ -16,6 +16,7 @@
   "service_unit_type",
   "allow_appointments",
   "overlap_appointments",
+  "service_unit_capacity",
   "inpatient_occupancy",
   "occupancy_status",
   "column_break_9",
@@ -31,6 +32,8 @@
   {
    "fieldname": "healthcare_service_unit_name",
    "fieldtype": "Data",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_global_search": 1,
    "in_list_view": 1,
    "label": "Service Unit",
@@ -41,6 +44,8 @@
    "bold": 1,
    "fieldname": "parent_healthcare_service_unit",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "ignore_user_permissions": 1,
    "in_list_view": 1,
    "label": "Parent Service Unit",
@@ -52,6 +57,8 @@
    "depends_on": "eval:doc.inpatient_occupancy != 1 && doc.allow_appointments != 1",
    "fieldname": "is_group",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Is Group"
   },
   {
@@ -59,6 +66,8 @@
    "depends_on": "eval:doc.is_group != 1",
    "fieldname": "service_unit_type",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Service Unit Type",
    "options": "Healthcare Service Unit Type"
   },
@@ -68,6 +77,8 @@
    "fetch_from": "service_unit_type.allow_appointments",
    "fieldname": "allow_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_list_view": 1,
    "label": "Allow Appointments",
    "no_copy": 1,
@@ -79,6 +90,8 @@
    "fetch_from": "service_unit_type.overlap_appointments",
    "fieldname": "overlap_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Allow Overlap",
    "no_copy": 1,
    "read_only": 1
@@ -90,6 +103,8 @@
    "fetch_from": "service_unit_type.inpatient_occupancy",
    "fieldname": "inpatient_occupancy",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_list_view": 1,
    "label": "Inpatient Occupancy",
    "no_copy": 1,
@@ -100,6 +115,8 @@
    "depends_on": "eval:doc.inpatient_occupancy == 1",
    "fieldname": "occupancy_status",
    "fieldtype": "Select",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Occupancy Status",
    "no_copy": 1,
    "options": "Vacant\nOccupied",
@@ -107,13 +124,17 @@
   },
   {
    "fieldname": "column_break_9",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "hide_days": 1,
+   "hide_seconds": 1
   },
   {
    "bold": 1,
    "depends_on": "eval:doc.is_group != 1",
    "fieldname": "warehouse",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Warehouse",
    "no_copy": 1,
    "options": "Warehouse"
@@ -121,6 +142,8 @@
   {
    "fieldname": "company",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "ignore_user_permissions": 1,
    "in_list_view": 1,
    "in_standard_filter": 1,
@@ -134,6 +157,8 @@
    "fieldname": "lft",
    "fieldtype": "Int",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "lft",
    "no_copy": 1,
    "print_hide": 1,
@@ -143,6 +168,8 @@
    "fieldname": "rgt",
    "fieldtype": "Int",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "rgt",
    "no_copy": 1,
    "print_hide": 1,
@@ -152,6 +179,8 @@
    "fieldname": "old_parent",
    "fieldtype": "Link",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "ignore_user_permissions": 1,
    "label": "Old Parent",
    "no_copy": 1,
@@ -163,14 +192,26 @@
    "collapsible": 1,
    "fieldname": "tree_details_section",
    "fieldtype": "Section Break",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Tree Details"
+  },
+  {
+   "depends_on": "eval:doc.overlap_appointments == 1",
+   "fieldname": "service_unit_capacity",
+   "fieldtype": "Int",
+   "label": "Service Unit Capacity",
+   "mandatory_depends_on": "eval:doc.overlap_appointments == 1",
+   "non_negative": 1
   }
  ],
+ "is_tree": 1,
  "links": [],
- "modified": "2020-05-20 18:26:56.065543",
+ "modified": "2021-08-19 14:09:11.643464",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Service Unit",
+ "nsm_parent_field": "parent_healthcare_service_unit",
  "owner": "Administrator",
  "permissions": [
   {
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
index 9e0417a..5e76ed7 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit.py
@@ -5,14 +5,21 @@
 from __future__ import unicode_literals
 
 from frappe.utils.nestedset import NestedSet
+from frappe.utils import cint, cstr
 import frappe
+from frappe import _
+import json
+
 
 class HealthcareServiceUnit(NestedSet):
 	nsm_parent_field = 'parent_healthcare_service_unit'
 
+	def validate(self):
+		self.set_service_unit_properties()
+
 	def autoname(self):
 		if self.company:
-			suffix = " - " + frappe.get_cached_value('Company',  self.company,  "abbr")
+			suffix = " - " + frappe.get_cached_value('Company', self.company, 'abbr')
 			if not self.healthcare_service_unit_name.endswith(suffix):
 				self.name = self.healthcare_service_unit_name + suffix
 		else:
@@ -22,16 +29,86 @@
 		super(HealthcareServiceUnit, self).on_update()
 		self.validate_one_root()
 
-	def after_insert(self):
-		if self.is_group:
-			self.allow_appointments = 0
-			self.overlap_appointments = 0
-			self.inpatient_occupancy = 0
-		elif self.service_unit_type:
+	def set_service_unit_properties(self):
+		if cint(self.is_group):
+			self.allow_appointments = False
+			self.overlap_appointments = False
+			self.inpatient_occupancy = False
+			self.service_unit_capacity = 0
+			self.occupancy_status = ''
+			self.service_unit_type = ''
+		elif self.service_unit_type != '':
 			service_unit_type = frappe.get_doc('Healthcare Service Unit Type', self.service_unit_type)
 			self.allow_appointments = service_unit_type.allow_appointments
-			self.overlap_appointments = service_unit_type.overlap_appointments
 			self.inpatient_occupancy = service_unit_type.inpatient_occupancy
-			if self.inpatient_occupancy:
+
+			if self.inpatient_occupancy and self.occupancy_status != '':
 				self.occupancy_status = 'Vacant'
-				self.overlap_appointments = 0
+
+			if service_unit_type.overlap_appointments:
+				self.overlap_appointments = True
+			else:
+				self.overlap_appointments = False
+				self.service_unit_capacity = 0
+
+		if self.overlap_appointments:
+			if not self.service_unit_capacity:
+				frappe.throw(_('Please set a valid Service Unit Capacity to enable Overlapping Appointments'),
+					title=_('Mandatory'))
+
+
+@frappe.whitelist()
+def add_multiple_service_units(parent, data):
+	'''
+	parent - parent service unit under which the service units are to be created
+	data (dict) - company, healthcare_service_unit_name, count, service_unit_type, warehouse, service_unit_capacity
+	'''
+	if not parent or not data:
+		return
+
+	data = json.loads(data)
+	company = data.get('company') or \
+		frappe.defaults.get_defaults().get('company') or \
+		frappe.db.get_single_value('Global Defaults', 'default_company')
+
+	if not data.get('healthcare_service_unit_name') or not company:
+		frappe.throw(_('Service Unit Name and Company are mandatory to create Healthcare Service Units'),
+			title=_('Missing Required Fields'))
+
+	count = cint(data.get('count') or 0)
+	if count <= 0:
+		frappe.throw(_('Number of Service Units to be created should at least be 1'),
+			title=_('Invalid Number of Service Units'))
+
+	capacity = cint(data.get('service_unit_capacity') or 1)
+
+	service_unit = {
+		'doctype': 'Healthcare Service Unit',
+		'parent_healthcare_service_unit': parent,
+		'service_unit_type': data.get('service_unit_type') or None,
+		'service_unit_capacity': capacity if capacity > 0 else 1,
+		'warehouse': data.get('warehouse') or None,
+		'company': company
+	}
+
+	service_unit_name = '{}'.format(data.get('healthcare_service_unit_name').strip(' -'))
+
+	last_suffix = frappe.db.sql("""SELECT
+		IFNULL(MAX(CAST(SUBSTRING(name FROM %(start)s FOR 4) AS UNSIGNED)), 0)
+		FROM `tabHealthcare Service Unit`
+		WHERE name like %(prefix)s AND company=%(company)s""",
+		{'start': len(service_unit_name)+2, 'prefix': '{}-%'.format(service_unit_name), 'company': company},
+		as_list=1)[0][0]
+	start_suffix = cint(last_suffix) + 1
+
+	failed_list = []
+	for i in range(start_suffix, count + start_suffix):
+		# name to be in the form WARD-####
+		service_unit['healthcare_service_unit_name'] = '{}-{}'.format(service_unit_name, cstr('%0*d' % (4, i)))
+		service_unit_doc = frappe.get_doc(service_unit)
+		try:
+			service_unit_doc.insert()
+		except Exception:
+			failed_list.append(service_unit['healthcare_service_unit_name'])
+
+	return failed_list
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
index b75f271..ea3fea6 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit/healthcare_service_unit_tree.js
@@ -1,35 +1,185 @@
-frappe.treeview_settings["Healthcare Service Unit"] = {
-	breadcrumbs: "Healthcare Service Unit",
-	title: __("Healthcare Service Unit"),
+frappe.provide("frappe.treeview_settings");
+
+frappe.treeview_settings['Healthcare Service Unit'] = {
+	breadcrumbs: 'Healthcare Service Unit',
+	title: __('Service Unit Tree'),
 	get_tree_root: false,
-	filters: [{
-		fieldname: "company",
-		fieldtype: "Select",
-		options: erpnext.utils.get_tree_options("company"),
-		label: __("Company"),
-		default: erpnext.utils.get_tree_default("company")
-	}],
 	get_tree_nodes: 'erpnext.healthcare.utils.get_children',
-	ignore_fields:["parent_healthcare_service_unit"],
-	onrender: function(node) {
-		if (node.data.occupied_out_of_vacant!==undefined) {
-			$('<span class="balance-area pull-right">'
-				+ " " + node.data.occupied_out_of_vacant
+	filters: [{
+		fieldname: 'company',
+		fieldtype: 'Select',
+		options: erpnext.utils.get_tree_options('company'),
+		label: __('Company'),
+		default: erpnext.utils.get_tree_default('company')
+	}],
+	fields: [
+		{
+			fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('New Service Unit Name'),
+			reqd: true
+		},
+		{
+			fieldtype: 'Check', fieldname: 'is_group', label: __('Is Group'),
+			description: __("Child nodes can be only created under 'Group' type nodes")
+		},
+		{
+			fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'),
+			options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'),
+			depends_on: 'eval:!doc.is_group', default: '',
+			onchange: () => {
+				if (cur_dialog) {
+					if (cur_dialog.fields_dict.service_unit_type.value) {
+						frappe.db.get_value('Healthcare Service Unit Type',
+							cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments')
+							.then(r => {
+								if (r.message.overlap_appointments) {
+									cur_dialog.set_df_property('service_unit_capacity', 'hidden', false);
+									cur_dialog.set_df_property('service_unit_capacity', 'reqd', true);
+								} else {
+									cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+									cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+								}
+							});
+					} else {
+						cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+						cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+					}
+				}
+			}
+		},
+		{
+			fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'),
+			description: __('Sets the number of concurrent appointments allowed'), reqd: false,
+			depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true
+		},
+		{
+			fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse',
+			description: __('Optional, if you want to manage stock separately for this Service Unit'),
+			depends_on: 'eval:!doc.is_group'
+		},
+		{
+			fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true,
+			default: () => {
+				return cur_page.page.page.fields_dict.company.value;
+			}
+		}
+	],
+	ignore_fields: ['parent_healthcare_service_unit'],
+	onrender: function (node) {
+		if (node.data.occupied_of_available !== undefined) {
+			$("<span class='balance-area pull-right text-muted small'>"
+				+ ' ' + node.data.occupied_of_available
 				+ '</span>').insertBefore(node.$ul);
 		}
-		if (node.data && node.data.inpatient_occupancy!==undefined) {
+		if (node.data && node.data.inpatient_occupancy !== undefined) {
 			if (node.data.inpatient_occupancy == 1) {
-				if (node.data.occupancy_status == "Occupied") {
-					$('<span class="balance-area pull-right">'
-						+ " " + node.data.occupancy_status
+				if (node.data.occupancy_status == 'Occupied') {
+					$("<span class='balance-area pull-right small'>"
+						+ ' ' + node.data.occupancy_status
 						+ '</span>').insertBefore(node.$ul);
 				}
-				if (node.data.occupancy_status == "Vacant") {
-					$('<span class="balance-area pull-right">'
-						+ " " + node.data.occupancy_status
+				if (node.data.occupancy_status == 'Vacant') {
+					$("<span class='balance-area pull-right text-muted small'>"
+						+ ' ' + node.data.occupancy_status
 						+ '</span>').insertBefore(node.$ul);
 				}
 			}
 		}
 	},
+	post_render: function (treeview) {
+		frappe.treeview_settings['Healthcare Service Unit'].treeview = {};
+		$.extend(frappe.treeview_settings['Healthcare Service Unit'].treeview, treeview);
+	},
+	toolbar: [
+		{
+			label: __('Add Multiple'),
+			condition: function (node) {
+				return node.expandable;
+			},
+			click: function (node) {
+				const dialog = new frappe.ui.Dialog({
+					title: __('Add Multiple Service Units'),
+					fields: [
+						{
+							fieldtype: 'Data', fieldname: 'healthcare_service_unit_name', label: __('Service Unit Name'),
+							reqd: true, description: __("Will be serially suffixed to maintain uniquness. Example: 'Ward' will be named as 'Ward-####'"),
+						},
+						{
+							fieldtype: 'Int', fieldname: 'count', label: __('Number of Service Units'),
+							reqd: true
+						},
+						{
+							fieldtype: 'Link', fieldname: 'service_unit_type', label: __('Service Unit Type'),
+							options: 'Healthcare Service Unit Type', description: __('Type of the new Service Unit'),
+							depends_on: 'eval:!doc.is_group', default: '', reqd: true,
+							onchange: () => {
+								if (cur_dialog) {
+									if (cur_dialog.fields_dict.service_unit_type.value) {
+										frappe.db.get_value('Healthcare Service Unit Type',
+											cur_dialog.fields_dict.service_unit_type.value, 'overlap_appointments')
+											.then(r => {
+												if (r.message.overlap_appointments) {
+													cur_dialog.set_df_property('service_unit_capacity', 'hidden', false);
+													cur_dialog.set_df_property('service_unit_capacity', 'reqd', true);
+												} else {
+													cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+													cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+												}
+											});
+									} else {
+										cur_dialog.set_df_property('service_unit_capacity', 'hidden', true);
+										cur_dialog.set_df_property('service_unit_capacity', 'reqd', false);
+									}
+								}
+							}
+						},
+						{
+							fieldtype: 'Int', fieldname: 'service_unit_capacity', label: __('Service Unit Capacity'),
+							description: __('Sets the number of concurrent appointments allowed'), reqd: false,
+							depends_on: "eval:!doc.is_group && doc.service_unit_type != ''", hidden: true
+						},
+						{
+							fieldtype: 'Link', fieldname: 'warehouse', label: __('Warehouse'), options: 'Warehouse',
+							description: __('Optional, if you want to manage stock separately for this Service Unit'),
+						},
+						{
+							fieldtype: 'Link', fieldname: 'company', label: __('Company'), options: 'Company', reqd: true,
+							default: () => {
+								return cur_page.page.page.fields_dict.company.get_value();
+							}
+						}
+					],
+					primary_action: () => {
+						dialog.hide();
+						let vals = dialog.get_values();
+						if (!vals) return;
+
+						return frappe.call({
+							method: 'erpnext.healthcare.doctype.healthcare_service_unit.healthcare_service_unit.add_multiple_service_units',
+							args: {
+								parent: node.data.value,
+								data: vals
+							},
+							callback: function (r) {
+								if (!r.exc && r.message) {
+									frappe.treeview_settings['Healthcare Service Unit'].treeview.tree.load_children(node, true);
+
+									frappe.show_alert({
+										message: __('{0} Service Units created', [vals.count - r.message.length]),
+										indicator: 'green'
+									});
+								} else {
+									frappe.msgprint(__('Could not create Service Units'));
+								}
+							},
+							freeze: true,
+							freeze_message: __('Creating {0} Service Units', [vals.count])
+						});
+					},
+					primary_action_label: __('Create')
+				});
+				dialog.show();
+			}
+		}
+	],
+	extend_toolbar: true
 };
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js b/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js
deleted file mode 100644
index a67a411..0000000
--- a/erpnext/healthcare/doctype/healthcare_service_unit/test_healthcare_service_unit.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Service Unit", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Healthcare Service Unit
-		() => frappe.tests.make('Healthcare Service Unit', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
index eb33ab6..ecf4aa1 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.js
@@ -68,8 +68,8 @@
 			if (values) {
 				frappe.call({
 					"method": "erpnext.healthcare.doctype.healthcare_service_unit_type.healthcare_service_unit_type.change_item_code",
-					"args": {item: doc.item, item_code: values['item_code'], doc_name: doc.name},
-					callback: function () {
+					"args": { item: doc.item, item_code: values['item_code'], doc_name: doc.name },
+					callback: function() {
 						frm.reload_doc();
 					}
 				});
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
index 4b8503d..9c81c65 100644
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
+++ b/erpnext/healthcare/doctype/healthcare_service_unit_type/healthcare_service_unit_type.json
@@ -29,6 +29,8 @@
   {
    "fieldname": "service_unit_type",
    "fieldtype": "Data",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "in_list_view": 1,
    "label": "Service Unit Type",
    "no_copy": 1,
@@ -41,6 +43,8 @@
    "depends_on": "eval:doc.inpatient_occupancy != 1",
    "fieldname": "allow_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Allow Appointments"
   },
   {
@@ -49,6 +53,8 @@
    "depends_on": "eval:doc.allow_appointments == 1 && doc.inpatient_occupany != 1",
    "fieldname": "overlap_appointments",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Allow Overlap"
   },
   {
@@ -57,6 +63,8 @@
    "depends_on": "eval:doc.allow_appointments != 1",
    "fieldname": "inpatient_occupancy",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Inpatient Occupancy"
   },
   {
@@ -65,17 +73,23 @@
    "depends_on": "eval:doc.inpatient_occupancy == 1 && doc.allow_appointments != 1",
    "fieldname": "is_billable",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Is Billable"
   },
   {
    "depends_on": "is_billable",
    "fieldname": "item_details",
    "fieldtype": "Section Break",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item Details"
   },
   {
    "fieldname": "item",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item",
    "no_copy": 1,
    "options": "Item",
@@ -84,6 +98,8 @@
   {
    "fieldname": "item_code",
    "fieldtype": "Data",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item Code",
    "mandatory_depends_on": "eval: doc.is_billable == 1",
    "no_copy": 1
@@ -91,6 +107,8 @@
   {
    "fieldname": "item_group",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Item Group",
    "mandatory_depends_on": "eval: doc.is_billable == 1",
    "options": "Item Group"
@@ -98,6 +116,8 @@
   {
    "fieldname": "uom",
    "fieldtype": "Link",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "UOM",
    "mandatory_depends_on": "eval: doc.is_billable == 1",
    "options": "UOM"
@@ -105,28 +125,38 @@
   {
    "fieldname": "no_of_hours",
    "fieldtype": "Int",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "UOM Conversion in Hours",
    "mandatory_depends_on": "eval: doc.is_billable == 1"
   },
   {
    "fieldname": "column_break_11",
-   "fieldtype": "Column Break"
+   "fieldtype": "Column Break",
+   "hide_days": 1,
+   "hide_seconds": 1
   },
   {
    "fieldname": "rate",
    "fieldtype": "Currency",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Rate / UOM"
   },
   {
    "default": "0",
    "fieldname": "disabled",
    "fieldtype": "Check",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Disabled",
    "no_copy": 1
   },
   {
    "fieldname": "description",
    "fieldtype": "Small Text",
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Description"
   },
   {
@@ -134,11 +164,13 @@
    "fieldname": "change_in_item",
    "fieldtype": "Check",
    "hidden": 1,
+   "hide_days": 1,
+   "hide_seconds": 1,
    "label": "Change in Item"
   }
  ],
  "links": [],
- "modified": "2020-05-20 15:31:09.627516",
+ "modified": "2021-08-19 17:52:30.266667",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare Service Unit Type",
diff --git a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js b/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js
deleted file mode 100644
index 6db8f9e..0000000
--- a/erpnext/healthcare/doctype/healthcare_service_unit_type/test_healthcare_service_unit_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Service Unit Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Healthcare Service Unit Type
-		() => frappe.tests.make('Healthcare Service Unit Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js b/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js
deleted file mode 100644
index ca10925..0000000
--- a/erpnext/healthcare/doctype/healthcare_settings/test_healthcare_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Healthcare Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Healthcare Settings
-		() => frappe.tests.make('Healthcare Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js
deleted file mode 100644
index 1ce9afa..0000000
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Inpatient Record", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Inpatient Record
-		() => frappe.tests.make('Inpatient Record', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
index a8c7720..b4a9612 100644
--- a/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/test_inpatient_record.py
@@ -151,7 +151,7 @@
 
 	if not service_unit:
 		service_unit = frappe.new_doc("Healthcare Service Unit")
-		service_unit.healthcare_service_unit_name = unit_name or "Test Service Unit Ip Occupancy"
+		service_unit.healthcare_service_unit_name = unit_name or "_Test Service Unit Ip Occupancy"
 		service_unit.company = "_Test Company"
 		service_unit.service_unit_type = get_service_unit_type()
 		service_unit.inpatient_occupancy = 1
@@ -159,12 +159,12 @@
 		service_unit.is_group = 0
 		service_unit_parent_name = frappe.db.exists({
 				"doctype": "Healthcare Service Unit",
-				"healthcare_service_unit_name": "All Healthcare Service Units",
+				"healthcare_service_unit_name": "_Test All Healthcare Service Units",
 				"is_group": 1
 				})
 		if not service_unit_parent_name:
 			parent_service_unit = frappe.new_doc("Healthcare Service Unit")
-			parent_service_unit.healthcare_service_unit_name = "All Healthcare Service Units"
+			parent_service_unit.healthcare_service_unit_name = "_Test All Healthcare Service Units"
 			parent_service_unit.is_group = 1
 			parent_service_unit.save(ignore_permissions = True)
 			service_unit.parent_healthcare_service_unit = parent_service_unit.name
@@ -180,7 +180,7 @@
 
 	if not service_unit_type:
 		service_unit_type = frappe.new_doc("Healthcare Service Unit Type")
-		service_unit_type.service_unit_type = "Test Service Unit Type Ip Occupancy"
+		service_unit_type.service_unit_type = "_Test Service Unit Type Ip Occupancy"
 		service_unit_type.inpatient_occupancy = 1
 		service_unit_type.save(ignore_permissions = True)
 		return service_unit_type.name
diff --git a/erpnext/healthcare/doctype/lab_test/lab_test.py b/erpnext/healthcare/doctype/lab_test/lab_test.py
index 4b57cd0..74495a8 100644
--- a/erpnext/healthcare/doctype/lab_test/lab_test.py
+++ b/erpnext/healthcare/doctype/lab_test/lab_test.py
@@ -34,7 +34,7 @@
 			frappe.db.set_value('Lab Prescription', self.prescription, 'lab_test_created', 1)
 			if frappe.db.get_value('Lab Prescription', self.prescription, 'invoiced'):
 				self.invoiced = True
-		if not self.lab_test_name and self.template:
+		if self.template:
 			self.load_test_from_template()
 			self.reload()
 
@@ -50,7 +50,7 @@
 					item.secondary_uom_result = float(item.result_value) * float(item.conversion_factor)
 				except:
 					item.secondary_uom_result = ''
-					frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated'.format(item.idx)), title = _('Warning'))
+					frappe.msgprint(_('Row #{0}: Result for Secondary UOM not calculated').format(item.idx), title = _('Warning'))
 
 	def validate_result_values(self):
 		if self.normal_test_items:
@@ -229,9 +229,9 @@
 			sample_collection = frappe.get_doc('Sample Collection', sample_exists[0][0])
 			quantity = int(sample_collection.sample_qty) + int(template.sample_qty)
 			if template.sample_details:
-				sample_details = sample_collection.sample_details + '\n-\n' + _('Test: ')
+				sample_details = sample_collection.sample_details + '\n-\n' + _('Test :')
 				sample_details += (template.get('lab_test_name') or template.get('template')) +	'\n'
-				sample_details += _('Collection Details: ') + '\n\t' + template.sample_details
+				sample_details += _('Collection Details:') + '\n\t' + template.sample_details
 				frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_details', sample_details)
 
 			frappe.db.set_value('Sample Collection', sample_collection.name, 'sample_qty', quantity)
diff --git a/erpnext/healthcare/doctype/lab_test/test_lab_test.js b/erpnext/healthcare/doctype/lab_test/test_lab_test.js
deleted file mode 100644
index 57cb22b..0000000
--- a/erpnext/healthcare/doctype/lab_test/test_lab_test.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Lab Test
-		() => frappe.tests.make('Lab Test', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js b/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js
deleted file mode 100644
index ace60de..0000000
--- a/erpnext/healthcare/doctype/lab_test_sample/test_lab_test_sample.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test Sample", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Lab Test Sample
-		() => frappe.tests.make('Lab Test Sample', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js b/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js
deleted file mode 100644
index 7c2ec8c..0000000
--- a/erpnext/healthcare/doctype/lab_test_template/test_lab_test_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Lab Test Template
-		() => frappe.tests.make('Lab Test Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js b/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js
deleted file mode 100644
index 1328dda..0000000
--- a/erpnext/healthcare/doctype/lab_test_uom/test_lab_test_uom.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Lab Test UOM", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Lab Test UOM
-		() => frappe.tests.make('Lab Test UOM', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/medical_code/test_medical_code.js b/erpnext/healthcare/doctype/medical_code/test_medical_code.js
deleted file mode 100644
index 8cc7c40..0000000
--- a/erpnext/healthcare/doctype/medical_code/test_medical_code.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Medical Code", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Medical Code
-		() => frappe.tests.make('Medical Code', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js b/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js
deleted file mode 100644
index 6ab6d53..0000000
--- a/erpnext/healthcare/doctype/medical_code_standard/test_medical_code_standard.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Medical Code Standard", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Medical Code Standard
-		() => frappe.tests.make('Medical Code Standard', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/medical_department/test_medical_department.js b/erpnext/healthcare/doctype/medical_department/test_medical_department.js
deleted file mode 100644
index fdf4971..0000000
--- a/erpnext/healthcare/doctype/medical_department/test_medical_department.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Medical Department", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Medical Department
-		() => frappe.tests.make('Medical Department', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/organism/test_organism.js b/erpnext/healthcare/doctype/organism/test_organism.js
deleted file mode 100644
index d57e553..0000000
--- a/erpnext/healthcare/doctype/organism/test_organism.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Organism", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Organism
-		() => frappe.tests.make('Organism', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient/patient.js b/erpnext/healthcare/doctype/patient/patient.js
index bce42e5..9266467 100644
--- a/erpnext/healthcare/doctype/patient/patient.js
+++ b/erpnext/healthcare/doctype/patient/patient.js
@@ -26,31 +26,39 @@
 		}
 
 		if (frm.doc.patient_name && frappe.user.has_role('Physician')) {
+			frm.add_custom_button(__('Patient Progress'), function() {
+				frappe.route_options = {'patient': frm.doc.name};
+				frappe.set_route('patient-progress');
+			}, __('View'));
+
 			frm.add_custom_button(__('Patient History'), function() {
 				frappe.route_options = {'patient': frm.doc.name};
 				frappe.set_route('patient_history');
-			},'View');
+			}, __('View'));
 		}
 
-		if (!frm.doc.__islocal && (frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) {
-			frm.add_custom_button(__('Vital Signs'), function () {
-				create_vital_signs(frm);
-			}, 'Create');
-			frm.add_custom_button(__('Medical Record'), function () {
-				create_medical_record(frm);
-			}, 'Create');
-			frm.add_custom_button(__('Patient Encounter'), function () {
-				create_encounter(frm);
-			}, 'Create');
-			frm.toggle_enable(['customer'], 0); // ToDo, allow change only if no transactions booked or better, add merge option
+		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Patient'};
+		frm.toggle_display(['address_html', 'contact_html'], !frm.is_new());
+
+		if (!frm.is_new()) {
+			if ((frappe.user.has_role('Nursing User') || frappe.user.has_role('Physician'))) {
+				frm.add_custom_button(__('Medical Record'), function () {
+					create_medical_record(frm);
+				}, 'Create');
+				frm.toggle_enable(['customer'], 0);
+			}
+			frappe.contacts.render_address_and_contact(frm);
+			erpnext.utils.set_party_dashboard_indicators(frm);
+		} else {
+			frappe.contacts.clear_address_and_contact(frm);
 		}
 	},
+
 	onload: function (frm) {
-		if (!frm.doc.dob) {
-			$(frm.fields_dict['age_html'].wrapper).html('');
-		}
 		if (frm.doc.dob) {
 			$(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${get_age(frm.doc.dob)}`);
+		} else {
+			$(frm.fields_dict['age_html'].wrapper).html('');
 		}
 	}
 });
@@ -59,16 +67,14 @@
 	if (frm.doc.dob) {
 		let today = new Date();
 		let birthDate = new Date(frm.doc.dob);
-		if (today < birthDate){
+		if (today < birthDate) {
 			frappe.msgprint(__('Please select a valid Date'));
 			frappe.model.set_value(frm.doctype,frm.docname, 'dob', '');
-		}
-		else {
+		} else {
 			let age_str = get_age(frm.doc.dob);
 			$(frm.fields_dict['age_html'].wrapper).html(`${__('AGE')} : ${age_str}`);
 		}
-	}
-	else {
+	} else {
 		$(frm.fields_dict['age_html'].wrapper).html('');
 	}
 });
diff --git a/erpnext/healthcare/doctype/patient/patient.json b/erpnext/healthcare/doctype/patient/patient.json
index 8af1a9c..4092a6a 100644
--- a/erpnext/healthcare/doctype/patient/patient.json
+++ b/erpnext/healthcare/doctype/patient/patient.json
@@ -1,6 +1,6 @@
 {
  "actions": [],
- "allow_copy": 1,
+ "allow_events_in_timeline": 1,
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "naming_series:",
@@ -24,12 +24,19 @@
   "image",
   "column_break_14",
   "status",
+  "uid",
   "inpatient_record",
   "inpatient_status",
   "report_preference",
   "mobile",
-  "email",
   "phone",
+  "email",
+  "invite_user",
+  "user_id",
+  "address_contacts",
+  "address_html",
+  "column_break_22",
+  "contact_html",
   "customer_details_section",
   "customer",
   "customer_group",
@@ -74,6 +81,7 @@
    "fieldtype": "Select",
    "in_preview": 1,
    "label": "Inpatient Status",
+   "no_copy": 1,
    "options": "\nAdmission Scheduled\nAdmitted\nDischarge Scheduled",
    "read_only": 1
   },
@@ -81,6 +89,7 @@
    "fieldname": "inpatient_record",
    "fieldtype": "Link",
    "label": "Inpatient Record",
+   "no_copy": 1,
    "options": "Inpatient Record",
    "read_only": 1
   },
@@ -101,6 +110,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Full Name",
+   "no_copy": 1,
    "read_only": 1,
    "search_index": 1
   },
@@ -118,6 +128,7 @@
    "fieldtype": "Select",
    "in_preview": 1,
    "label": "Blood Group",
+   "no_copy": 1,
    "options": "\nA Positive\nA Negative\nAB Positive\nAB Negative\nB Positive\nB Negative\nO Positive\nO Negative"
   },
   {
@@ -125,7 +136,8 @@
    "fieldname": "dob",
    "fieldtype": "Date",
    "in_preview": 1,
-   "label": "Date of birth"
+   "label": "Date of birth",
+   "no_copy": 1
   },
   {
    "fieldname": "age_html",
@@ -167,6 +179,7 @@
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
    "label": "Customer",
+   "no_copy": 1,
    "options": "Customer",
    "set_only_once": 1
   },
@@ -183,6 +196,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Mobile",
+   "no_copy": 1,
    "options": "Phone"
   },
   {
@@ -192,6 +206,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Email",
+   "no_copy": 1,
    "options": "Email"
   },
   {
@@ -199,6 +214,7 @@
    "fieldtype": "Data",
    "in_filter": 1,
    "label": "Phone",
+   "no_copy": 1,
    "options": "Phone"
   },
   {
@@ -230,7 +246,8 @@
    "fieldname": "medication",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Medication"
+   "label": "Medication",
+   "no_copy": 1
   },
   {
    "fieldname": "column_break_20",
@@ -240,13 +257,15 @@
    "fieldname": "medical_history",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Medical History"
+   "label": "Medical History",
+   "no_copy": 1
   },
   {
    "fieldname": "surgical_history",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Surgical History"
+   "label": "Surgical History",
+   "no_copy": 1
   },
   {
    "collapsible": 1,
@@ -258,8 +277,8 @@
    "fieldname": "occupation",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "in_standard_filter": 1,
-   "label": "Occupation"
+   "label": "Occupation",
+   "no_copy": 1
   },
   {
    "fieldname": "column_break_25",
@@ -269,6 +288,7 @@
    "fieldname": "marital_status",
    "fieldtype": "Select",
    "label": "Marital Status",
+   "no_copy": 1,
    "options": "\nSingle\nMarried\nDivorced\nWidow"
   },
   {
@@ -281,25 +301,29 @@
    "fieldname": "tobacco_past_use",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "label": "Tobacco Consumption (Past)"
+   "label": "Tobacco Consumption (Past)",
+   "no_copy": 1
   },
   {
    "fieldname": "tobacco_current_use",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "label": "Tobacco Consumption (Present)"
+   "label": "Tobacco Consumption (Present)",
+   "no_copy": 1
   },
   {
    "fieldname": "alcohol_past_use",
    "fieldtype": "Data",
    "ignore_xss_filter": 1,
-   "label": "Alcohol Consumption (Past)"
+   "label": "Alcohol Consumption (Past)",
+   "no_copy": 1
   },
   {
    "fieldname": "alcohol_current_use",
    "fieldtype": "Data",
    "ignore_user_permissions": 1,
-   "label": "Alcohol Consumption (Present)"
+   "label": "Alcohol Consumption (Present)",
+   "no_copy": 1
   },
   {
    "fieldname": "column_break_32",
@@ -309,13 +333,15 @@
    "fieldname": "surrounding_factors",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Occupational Hazards and Environmental Factors"
+   "label": "Occupational Hazards and Environmental Factors",
+   "no_copy": 1
   },
   {
    "fieldname": "other_risk_factors",
    "fieldtype": "Small Text",
    "ignore_xss_filter": 1,
-   "label": "Other Risk Factors"
+   "label": "Other Risk Factors",
+   "no_copy": 1
   },
   {
    "collapsible": 1,
@@ -331,7 +357,8 @@
    "fieldname": "patient_details",
    "fieldtype": "Text",
    "ignore_xss_filter": 1,
-   "label": "Patient Details"
+   "label": "Patient Details",
+   "no_copy": 1
   },
   {
    "fieldname": "default_currency",
@@ -342,19 +369,22 @@
   {
    "fieldname": "last_name",
    "fieldtype": "Data",
-   "label": "Last Name"
+   "label": "Last Name",
+   "no_copy": 1
   },
   {
    "fieldname": "first_name",
    "fieldtype": "Data",
    "label": "First Name",
+   "no_copy": 1,
    "oldfieldtype": "Data",
    "reqd": 1
   },
   {
    "fieldname": "middle_name",
    "fieldtype": "Data",
-   "label": "Middle Name (optional)"
+   "label": "Middle Name (optional)",
+   "no_copy": 1
   },
   {
    "collapsible": 1,
@@ -389,13 +419,63 @@
    "fieldtype": "Link",
    "label": "Print Language",
    "options": "Language"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "address_contacts",
+   "fieldtype": "Section Break",
+   "label": "Address and Contact",
+   "options": "fa fa-map-marker"
+  },
+  {
+   "fieldname": "address_html",
+   "fieldtype": "HTML",
+   "label": "Address HTML",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_22",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "contact_html",
+   "fieldtype": "HTML",
+   "label": "Contact HTML",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "allow_in_quick_entry": 1,
+   "default": "1",
+   "fieldname": "invite_user",
+   "fieldtype": "Check",
+   "label": "Invite as User",
+   "no_copy": 1,
+   "read_only_depends_on": "eval: doc.user_id"
+  },
+  {
+   "fieldname": "user_id",
+   "fieldtype": "Read Only",
+   "label": "User ID",
+   "no_copy": 1,
+   "options": "User"
+  },
+  {
+   "allow_in_quick_entry": 1,
+   "bold": 1,
+   "fieldname": "uid",
+   "fieldtype": "Data",
+   "in_standard_filter": 1,
+   "label": "Identification Number (UID)",
+   "unique": 1
   }
  ],
  "icon": "fa fa-user",
  "image_field": "image",
  "links": [],
  "max_attachments": 50,
- "modified": "2020-04-25 17:24:32.146415",
+ "modified": "2021-03-14 13:21:09.759906",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient",
@@ -453,7 +533,7 @@
  ],
  "quick_entry": 1,
  "restrict_to_domain": "Healthcare",
- "search_fields": "patient_name,mobile,email,phone",
+ "search_fields": "patient_name,mobile,email,phone,uid",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 56a3400..9dae1f6 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -8,24 +8,27 @@
 from frappe.model.document import Document
 from frappe.utils import cint, cstr, getdate
 import dateutil
+from frappe.contacts.address_and_contact import load_address_and_contact
+from frappe.contacts.doctype.contact.contact import get_default_contact
 from frappe.model.naming import set_name_by_naming_series
 from frappe.utils.nestedset import get_root_of
 from erpnext import get_default_currency
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account, send_registration_sms
+from erpnext.accounts.party import get_dashboard_info
 
 class Patient(Document):
+	def onload(self):
+		'''Load address and contacts in `__onload`'''
+		load_address_and_contact(self)
+		self.load_dashboard_info()
+
 	def validate(self):
 		self.set_full_name()
-		self.add_as_website_user()
 
 	def before_insert(self):
 		self.set_missing_customer_details()
 
 	def after_insert(self):
-		self.add_as_website_user()
-		self.reload()
-		if frappe.db.get_single_value('Healthcare Settings', 'link_customer_to_patient') and not self.customer:
-			create_customer(self)
 		if frappe.db.get_single_value('Healthcare Settings', 'collect_registration_fee'):
 			frappe.db.set_value('Patient', self.name, 'status', 'Disabled')
 		else:
@@ -49,6 +52,16 @@
 			else:
 				create_customer(self)
 
+		self.set_contact() # add or update contact
+
+		if not self.user_id and self.email and self.invite_user:
+			self.create_website_user()
+
+	def load_dashboard_info(self):
+		if self.customer:
+			info = get_dashboard_info('Customer', self.customer, None)
+			self.set_onload('dashboard_info', info)
+
 	def set_full_name(self):
 		if self.last_name:
 			self.patient_name = ' '.join(filter(None, [self.first_name, self.last_name]))
@@ -71,18 +84,24 @@
 		if not self.language:
 			self.language = frappe.db.get_single_value('System Settings', 'language')
 
-	def add_as_website_user(self):
-		if self.email:
-			if not frappe.db.exists ('User', self.email):
-				user = frappe.get_doc({
-					'doctype': 'User',
-					'first_name': self.first_name,
-					'last_name': self.last_name,
-					'email': self.email,
-					'user_type': 'Website User'
-				})
-				user.flags.ignore_permissions = True
-				user.add_roles('Patient')
+	def create_website_user(self):
+		if self.email and not frappe.db.exists('User', self.email):
+			user = frappe.get_doc({
+				'doctype': 'User',
+				'first_name': self.first_name,
+				'last_name': self.last_name,
+				'email': self.email,
+				'user_type': 'Website User',
+				'gender': self.sex,
+				'phone': self.phone,
+				'mobile_no': self.mobile,
+				'birth_date': self.dob
+			})
+			user.flags.ignore_permissions = True
+			user.enabled = True
+			user.send_welcome_email = True
+			user.add_roles('Patient')
+			frappe.db.set_value(self.doctype, self.name, 'user_id', user.name)
 
 	def autoname(self):
 		patient_name_by = frappe.db.get_single_value('Healthcare Settings', 'patient_name_by')
@@ -102,12 +121,19 @@
 
 		return name
 
+	@property
+	def age(self):
+		if not self.dob:
+			return
+		dob = getdate(self.dob)
+		age = dateutil.relativedelta.relativedelta(getdate(), dob)
+		return age
+
 	def get_age(self):
-		age_str = ''
-		if self.dob:
-			dob = getdate(self.dob)
-			age = dateutil.relativedelta.relativedelta(getdate(), dob)
-			age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
+		age = self.age
+		if not age:
+			return
+		age_str = f'{str(age.years)} {_("Years(s)")} {str(age.months)} {_("Month(s)")} {str(age.days)} {_("Day(s)")}'
 		return age_str
 
 	@frappe.whitelist()
@@ -124,6 +150,58 @@
 
 			return {'invoice': sales_invoice.name}
 
+	def set_contact(self):
+		if frappe.db.exists('Dynamic Link', {'parenttype':'Contact', 'link_doctype':'Patient', 'link_name':self.name}):
+			old_doc = self.get_doc_before_save()
+			if old_doc.email != self.email or old_doc.mobile != self.mobile or old_doc.phone != self.phone:
+				self.update_contact()
+		else:
+			self.reload()
+			if self.email or self.mobile or self.phone:
+				contact = frappe.get_doc({
+					'doctype': 'Contact',
+					'first_name': self.first_name,
+					'middle_name': self.middle_name,
+					'last_name': self.last_name,
+					'gender': self.sex,
+					'is_primary_contact': 1
+				})
+				contact.append('links', dict(link_doctype='Patient', link_name=self.name))
+				if self.customer:
+					contact.append('links', dict(link_doctype='Customer', link_name=self.customer))
+
+				contact.insert(ignore_permissions=True)
+				self.update_contact(contact) # update email, mobile and phone
+
+	def update_contact(self, contact=None):
+		if not contact:
+			contact_name = get_default_contact(self.doctype, self.name)
+			if contact_name:
+				contact = frappe.get_doc('Contact', contact_name)
+
+		if contact:
+			if self.email and self.email != contact.email_id:
+				for email in contact.email_ids:
+					email.is_primary = True if email.email_id == self.email else False
+				contact.add_email(self.email, is_primary=True)
+				contact.set_primary_email()
+
+			if self.mobile and self.mobile != contact.mobile_no:
+				for mobile in contact.phone_nos:
+					mobile.is_primary_mobile_no = True if mobile.phone == self.mobile else False
+				contact.add_phone(self.mobile, is_primary_mobile_no=True)
+				contact.set_primary('mobile_no')
+
+			if self.phone and self.phone != contact.phone:
+				for phone in contact.phone_nos:
+					phone.is_primary_phone = True if phone.phone == self.phone else False
+				contact.add_phone(self.phone, is_primary_phone=True)
+				contact.set_primary('phone')
+
+		contact.flags.ignore_validate = True # disable hook TODO: safe?
+		contact.save(ignore_permissions=True)
+
+
 def create_customer(doc):
 	customer = frappe.get_doc({
 		'doctype': 'Customer',
@@ -149,8 +227,8 @@
 	sales_invoice.debit_to = get_receivable_account(company)
 
 	item_line = sales_invoice.append('items')
-	item_line.item_name = 'Registeration Fee'
-	item_line.description = 'Registeration Fee'
+	item_line.item_name = 'Registration Fee'
+	item_line.description = 'Registration Fee'
 	item_line.qty = 1
 	item_line.uom = uom
 	item_line.conversion_factor = 1
@@ -174,8 +252,11 @@
 	return details
 
 def get_timeline_data(doctype, name):
-	"""Return timeline data from medical records"""
-	return dict(frappe.db.sql('''
+	'''
+	Return Patient's timeline data from medical records
+	Also include the associated Customer timeline data
+	'''
+	patient_timeline_data = dict(frappe.db.sql('''
 		SELECT
 			unix_timestamp(communication_date), count(*)
 		FROM
@@ -184,3 +265,11 @@
 			patient=%s
 			and `communication_date` > date_sub(curdate(), interval 1 year)
 		GROUP BY communication_date''', name))
+
+	customer = frappe.db.get_value(doctype, name, 'customer')
+	if customer:
+		from erpnext.accounts.party import get_timeline_data
+		customer_timeline_data = get_timeline_data('Customer', customer)
+		patient_timeline_data.update(customer_timeline_data)
+
+	return patient_timeline_data
diff --git a/erpnext/healthcare/doctype/patient/patient_dashboard.py b/erpnext/healthcare/doctype/patient/patient_dashboard.py
index 39603f7..7f7cfa8 100644
--- a/erpnext/healthcare/doctype/patient/patient_dashboard.py
+++ b/erpnext/healthcare/doctype/patient/patient_dashboard.py
@@ -6,22 +6,33 @@
 		'heatmap': True,
 		'heatmap_message': _('This is based on transactions against this Patient. See timeline below for details'),
 		'fieldname': 'patient',
+		'non_standard_fieldnames': {
+			'Payment Entry': 'party'
+		},
 		'transactions': [
 			{
-				'label': _('Appointments and Patient Encounters'),
-				'items': ['Patient Appointment', 'Patient Encounter']
+				'label': _('Appointments and Encounters'),
+				'items': ['Patient Appointment', 'Vital Signs', 'Patient Encounter']
 			},
 			{
 				'label': _('Lab Tests and Vital Signs'),
- 				'items': ['Lab Test', 'Sample Collection', 'Vital Signs']
+				'items': ['Lab Test', 'Sample Collection']
 			},
 			{
-				'label': _('Billing'),
-				'items': ['Sales Invoice']
+				'label': _('Rehab and Physiotherapy'),
+				'items': ['Patient Assessment', 'Therapy Session', 'Therapy Plan']
 			},
 			{
-				'label': _('Orders'),
-				'items': ['Inpatient Medication Order']
+				'label': _('Surgery'),
+				'items': ['Clinical Procedure']
+			},
+			{
+				'label': _('Admissions'),
+				'items': ['Inpatient Record', 'Inpatient Medication Order']
+			},
+			{
+				'label': _('Billing and Payments'),
+				'items': ['Sales Invoice', 'Payment Entry']
 			}
 		]
 	}
diff --git a/erpnext/healthcare/doctype/patient/test_patient.js b/erpnext/healthcare/doctype/patient/test_patient.js
deleted file mode 100644
index e1d9ecb..0000000
--- a/erpnext/healthcare/doctype/patient/test_patient.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Patient', [
-		// insert a new Patient
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
index c6e489e..49847d5 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.js
@@ -17,9 +17,9 @@
 	},
 
 	refresh: function(frm) {
-		frm.set_query('patient', function () {
+		frm.set_query('patient', function() {
 			return {
-				filters: {'status': 'Active'}
+				filters: { 'status': 'Active' }
 			};
 		});
 
@@ -64,7 +64,7 @@
 				} else {
 					frappe.call({
 						method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd',
-						args: {'patient': frm.doc.patient},
+						args: { 'patient': frm.doc.patient },
 						callback: function(data) {
 							if (data.message == true) {
 								if (frm.doc.mode_of_payment && frm.doc.paid_amount) {
@@ -97,7 +97,7 @@
 
 		if (frm.doc.patient) {
 			frm.add_custom_button(__('Patient History'), function() {
-				frappe.route_options = {'patient': frm.doc.patient};
+				frappe.route_options = { 'patient': frm.doc.patient };
 				frappe.set_route('patient_history');
 			}, __('View'));
 		}
@@ -111,14 +111,14 @@
 			});
 
 			if (frm.doc.procedure_template) {
-				frm.add_custom_button(__('Clinical Procedure'), function(){
+				frm.add_custom_button(__('Clinical Procedure'), function() {
 					frappe.model.open_mapped_doc({
 						method: 'erpnext.healthcare.doctype.clinical_procedure.clinical_procedure.make_procedure',
 						frm: frm,
 					});
 				}, __('Create'));
 			} else if (frm.doc.therapy_type) {
-				frm.add_custom_button(__('Therapy Session'),function(){
+				frm.add_custom_button(__('Therapy Session'), function() {
 					frappe.model.open_mapped_doc({
 						method: 'erpnext.healthcare.doctype.therapy_session.therapy_session.create_therapy_session',
 						frm: frm,
@@ -148,7 +148,7 @@
 					doctype: 'Patient',
 					name: frm.doc.patient
 				},
-				callback: function (data) {
+				callback: function(data) {
 					let age = null;
 					if (data.message.dob) {
 						age = calculate_age(data.message.dob);
@@ -165,7 +165,7 @@
 	},
 
 	practitioner: function(frm) {
-		if (frm.doc.practitioner ) {
+		if (frm.doc.practitioner) {
 			frm.events.set_payment_details(frm);
 		}
 	},
@@ -230,7 +230,7 @@
 	toggle_payment_fields: function(frm) {
 		frappe.call({
 			method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.check_payment_fields_reqd',
-			args: {'patient': frm.doc.patient},
+			args: { 'patient': frm.doc.patient },
 			callback: function(data) {
 				if (data.message.fee_validity) {
 					// if fee validity exists and automated appointment invoicing is enabled,
@@ -254,7 +254,7 @@
 					frm.toggle_display('paid_amount', data.message ? 1 : 0);
 					frm.toggle_display('billing_item', data.message ? 1 : 0);
 					frm.toggle_reqd('mode_of_payment', data.message ? 1 : 0);
-					frm.toggle_reqd('paid_amount', data.message ? 1 :0);
+					frm.toggle_reqd('paid_amount', data.message ? 1 : 0);
 					frm.toggle_reqd('billing_item', data.message ? 1 : 0);
 				}
 			}
@@ -265,7 +265,7 @@
 		if (frm.doc.patient) {
 			frappe.call({
 				method: "erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_prescribed_therapies",
-				args: {patient: frm.doc.patient},
+				args: { patient: frm.doc.patient },
 				callback: function(r) {
 					if (r.message) {
 						show_therapy_types(frm, r.message);
@@ -302,13 +302,13 @@
 		let d = new frappe.ui.Dialog({
 			title: __('Available slots'),
 			fields: [
-				{ fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department'},
-				{ fieldtype: 'Column Break'},
-				{ fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner'},
-				{ fieldtype: 'Column Break'},
-				{ fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date'},
-				{ fieldtype: 'Section Break'},
-				{ fieldtype: 'HTML', fieldname: 'available_slots'}
+				{ fieldtype: 'Link', options: 'Medical Department', reqd: 1, fieldname: 'department', label: 'Medical Department' },
+				{ fieldtype: 'Column Break' },
+				{ fieldtype: 'Link', options: 'Healthcare Practitioner', reqd: 1, fieldname: 'practitioner', label: 'Healthcare Practitioner' },
+				{ fieldtype: 'Column Break' },
+				{ fieldtype: 'Date', reqd: 1, fieldname: 'appointment_date', label: 'Date' },
+				{ fieldtype: 'Section Break' },
+				{ fieldtype: 'HTML', fieldname: 'available_slots' }
 
 			],
 			primary_action_label: __('Book'),
@@ -386,59 +386,22 @@
 						let $wrapper = d.fields_dict.available_slots.$wrapper;
 
 						// make buttons for each slot
-						let slot_details = data.slot_details;
-						let slot_html = '';
-						for (let i = 0; i < slot_details.length; i++) {
-							slot_html = slot_html + `<label>${slot_details[i].slot_name}</label>`;
-							slot_html = slot_html + `<br/>` + slot_details[i].avail_slot.map(slot => {
-								let disabled = '';
-								let start_str = slot.from_time;
-								let slot_start_time = moment(slot.from_time, 'HH:mm:ss');
-								let slot_to_time = moment(slot.to_time, 'HH:mm:ss');
-								let interval = (slot_to_time - slot_start_time)/60000 | 0;
-								// iterate in all booked appointments, update the start time and duration
-								slot_details[i].appointments.forEach(function(booked) {
-									let booked_moment = moment(booked.appointment_time, 'HH:mm:ss');
-									let end_time = booked_moment.clone().add(booked.duration, 'minutes');
-									// Deal with 0 duration appointments
-									if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_to_time)) {
-										if(booked.duration == 0){
-											disabled = 'disabled="disabled"';
-											return false;
-										}
-									}
-									// Check for overlaps considering appointment duration
-									if (slot_start_time.isBefore(end_time) && slot_to_time.isAfter(booked_moment)) {
-										// There is an overlap
-										disabled = 'disabled="disabled"';
-										return false;
-									}
-								});
-								return `<button class="btn btn-default"
-									data-name=${start_str}
-									data-duration=${interval}
-									data-service-unit="${slot_details[i].service_unit || ''}"
-									style="margin: 0 10px 10px 0; width: 72px;" ${disabled}>
-									${start_str.substring(0, start_str.length - 3)}
-								</button>`;
-							}).join("");
-							slot_html = slot_html + `<br/>`;
-						}
+						let slot_html = get_slots(data.slot_details);
 
 						$wrapper
 							.css('margin-bottom', 0)
 							.addClass('text-center')
 							.html(slot_html);
 
-						// blue button when clicked
+						// highlight button when clicked
 						$wrapper.on('click', 'button', function() {
 							let $btn = $(this);
-							$wrapper.find('button').removeClass('btn-primary');
-							$btn.addClass('btn-primary');
+							$wrapper.find('button').removeClass('btn-outline-primary');
+							$btn.addClass('btn-outline-primary');
 							selected_slot = $btn.attr('data-name');
 							service_unit = $btn.attr('data-service-unit');
 							duration = $btn.attr('data-duration');
-							// enable dialog action
+							// enable primary action 'Book'
 							d.get_primary_btn().attr('disabled', null);
 						});
 
@@ -448,19 +411,102 @@
 					}
 				},
 				freeze: true,
-				freeze_message: __('Fetching records......')
+				freeze_message: __('Fetching Schedule...')
 			});
 		} else {
 			fd.available_slots.html(__('Appointment date and Healthcare Practitioner are Mandatory').bold());
 		}
 	}
+
+	function get_slots(slot_details) {
+		let slot_html = '';
+		let appointment_count = 0;
+		let disabled = false;
+		let start_str, slot_start_time, slot_end_time, interval, count, count_class, tool_tip, available_slots;
+
+		slot_details.forEach((slot_info) => {
+			slot_html += `<div class="slot-info">
+				<span> <b> ${__('Practitioner Schedule:')} </b> ${slot_info.slot_name} </span><br>
+				<span> <b> ${__('Service Unit:')} </b> ${slot_info.service_unit} </span>`;
+
+			if (slot_info.service_unit_capacity) {
+				slot_html += `<br><span> <b> ${__('Maximum Capacity:')} </b> ${slot_info.service_unit_capacity} </span>`;
+			}
+
+			slot_html += '</div><br><br>';
+
+			slot_html += slot_info.avail_slot.map(slot => {
+				appointment_count = 0;
+				disabled = false;
+				start_str = slot.from_time;
+				slot_start_time = moment(slot.from_time, 'HH:mm:ss');
+				slot_end_time = moment(slot.to_time, 'HH:mm:ss');
+				interval = (slot_end_time - slot_start_time) / 60000 | 0;
+
+				// iterate in all booked appointments, update the start time and duration
+				slot_info.appointments.forEach((booked) => {
+					let booked_moment = moment(booked.appointment_time, 'HH:mm:ss');
+					let end_time = booked_moment.clone().add(booked.duration, 'minutes');
+
+					// Deal with 0 duration appointments
+					if (booked_moment.isSame(slot_start_time) || booked_moment.isBetween(slot_start_time, slot_end_time)) {
+						if (booked.duration == 0) {
+							disabled = true;
+							return false;
+						}
+					}
+
+					// Check for overlaps considering appointment duration
+					if (slot_info.allow_overlap != 1) {
+						if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) {
+							// There is an overlap
+							disabled = true;
+							return false;
+						}
+					} else {
+						if (slot_start_time.isBefore(end_time) && slot_end_time.isAfter(booked_moment)) {
+							appointment_count++;
+						}
+						if (appointment_count >= slot_info.service_unit_capacity) {
+							// There is an overlap
+							disabled = true;
+							return false;
+						}
+					}
+				});
+
+				if (slot_info.allow_overlap == 1 && slot_info.service_unit_capacity > 1) {
+					available_slots = slot_info.service_unit_capacity - appointment_count;
+					count = `${(available_slots > 0 ? available_slots : __('Full'))}`;
+					count_class = `${(available_slots > 0 ? 'badge-success' : 'badge-danger')}`;
+					tool_tip =`${available_slots} ${__('slots available for booking')}`;
+				}
+				return `
+					<button class="btn btn-secondary" data-name=${start_str}
+						data-duration=${interval}
+						data-service-unit="${slot_info.service_unit || ''}"
+						style="margin: 0 10px 10px 0; width: auto;" ${disabled ? 'disabled="disabled"' : ""}
+						data-toggle="tooltip" title="${tool_tip}">
+						${start_str.substring(0, start_str.length - 3)}<br>
+						<span class='badge ${count_class}'> ${count} </span>
+					</button>`;
+			}).join("");
+
+			if (slot_info.service_unit_capacity) {
+				slot_html += `<br/><small>${__('Each slot indicates the capacity currently available for booking')}</small>`;
+			}
+			slot_html += `<br/><br/>`;
+		});
+
+		return slot_html;
+	}
 };
 
 let get_prescribed_procedure = function(frm) {
 	if (frm.doc.patient) {
 		frappe.call({
 			method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.get_procedure_prescribed',
-			args: {patient: frm.doc.patient},
+			args: { patient: frm.doc.patient },
 			callback: function(r) {
 				if (r.message && r.message.length) {
 					show_procedure_templates(frm, r.message);
@@ -480,7 +526,7 @@
 	}
 };
 
-let show_procedure_templates = function(frm, result){
+let show_procedure_templates = function(frm, result) {
 	let d = new frappe.ui.Dialog({
 		title: __('Prescribed Procedures'),
 		fields: [
@@ -500,9 +546,11 @@
 		data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
 		data-date="%(date)s"  data-department="%(department)s">\
 		<button class="btn btn-default btn-xs">Add\
-		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {name:y[0], procedure_template: y[1],
-				encounter:y[2], consulting_practitioner:y[3], encounter_date:y[4],
-				practitioner:y[5]? y[5]:'', date: y[6]? y[6]:'', department: y[7]? y[7]:''})).appendTo(html_field);
+		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {
+			name: y[0], procedure_template: y[1],
+			encounter: y[2], consulting_practitioner: y[3], encounter_date: y[4],
+			practitioner: y[5] ? y[5] : '', date: y[6] ? y[6] : '', department: y[7] ? y[7] : ''
+		})).appendTo(html_field);
 		row.find("a").click(function() {
 			frm.doc.procedure_template = $(this).attr('data-procedure-template');
 			frm.doc.procedure_prescription = $(this).attr('data-name');
@@ -520,7 +568,7 @@
 	});
 	if (!result) {
 		let msg = __('There are no procedure prescribed for ') + frm.doc.patient;
-		$(repl('<div class="col-xs-12" style="padding-top:20px;" >%(msg)s</div></div>', {msg: msg})).appendTo(html_field);
+		$(repl('<div class="col-xs-12" style="padding-top:20px;" >%(msg)s</div></div>', { msg: msg })).appendTo(html_field);
 	}
 	d.show();
 };
@@ -535,7 +583,7 @@
 		]
 	});
 	var html_field = d.fields_dict.therapy_type.$wrapper;
-	$.each(result, function(x, y){
+	$.each(result, function(x, y) {
 		var row = $(repl('<div class="col-xs-12" style="padding-top:12px; text-align:center;" >\
 		<div class="col-xs-5"> %(encounter)s <br> %(practitioner)s <br> %(date)s </div>\
 		<div class="col-xs-5"> %(therapy)s </div>\
@@ -544,9 +592,11 @@
 		data-encounter="%(encounter)s" data-practitioner="%(practitioner)s"\
 		data-date="%(date)s"  data-department="%(department)s">\
 		<button class="btn btn-default btn-xs">Add\
-		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {therapy:y[0],
-		name: y[1], encounter:y[2], practitioner:y[3], date:y[4],
-		department:y[6]? y[6]:'', therapy_plan:y[5]})).appendTo(html_field);
+		</button></a></div></div><div class="col-xs-12"><hr/><div/>', {
+			therapy: y[0],
+			name: y[1], encounter: y[2], practitioner: y[3], date: y[4],
+			department: y[6] ? y[6] : '', therapy_plan: y[5]
+		})).appendTo(html_field);
 
 		row.find("a").click(function() {
 			frm.doc.therapy_type = $(this).attr("data-therapy");
@@ -581,13 +631,13 @@
 	frappe.new_doc('Vital Signs');
 };
 
-let update_status = function(frm, status){
+let update_status = function(frm, status) {
 	let doc = frm.doc;
 	frappe.confirm(__('Are you sure you want to cancel this appointment?'),
 		function() {
 			frappe.call({
 				method: 'erpnext.healthcare.doctype.patient_appointment.patient_appointment.update_status',
-				args: {appointment_id: doc.name, status:status},
+				args: { appointment_id: doc.name, status: status },
 				callback: function(data) {
 					if (!data.exc) {
 						frm.reload_doc();
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
index 73ec3bc..28d3a6d 100644
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.json
@@ -131,7 +131,7 @@
    "fieldtype": "Link",
    "label": "Service Unit",
    "options": "Healthcare Service Unit",
-   "set_only_once": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:doc.practitioner;",
@@ -349,7 +349,7 @@
   }
  ],
  "links": [],
- "modified": "2021-06-16 00:40:26.841794",
+ "modified": "2021-08-30 09:00:41.329387",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient Appointment",
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 05e2cd3..f0d5af9 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -6,7 +6,7 @@
 import frappe
 from frappe.model.document import Document
 import json
-from frappe.utils import getdate, get_time, flt
+from frappe.utils import getdate, get_time, flt, get_link_to_form
 from frappe.model.mapper import get_mapped_doc
 from frappe import _
 import datetime
@@ -15,6 +15,11 @@
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_receivable_account, get_income_account
 from erpnext.healthcare.utils import check_fee_validity, get_service_item_and_practitioner_charge, manage_fee_validity
 
+class MaximumCapacityError(frappe.ValidationError):
+	pass
+class OverlapError(frappe.ValidationError):
+	pass
+
 class PatientAppointment(Document):
 	def validate(self):
 		self.validate_overlaps()
@@ -49,26 +54,49 @@
 		end_time = datetime.datetime.combine(getdate(self.appointment_date), get_time(self.appointment_time)) \
 			 + datetime.timedelta(minutes=flt(self.duration))
 
-		overlaps = frappe.db.sql("""
-		select
-			name, practitioner, patient, appointment_time, duration
-		from
-			`tabPatient Appointment`
-		where
-			appointment_date=%s and name!=%s and status NOT IN ("Closed", "Cancelled")
-			and (practitioner=%s or patient=%s) and
-			((appointment_time<%s and appointment_time + INTERVAL duration MINUTE>%s) or
-			(appointment_time>%s and appointment_time<%s) or
-			(appointment_time=%s))
-		""", (self.appointment_date, self.name, self.practitioner, self.patient,
-		self.appointment_time, end_time.time(), self.appointment_time, end_time.time(), self.appointment_time))
+		# all appointments for both patient and practitioner overlapping the duration of this appointment
+		overlapping_appointments = frappe.db.sql("""
+			SELECT
+				name, practitioner, patient, appointment_time, duration, service_unit
+			FROM
+				`tabPatient Appointment`
+			WHERE
+				appointment_date=%(appointment_date)s AND name!=%(name)s AND status NOT IN ("Closed", "Cancelled") AND
+				(practitioner=%(practitioner)s OR patient=%(patient)s) AND
+				((appointment_time<%(appointment_time)s AND appointment_time + INTERVAL duration MINUTE>%(appointment_time)s) OR
+				(appointment_time>%(appointment_time)s AND appointment_time<%(end_time)s) OR
+				(appointment_time=%(appointment_time)s))
+			""",
+			{
+				'appointment_date': self.appointment_date,
+				'name': self.name,
+				'practitioner': self.practitioner,
+				'patient': self.patient,
+				'appointment_time': self.appointment_time,
+				'end_time':end_time.time()
+			},
+			as_dict = True
+		)
 
-		if overlaps:
-			overlapping_details = _('Appointment overlaps with ')
-			overlapping_details += "<b><a href='/app/Form/Patient Appointment/{0}'>{0}</a></b><br>".format(overlaps[0][0])
-			overlapping_details += _('{0} has appointment scheduled with {1} at {2} having {3} minute(s) duration.').format(
-				overlaps[0][1], overlaps[0][2], overlaps[0][3], overlaps[0][4])
-			frappe.throw(overlapping_details, title=_('Appointments Overlapping'))
+		if not overlapping_appointments:
+			return # No overlaps, nothing to validate!
+
+		if self.service_unit: # validate service unit capacity if overlap enabled
+			allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', self.service_unit,
+				['overlap_appointments', 'service_unit_capacity'])
+			if allow_overlap:
+				service_unit_appointments = list(filter(lambda appointment: appointment['service_unit'] == self.service_unit and
+					appointment['patient'] != self.patient, overlapping_appointments)) # if same patient already booked, it should be an overlap
+				if len(service_unit_appointments) >= (service_unit_capacity or 1):
+					frappe.throw(_("Not allowed, {} cannot exceed maximum capacity {}")
+						.format(frappe.bold(self.service_unit), frappe.bold(service_unit_capacity or 1)), MaximumCapacityError)
+				else: # service_unit_appointments within capacity, remove from overlapping_appointments
+					overlapping_appointments = [appointment for appointment in overlapping_appointments if appointment not in service_unit_appointments]
+
+		if overlapping_appointments:
+			frappe.throw(_("Not allowed, cannot overlap appointment {}")
+				.format(frappe.bold(', '.join([appointment['name'] for appointment in overlapping_appointments]))), OverlapError)
+
 
 	def validate_service_unit(self):
 		if self.inpatient_record and self.service_unit:
@@ -109,9 +137,13 @@
 					frappe.db.set_value('Patient Appointment', self.name, 'notes', comments)
 
 	def update_fee_validity(self):
+		if not frappe.db.get_single_value('Healthcare Settings', 'enable_free_follow_ups'):
+			return
+
 		fee_validity = manage_fee_validity(self)
 		if fee_validity:
-			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
+			frappe.msgprint(_('{0}: {1} has fee validity till {2}').format(self.patient,
+				frappe.bold(self.patient_name), fee_validity.valid_till))
 
 	@frappe.whitelist()
 	def get_therapy_types(self):
@@ -301,17 +333,13 @@
 
 
 def get_available_slots(practitioner_doc, date):
-	available_slots = []
-	slot_details = []
+	available_slots = slot_details = []
 	weekday = date.strftime('%A')
 	practitioner = practitioner_doc.name
 
 	for schedule_entry in practitioner_doc.practitioner_schedules:
-		if schedule_entry.schedule:
-			practitioner_schedule = frappe.get_doc('Practitioner Schedule', schedule_entry.schedule)
-		else:
-			frappe.throw(_('{0} does not have a Healthcare Practitioner Schedule. Add it in Healthcare Practitioner').format(
-				frappe.bold(practitioner)), title=_('Practitioner Schedule Not Found'))
+		validate_practitioner_schedules(schedule_entry, practitioner)
+		practitioner_schedule = frappe.get_doc('Practitioner Schedule', schedule_entry.schedule)
 
 		if practitioner_schedule:
 			available_slots = []
@@ -321,6 +349,8 @@
 
 			if available_slots:
 				appointments = []
+				allow_overlap = 0
+				service_unit_capacity = 0
 				# fetch all appointments to practitioner by service unit
 				filters = {
 					'practitioner': practitioner,
@@ -330,8 +360,8 @@
 				}
 
 				if schedule_entry.service_unit:
-					slot_name  = schedule_entry.schedule + ' - ' + schedule_entry.service_unit
-					allow_overlap = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, 'overlap_appointments')
+					slot_name = f'{schedule_entry.schedule}'
+					allow_overlap, service_unit_capacity = frappe.get_value('Healthcare Service Unit', schedule_entry.service_unit, ['overlap_appointments', 'service_unit_capacity'])
 					if not allow_overlap:
 						# fetch all appointments to service unit
 						filters.pop('practitioner')
@@ -346,12 +376,25 @@
 					filters=filters,
 					fields=['name', 'appointment_time', 'duration', 'status'])
 
-				slot_details.append({'slot_name':slot_name, 'service_unit':schedule_entry.service_unit,
-					'avail_slot':available_slots, 'appointments': appointments})
+				slot_details.append({'slot_name': slot_name, 'service_unit': schedule_entry.service_unit, 'avail_slot': available_slots,
+					'appointments': appointments,  'allow_overlap': allow_overlap, 'service_unit_capacity': service_unit_capacity})
 
 	return slot_details
 
 
+def validate_practitioner_schedules(schedule_entry, practitioner):
+	if schedule_entry.schedule:
+		if not schedule_entry.service_unit:
+			frappe.throw(_('Practitioner {0} does not have a Service Unit set against the Practitioner Schedule {1}.').format(
+				get_link_to_form('Healthcare Practitioner', practitioner), frappe.bold(schedule_entry.schedule)),
+				title=_('Service Unit Not Found'))
+
+	else:
+		frappe.throw(_('Practitioner {0} does not have a Practitioner Schedule assigned.').format(
+			get_link_to_form('Healthcare Practitioner', practitioner)),
+			title=_('Practitioner Schedule Not Found'))
+
+
 @frappe.whitelist()
 def update_status(appointment_id, status):
 	frappe.db.set_value('Patient Appointment', appointment_id, 'status', status)
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js
deleted file mode 100644
index 71fc177..0000000
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient Appointment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Patient Appointment
-		() => frappe.tests.make('Patient Appointment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
index 9c3392c..36ef2d1 100644
--- a/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/test_patient_appointment.py
@@ -16,9 +16,11 @@
 		frappe.db.sql("""delete from `tabFee Validity`""")
 		frappe.db.sql("""delete from `tabPatient Encounter`""")
 		make_pos_profile()
+		frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test %'""")
+		frappe.db.sql("""delete from `tabHealthcare Service Unit` where name like '_Test Service Unit Type%'""")
 
 	def test_status(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
 		appointment = create_appointment(patient, practitioner, nowdate())
 		self.assertEqual(appointment.status, 'Open')
@@ -30,7 +32,7 @@
 		self.assertEqual(frappe.db.get_value('Patient Appointment', appointment.name, 'status'), 'Open')
 
 	def test_start_encounter(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 4), invoice = 1)
 		appointment.reload()
@@ -44,7 +46,7 @@
 		self.assertEqual(encounter.invoiced, frappe.db.get_value('Patient Appointment', appointment.name, 'invoiced'))
 
 	def test_auto_invoicing(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 0)
 		appointment = create_appointment(patient, practitioner, nowdate())
@@ -60,13 +62,14 @@
 		self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
 
 	def test_auto_invoicing_based_on_department(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
+		medical_department = create_medical_department()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
-		appointment_type = create_appointment_type()
+		appointment_type = create_appointment_type({'medical_department': medical_department})
 
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
-			invoice=1, appointment_type=appointment_type.name, department='_Test Medical Department')
+			invoice=1, appointment_type=appointment_type.name, department=medical_department)
 		appointment.reload()
 
 		self.assertEqual(appointment.invoiced, 1)
@@ -78,7 +81,7 @@
 		self.assertEqual(frappe.db.get_value('Sales Invoice', sales_invoice_name, 'paid_amount'), appointment.paid_amount)
 
 	def test_auto_invoicing_according_to_appointment_type_charge(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 
@@ -88,9 +91,9 @@
 				'op_consulting_charge': 300
 		}]
 		appointment_type = create_appointment_type(args={
-				'name': 'Generic Appointment Type charge',
-				'items': items
-			})
+			'name': 'Generic Appointment Type charge',
+			'items': items
+		})
 
 		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 2),
 			invoice=1, appointment_type=appointment_type.name)
@@ -104,21 +107,24 @@
 		self.assertTrue(sales_invoice_name)
 
 	def test_appointment_cancel(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 1)
 		appointment = create_appointment(patient, practitioner, nowdate())
-		fee_validity = frappe.db.get_value('Fee Validity Reference', {'appointment': appointment.name}, 'parent')
+		fee_validity = frappe.db.get_value('Fee Validity', {'patient': patient, 'practitioner': practitioner})
 		# fee validity created
 		self.assertTrue(fee_validity)
 
-		visited = frappe.db.get_value('Fee Validity', fee_validity, 'visited')
+		# first follow up appointment
+		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1))
+		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 1)
+
 		update_status(appointment.name, 'Cancelled')
 		# check fee validity updated
-		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), visited - 1)
+		self.assertEqual(frappe.db.get_value('Fee Validity', fee_validity, 'visited'), 0)
 
 		frappe.db.set_value('Healthcare Settings', None, 'enable_free_follow_ups', 0)
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
-		appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
+		appointment = create_appointment(patient, practitioner, add_days(nowdate(), 1), invoice=1)
 		update_status(appointment.name, 'Cancelled')
 		# check invoice cancelled
 		sales_invoice_name = frappe.db.get_value('Sales Invoice Item', {'reference_dn': appointment.name}, 'parent')
@@ -130,7 +136,7 @@
 			create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
 
 		frappe.db.sql("""delete from `tabInpatient Record`""")
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		patient = create_patient()
 		# Schedule Admission
 		ip_record = create_inpatient(patient)
@@ -138,7 +144,7 @@
 		ip_record.save(ignore_permissions = True)
 
 		# Admit
-		service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+		service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
 		admit_patient(ip_record, service_unit, now_datetime())
 
 		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit)
@@ -156,7 +162,7 @@
 			create_inpatient, get_healthcare_service_unit, mark_invoiced_inpatient_occupancy
 
 		frappe.db.sql("""delete from `tabInpatient Record`""")
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		patient = create_patient()
 		# Schedule Admission
 		ip_record = create_inpatient(patient)
@@ -164,10 +170,10 @@
 		ip_record.save(ignore_permissions = True)
 
 		# Admit
-		service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+		service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
 		admit_patient(ip_record, service_unit, now_datetime())
 
-		appointment_service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy for Appointment')
+		appointment_service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy for Appointment')
 		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=appointment_service_unit, save=0)
 		self.assertRaises(frappe.exceptions.ValidationError, appointment.save)
 
@@ -189,7 +195,7 @@
 		assert payment_required is True
 
 	def test_sales_invoice_should_be_generated_for_new_patient_appointment(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		frappe.db.set_value('Healthcare Settings', None, 'automate_appointment_invoicing', 1)
 		invoice_count = frappe.db.count('Sales Invoice')
 
@@ -200,10 +206,10 @@
 		assert new_invoice_count == invoice_count + 1
 
 	def test_patient_appointment_should_consider_permissions_while_fetching_appointments(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
 		create_appointment(patient, practitioner, nowdate())
 
-		patient, medical_department, new_practitioner = create_healthcare_docs(practitioner_name='Dr. John')
+		patient, new_practitioner = create_healthcare_docs(id=5)
 		create_appointment(patient, new_practitioner, nowdate())
 
 		roles = [{"doctype": "Has Role", "role": "Physician"}]
@@ -220,41 +226,102 @@
 		appointments = frappe.get_list('Patient Appointment')
 		assert len(appointments) == 2
 
-def create_healthcare_docs(practitioner_name=None):
-	if not practitioner_name:
-		practitioner_name = '_Test Healthcare Practitioner'
+	def test_overlap_appointment(self):
+		from erpnext.healthcare.doctype.patient_appointment.patient_appointment import OverlapError
+		patient, practitioner = create_healthcare_docs(id=1)
+		patient_1, practitioner_1 = create_healthcare_docs(id=2)
+		service_unit = create_service_unit(id=0)
+		service_unit_1 = create_service_unit(id=1)
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit) # valid
 
-	patient = create_patient()
-	practitioner = frappe.db.exists('Healthcare Practitioner', practitioner_name)
-	medical_department = frappe.db.exists('Medical Department', '_Test Medical Department')
+		# patient and practitioner cannot have overlapping appointments
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=service_unit_1, save=0) # diff service unit
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner, nowdate(), save=0) # with no service unit link
+		self.assertRaises(OverlapError, appointment.save)
 
-	if not medical_department:
-		medical_department = frappe.new_doc('Medical Department')
-		medical_department.department = '_Test Medical Department'
-		medical_department.save(ignore_permissions=True)
-		medical_department = medical_department.name
+		# patient cannot have overlapping appointments with other practitioners
+		appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner_1, nowdate(), service_unit=service_unit_1, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient, practitioner_1, nowdate(), save=0)
+		self.assertRaises(OverlapError, appointment.save)
 
-	if not practitioner:
-		practitioner = frappe.new_doc('Healthcare Practitioner')
-		practitioner.first_name = practitioner_name
-		practitioner.gender = 'Female'
-		practitioner.department = medical_department
-		practitioner.op_consulting_charge = 500
-		practitioner.inpatient_visit_charge = 500
-		practitioner.save(ignore_permissions=True)
-		practitioner = practitioner.name
+		# practitioner cannot have overlapping appointments with other patients
+		appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient_1, practitioner, nowdate(), service_unit=service_unit_1, save=0)
+		self.assertRaises(OverlapError, appointment.save)
+		appointment = create_appointment(patient_1, practitioner, nowdate(), save=0)
+		self.assertRaises(OverlapError, appointment.save)
 
-	return patient, medical_department, practitioner
+	def test_service_unit_capacity(self):
+		from erpnext.healthcare.doctype.patient_appointment.patient_appointment import MaximumCapacityError, OverlapError
+		practitioner = create_practitioner()
+		capacity = 3
+		overlap_service_unit_type = create_service_unit_type(id=10, allow_appointments=1, overlap_appointments=1)
+		overlap_service_unit = create_service_unit(id=100, service_unit_type=overlap_service_unit_type, service_unit_capacity=capacity)
 
-def create_patient():
-	patient = frappe.db.exists('Patient', '_Test Patient')
-	if not patient:
-		patient = frappe.new_doc('Patient')
-		patient.first_name = '_Test Patient'
-		patient.sex = 'Female'
-		patient.save(ignore_permissions=True)
-		patient = patient.name
-	return patient
+		for i in range(0, capacity):
+			patient = create_patient(id=i)
+			create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit) # valid
+			appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0) # overlap
+			self.assertRaises(OverlapError, appointment.save)
+
+		patient = create_patient(id=capacity)
+		appointment = create_appointment(patient, practitioner, nowdate(), service_unit=overlap_service_unit, save=0)
+		self.assertRaises(MaximumCapacityError, appointment.save)
+
+
+def create_healthcare_docs(id=0):
+	patient = create_patient(id)
+	practitioner = create_practitioner(id)
+
+	return patient, practitioner
+
+
+def create_patient(id=0):
+	if frappe.db.exists('Patient', {'firstname':f'_Test Patient {str(id)}'}):
+		patient = frappe.db.get_value('Patient', {'first_name': f'_Test Patient {str(id)}'}, ['name'])
+		return patient
+
+	patient = frappe.new_doc('Patient')
+	patient.first_name = f'_Test Patient {str(id)}'
+	patient.sex = 'Female'
+	patient.save(ignore_permissions=True)
+
+	return patient.name
+
+
+def create_medical_department(id=0):
+	if frappe.db.exists('Medical Department', f'_Test Medical Department {str(id)}'):
+		return f'_Test Medical Department {str(id)}'
+
+	medical_department = frappe.new_doc('Medical Department')
+	medical_department.department = f'_Test Medical Department {str(id)}'
+	medical_department.save(ignore_permissions=True)
+
+	return medical_department.name
+
+
+def create_practitioner(id=0, medical_department=None):
+	if frappe.db.exists('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}):
+		practitioner = frappe.db.get_value('Healthcare Practitioner', {'firstname':f'_Test Healthcare Practitioner {str(id)}'}, ['name'])
+		return practitioner
+
+	practitioner = frappe.new_doc('Healthcare Practitioner')
+	practitioner.first_name = f'_Test Healthcare Practitioner {str(id)}'
+	practitioner.gender = 'Female'
+	practitioner.department = medical_department or create_medical_department(id)
+	practitioner.op_consulting_charge = 500
+	practitioner.inpatient_visit_charge = 500
+	practitioner.save(ignore_permissions=True)
+
+	return practitioner.name
+
 
 def create_encounter(appointment):
 	if appointment:
@@ -267,8 +334,10 @@
 		encounter.company = appointment.company
 		encounter.save()
 		encounter.submit()
+
 		return encounter
 
+
 def create_appointment(patient, practitioner, appointment_date, invoice=0, procedure_template=0,
 	service_unit=None, appointment_type=None, save=1, department=None):
 	item = create_healthcare_service_items()
@@ -281,6 +350,7 @@
 	appointment.appointment_date = appointment_date
 	appointment.company = '_Test Company'
 	appointment.duration = 15
+
 	if service_unit:
 		appointment.service_unit = service_unit
 	if invoice:
@@ -291,11 +361,14 @@
 		appointment.procedure_template = create_clinical_procedure_template().get('name')
 	if save:
 		appointment.save(ignore_permissions=True)
+
 	return appointment
 
+
 def create_healthcare_service_items():
 	if frappe.db.exists('Item', 'HLC-SI-001'):
 		return 'HLC-SI-001'
+
 	item = frappe.new_doc('Item')
 	item.item_code = 'HLC-SI-001'
 	item.item_name = 'Consulting Charges'
@@ -303,11 +376,14 @@
 	item.is_stock_item = 0
 	item.stock_uom = 'Nos'
 	item.save()
+
 	return item.name
 
+
 def create_clinical_procedure_template():
 	if frappe.db.exists('Clinical Procedure Template', 'Knee Surgery and Rehab'):
 		return frappe.get_doc('Clinical Procedure Template', 'Knee Surgery and Rehab')
+
 	template = frappe.new_doc('Clinical Procedure Template')
 	template.template = 'Knee Surgery and Rehab'
 	template.item_code = 'Knee Surgery and Rehab'
@@ -316,8 +392,10 @@
 	template.description = 'Knee Surgery and Rehab'
 	template.rate = 50000
 	template.save()
+
 	return template
 
+
 def create_appointment_type(args=None):
 	if not args:
 		args =  frappe.local.form_dict
@@ -330,9 +408,9 @@
 	else:
 		item = create_healthcare_service_items()
 		items = [{
-				'medical_department': '_Test Medical Department',
-				'op_consulting_charge_item': item,
-				'op_consulting_charge': 200
+			'medical_department': args.get('medical_department') or '_Test Medical Department',
+			'op_consulting_charge_item': item,
+			'op_consulting_charge': 200
 		}]
 		return frappe.get_doc({
 			'doctype': 'Appointment Type',
@@ -356,3 +434,30 @@
 			"roles": roles,
 		}).insert()
 	return user
+
+
+def create_service_unit_type(id=0, allow_appointments=1, overlap_appointments=0):
+	if frappe.db.exists('Healthcare Service Unit Type', f'_Test Service Unit Type {str(id)}'):
+		return f'_Test Service Unit Type {str(id)}'
+
+	service_unit_type = frappe.new_doc('Healthcare Service Unit Type')
+	service_unit_type.service_unit_type = f'_Test Service Unit Type {str(id)}'
+	service_unit_type.allow_appointments = allow_appointments
+	service_unit_type.overlap_appointments = overlap_appointments
+	service_unit_type.save(ignore_permissions=True)
+
+	return service_unit_type.name
+
+
+def create_service_unit(id=0, service_unit_type=None, service_unit_capacity=0):
+	if frappe.db.exists('Healthcare Service Unit', f'_Test Service Unit {str(id)}'):
+		return f'_Test service_unit {str(id)}'
+
+	service_unit = frappe.new_doc('Healthcare Service Unit')
+	service_unit.is_group = 0
+	service_unit.healthcare_service_unit_name= f'_Test Service Unit {str(id)}'
+	service_unit.service_unit_type = service_unit_type or create_service_unit_type(id)
+	service_unit.service_unit_capacity = service_unit_capacity
+	service_unit.save(ignore_permissions=True)
+
+	return service_unit.name
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
index aaeaa69..c346626 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.js
@@ -185,7 +185,42 @@
 			};
 			frm.set_value(values);
 		}
-	}
+	},
+
+	get_applicable_treatment_plans: function(frm) {
+		frappe.call({
+			method: 'get_applicable_treatment_plans',
+			doc: frm.doc,
+			args: {'encounter': frm.doc},
+			freeze: true,
+			freeze_message: __('Fetching Treatment Plans'),
+			callback: function() {
+				new frappe.ui.form.MultiSelectDialog({
+					doctype: "Treatment Plan Template",
+					target: this.cur_frm,
+					setters: {
+						medical_department: "",
+					},
+					action(selections) {
+						frappe.call({
+							method: 'set_treatment_plans',
+							doc: frm.doc,
+							args: selections,
+						}).then(() => {
+							frm.refresh_field('drug_prescription');
+							frm.refresh_field('procedure_prescription');
+							frm.refresh_field('lab_test_prescription');
+							frm.refresh_field('therapies');
+						});
+						cur_dialog.hide();
+					}
+				});
+
+
+			}
+		});
+	},
+
 });
 
 var schedule_inpatient = function(frm) {
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
index b646ff9..994597d 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.json
@@ -31,6 +31,7 @@
   "sb_symptoms",
   "symptoms",
   "symptoms_in_print",
+  "get_applicable_treatment_plans",
   "physical_examination",
   "diagnosis",
   "diagnosis_in_print",
@@ -324,11 +325,17 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "depends_on": "eval:doc.patient",
+   "fieldname": "get_applicable_treatment_plans",
+   "fieldtype": "Button",
+   "label": "Get Applicable Treatment Plans"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-30 10:39:00.783119",
+ "modified": "2021-07-27 11:39:12.347704",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Patient Encounter",
@@ -358,4 +365,4 @@
  "title_field": "title",
  "track_changes": 1,
  "track_seen": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
index 2b3029e..7a745ae 100644
--- a/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/patient_encounter.py
@@ -10,6 +10,7 @@
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
 
+
 class PatientEncounter(Document):
 	def validate(self):
 		self.set_title()
@@ -33,6 +34,85 @@
 		self.title = _('{0} with {1}').format(self.patient_name or self.patient,
 			self.practitioner_name or self.practitioner)[:100]
 
+	@frappe.whitelist()
+	@staticmethod
+	def get_applicable_treatment_plans(encounter):
+		patient = frappe.get_doc('Patient', encounter['patient'])
+
+		plan_filters = {}
+		plan_filters['name'] = ['in', []]
+
+		age = patient.age
+		if age:
+			plan_filters['patient_age_from'] = ['<=', age.years]
+			plan_filters['patient_age_to'] = ['>=', age.years]
+
+		gender = patient.sex
+		if gender:
+			plan_filters['gender'] = ['in', [gender, None]]
+
+		diagnosis = encounter.get('diagnosis')
+		if diagnosis:
+			diagnosis = [_diagnosis['diagnosis'] for _diagnosis in encounter['diagnosis']]
+			filters = [
+				['diagnosis', 'in', diagnosis],
+				['parenttype', '=', 'Treatment Plan Template'],
+			]
+			diagnosis = frappe.get_list('Patient Encounter Diagnosis', filters=filters, fields='*')
+			plan_names = [_diagnosis['parent'] for _diagnosis in diagnosis]
+			plan_filters['name'][1].extend(plan_names)
+
+		symptoms = encounter.get('symptoms')
+		if symptoms:
+			symptoms = [symptom['complaint'] for symptom in encounter['symptoms']]
+			filters = [
+				['complaint', 'in', symptoms],
+				['parenttype', '=', 'Treatment Plan Template'],
+			]
+			symptoms = frappe.get_list('Patient Encounter Symptom', filters=filters, fields='*')
+			plan_names = [symptom['parent'] for symptom in symptoms]
+			plan_filters['name'][1].extend(plan_names)
+
+		if not plan_filters['name'][1]:
+			plan_filters.pop('name')
+
+		plans = frappe.get_list('Treatment Plan Template', fields='*', filters=plan_filters)
+
+		return plans
+
+	@frappe.whitelist()
+	def set_treatment_plans(self, treatment_plans=None):
+		for treatment_plan in treatment_plans:
+			self.set_treatment_plan(treatment_plan)
+
+	def set_treatment_plan(self, plan):
+		plan_items = frappe.get_list('Treatment Plan Template Item', filters={'parent': plan}, fields='*')
+		for plan_item in plan_items:
+			self.set_treatment_plan_item(plan_item)
+
+		drugs = frappe.get_list('Drug Prescription', filters={'parent': plan}, fields='*')
+		for drug in drugs:
+			self.append('drug_prescription', drug)
+
+		self.save()
+
+	def set_treatment_plan_item(self, plan_item):
+		if plan_item.type == 'Clinical Procedure Template':
+			self.append('procedure_prescription', {
+				'procedure': plan_item.template
+			})
+
+		if plan_item.type == 'Lab Test Template':
+			self.append('lab_test_prescription', {
+				'lab_test_code': plan_item.template
+			})
+
+		if plan_item.type == 'Therapy Type':
+			self.append('therapies', {
+				'therapy_type': plan_item.template
+			})
+
+
 @frappe.whitelist()
 def make_ip_medication_order(source_name, target_doc=None):
 	def set_missing_values(source, target):
diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js
deleted file mode 100644
index 1baabf7..0000000
--- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient Encounter", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Patient Encounter
-		() => frappe.tests.make('Patient Encounter', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py
index f5df152..9697682 100644
--- a/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py
+++ b/erpnext/healthcare/doctype/patient_encounter/test_patient_encounter.py
@@ -4,5 +4,82 @@
 from __future__ import unicode_literals
 import unittest
 
+import frappe
+from erpnext.healthcare.doctype.patient_encounter.patient_encounter import PatientEncounter
+
+
 class TestPatientEncounter(unittest.TestCase):
-	pass
+	def setUp(self):
+		try:
+			gender_m = frappe.get_doc({
+				'doctype': 'Gender',
+				'gender': 'MALE'
+			}).insert()
+			gender_f = frappe.get_doc({
+				'doctype': 'Gender',
+				'gender': 'FEMALE'
+			}).insert()
+		except frappe.exceptions.DuplicateEntryError:
+			gender_m = frappe.get_doc({
+				'doctype': 'Gender',
+				'gender': 'MALE'
+			})
+			gender_f = frappe.get_doc({
+				'doctype': 'Gender',
+				'gender': 'FEMALE'
+			})
+
+		self.patient_male = frappe.get_doc({
+			'doctype': 'Patient',
+			'first_name': 'John',
+			'sex': gender_m.gender,
+		}).insert()
+		self.patient_female = frappe.get_doc({
+			'doctype': 'Patient',
+			'first_name': 'Curie',
+			'sex': gender_f.gender,
+		}).insert()
+		self.practitioner = frappe.get_doc({
+			'doctype': 'Healthcare Practitioner',
+			'first_name': 'Doc',
+			'sex': 'MALE',
+		}).insert()
+		try:
+			self.care_plan_male = frappe.get_doc({
+				'doctype': 'Treatment Plan Template',
+				'template_name': 'test plan - m',
+				'gender': gender_m.gender,
+			}).insert()
+			self.care_plan_female = frappe.get_doc({
+				'doctype': 'Treatment Plan Template',
+				'template_name': 'test plan - f',
+				'gender': gender_f.gender,
+			}).insert()
+		except frappe.exceptions.DuplicateEntryError:
+			self.care_plan_male = frappe.get_doc({
+				'doctype': 'Treatment Plan Template',
+				'template_name': 'test plan - m',
+				'gender': gender_m.gender,
+			})
+			self.care_plan_female = frappe.get_doc({
+				'doctype': 'Treatment Plan Template',
+				'template_name': 'test plan - f',
+				'gender': gender_f.gender,
+			})
+
+	def test_treatment_plan_template_filter(self):
+		encounter = frappe.get_doc({
+			'doctype': 'Patient Encounter',
+			'patient': self.patient_male.name,
+			'practitioner': self.practitioner.name,
+		}).insert()
+		plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict())
+		self.assertEqual(plans[0]['name'], self.care_plan_male.template_name)
+
+		encounter = frappe.get_doc({
+			'doctype': 'Patient Encounter',
+			'patient': self.patient_female.name,
+			'practitioner': self.practitioner.name,
+		}).insert()
+		plans = PatientEncounter.get_applicable_treatment_plans(encounter.as_dict())
+		self.assertEqual(plans[0]['name'], self.care_plan_female.template_name)
diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
index 63b0085..9e0d3c3 100644
--- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
+++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
@@ -18,7 +18,7 @@
 	def validate_submittable_doctypes(self):
 		for entry in self.custom_doctypes:
 			if not cint(frappe.db.get_value('DocType', entry.document_type, 'is_submittable')):
-				msg = _('Row #{0}: Document Type {1} is not submittable. ').format(
+				msg = _('Row #{0}: Document Type {1} is not submittable.').format(
 					entry.idx, frappe.bold(entry.document_type))
 				msg += _('Patient Medical Record can only be created for submittable document types.')
 				frappe.throw(msg)
@@ -116,12 +116,12 @@
 		fieldname = entry.get('fieldname')
 		if entry.get('fieldtype') == 'Table' and doc.get(fieldname):
 			formatted_value = get_formatted_value_for_table_field(doc.get(fieldname), meta.get_field(fieldname))
-			subject += frappe.bold(_(entry.get('label')) + ': ') + '<br>' + cstr(formatted_value) + '<br>'
+			subject += frappe.bold(_(entry.get('label')) + ':') + '<br>' + cstr(formatted_value) + '<br>'
 
 		else:
 			if doc.get(fieldname):
 				formatted_value = format_value(doc.get(fieldname), meta.get_field(fieldname), doc)
-				subject += frappe.bold(_(entry.get('label')) + ': ') + cstr(formatted_value) + '<br>'
+				subject += frappe.bold(_(entry.get('label')) + ':') + cstr(formatted_value) + '<br>'
 
 	return subject
 
diff --git a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py
index 33119d8..9169ea6 100644
--- a/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py
+++ b/erpnext/healthcare/doctype/patient_history_settings/test_patient_history_settings.py
@@ -38,13 +38,12 @@
 		# tests for medical record creation of standard doctypes in test_patient_medical_record.py
 		patient = create_patient()
 		doc = create_doc(patient)
-
 		# check for medical record
 		medical_rec = frappe.db.exists("Patient Medical Record", {"status": "Open", "reference_name": doc.name})
 		self.assertTrue(medical_rec)
 
 		medical_rec = frappe.get_doc("Patient Medical Record", medical_rec)
-		expected_subject = "Date: {0}Rating: 3Feedback: Test Patient History Settings".format(
+		expected_subject = "Date:{0}Rating:3Feedback:Test Patient History Settings".format(
 			frappe.utils.format_date(getdate()))
 		self.assertEqual(strip_html(medical_rec.subject), expected_subject)
 		self.assertEqual(medical_rec.patient, patient)
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js
deleted file mode 100644
index 66dda09..0000000
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Patient Medical Record", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Patient Medical Record
-		() => frappe.tests.make('Patient Medical Record', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
index f8ccc8a..5b7d8d6 100644
--- a/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
+++ b/erpnext/healthcare/doctype/patient_medical_record/test_patient_medical_record.py
@@ -5,7 +5,7 @@
 import unittest
 import frappe
 from frappe.utils import nowdate
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_encounter, create_healthcare_docs, create_appointment, create_medical_department
 from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 class TestPatientMedicalRecord(unittest.TestCase):
@@ -15,7 +15,8 @@
 		make_pos_profile()
 
 	def test_medical_record(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
+		medical_department = create_medical_department()
 		appointment = create_appointment(patient, practitioner, nowdate(), invoice=1)
 		encounter = create_encounter(appointment)
 
diff --git a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js b/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js
deleted file mode 100644
index 32dac2c..0000000
--- a/erpnext/healthcare/doctype/practitioner_schedule/test_practitioner_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Practitioner Schedule", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Practitioner Schedule
-		() => frappe.tests.make('Practitioner Schedule', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js b/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js
deleted file mode 100644
index 009614f..0000000
--- a/erpnext/healthcare/doctype/prescription_dosage/test_prescription_dosage.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Prescription Dosage", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Prescription Dosage
-		() => frappe.tests.make('Prescription Dosage', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js b/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js
deleted file mode 100644
index 4971e79..0000000
--- a/erpnext/healthcare/doctype/prescription_duration/test_prescription_duration.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Prescription Duration", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Prescription Duration
-		() => frappe.tests.make('Prescription Duration', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js b/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js
deleted file mode 100644
index 2b4aed7..0000000
--- a/erpnext/healthcare/doctype/sample_collection/test_sample_collection.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sample Collection", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Sample Collection
-		() => frappe.tests.make('Sample Collection', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js b/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js
deleted file mode 100644
index c2cf406..0000000
--- a/erpnext/healthcare/doctype/sensitivity/test_sensitivity.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sensitivity", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Sensitivity
-		() => frappe.tests.make('Sensitivity', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
index 113fa51..983fba9 100644
--- a/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/test_therapy_plan.py
@@ -8,11 +8,13 @@
 from frappe.utils import getdate, flt, nowdate
 from erpnext.healthcare.doctype.therapy_type.test_therapy_type import create_therapy_type
 from erpnext.healthcare.doctype.therapy_plan.therapy_plan import make_therapy_session, make_sales_invoice
-from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import create_healthcare_docs, create_patient, create_appointment
+from erpnext.healthcare.doctype.patient_appointment.test_patient_appointment import \
+	create_healthcare_docs, create_patient, create_appointment, create_medical_department
 
 class TestTherapyPlan(unittest.TestCase):
 	def test_creation_on_encounter_submission(self):
-		patient, medical_department, practitioner = create_healthcare_docs()
+		patient, practitioner = create_healthcare_docs()
+		medical_department = create_medical_department()
 		encounter = create_encounter(patient, medical_department, practitioner)
 		self.assertTrue(frappe.db.exists('Therapy Plan', encounter.therapy_plan))
 
@@ -28,8 +30,9 @@
 		frappe.get_doc(session).submit()
 		self.assertEqual(frappe.db.get_value('Therapy Plan', plan.name, 'status'), 'Completed')
 
-		patient, medical_department, practitioner = create_healthcare_docs()
-		appointment = create_appointment(patient, practitioner, nowdate())
+		patient, practitioner = create_healthcare_docs()
+		appointment = create_appointment(patient, practitioner, nowdate())		
+
 		session = make_therapy_session(plan.name, plan.patient, 'Basic Rehab', '_Test Company', appointment.name)
 		session = frappe.get_doc(session)
 		session.submit()
diff --git a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
index a5dad29..80fc83f 100644
--- a/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
+++ b/erpnext/healthcare/doctype/therapy_type/test_therapy_type.py
@@ -34,7 +34,8 @@
 		})
 		therapy_type.save()
 	else:
-		therapy_type = frappe.get_doc('Therapy Type', 'Basic Rehab')
+		therapy_type = frappe.get_doc('Therapy Type', therapy_type)
+
 	return therapy_type
 
 def create_exercise_type():
@@ -47,4 +48,7 @@
 			'description': 'Squat and Rise'
 		})
 		exercise_type.save()
+	else:
+		exercise_type = frappe.get_doc('Exercise Type', exercise_type)
+
 	return exercise_type
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/healthcare/doctype/treatment_plan_template/__init__.py
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_records.json b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json
new file mode 100644
index 0000000..d661b43
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/test_records.json
@@ -0,0 +1,7 @@
+[
+  {
+    "doctype": "Treatment Plan Template",
+    "template_name": "Chemo",
+    "patient_age_from": 21
+  }
+]
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py
new file mode 100644
index 0000000..21ede71
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/test_treatment_plan_template.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestTreatmentPlanTemplate(unittest.TestCase):
+	pass
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js
new file mode 100644
index 0000000..986c3cb
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.js
@@ -0,0 +1,14 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Treatment Plan Template', {
+	refresh: function (frm) {
+		frm.set_query('type', 'items', function () {
+			return {
+				filters: {
+					'name': ['in', ['Lab Test Template', 'Clinical Procedure Template', 'Therapy Type']],
+				}
+			};
+		});
+	},
+});
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json
new file mode 100644
index 0000000..85a312f
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.json
@@ -0,0 +1,189 @@
+{
+ "actions": [],
+ "autoname": "field:template_name",
+ "creation": "2021-06-10 10:14:17.901273",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "section_break_1",
+  "template_name",
+  "description",
+  "practitioners",
+  "disabled",
+  "column_break_1",
+  "medical_department",
+  "goal",
+  "order_group",
+  "section_break_8",
+  "patient_age_from",
+  "complaints",
+  "gender",
+  "column_break_12",
+  "patient_age_to",
+  "diagnosis",
+  "plan_items_section",
+  "items",
+  "drugs"
+ ],
+ "fields": [
+  {
+   "fieldname": "section_break_1",
+   "fieldtype": "Section Break",
+   "label": "Plan Details"
+  },
+  {
+   "fieldname": "medical_department",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Medical Department",
+   "options": "Medical Department"
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "label": "Description"
+  },
+  {
+   "fieldname": "goal",
+   "fieldtype": "Small Text",
+   "label": "Goal"
+  },
+  {
+   "fieldname": "practitioners",
+   "fieldtype": "Table MultiSelect",
+   "label": "Practitioners",
+   "options": "Treatment Plan Template Practitioner"
+  },
+  {
+   "fieldname": "order_group",
+   "fieldtype": "Link",
+   "label": "Order Group",
+   "options": "Patient Encounter",
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_8",
+   "fieldtype": "Section Break",
+   "label": "Plan Conditions"
+  },
+  {
+   "fieldname": "template_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Template Name",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "patient_age_from",
+   "fieldtype": "Int",
+   "label": "Patient Age From",
+   "non_negative": 1
+  },
+  {
+   "fieldname": "column_break_12",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "patient_age_to",
+   "fieldtype": "Int",
+   "label": "Patient Age To",
+   "non_negative": 1
+  },
+  {
+   "fieldname": "gender",
+   "fieldtype": "Link",
+   "label": "Gender",
+   "options": "Gender"
+  },
+  {
+   "fieldname": "complaints",
+   "fieldtype": "Table MultiSelect",
+   "label": "Complaints",
+   "options": "Patient Encounter Symptom"
+  },
+  {
+   "fieldname": "diagnosis",
+   "fieldtype": "Table MultiSelect",
+   "label": "Diagnosis",
+   "options": "Patient Encounter Diagnosis"
+  },
+  {
+   "fieldname": "plan_items_section",
+   "fieldtype": "Section Break",
+   "label": "Plan Items"
+  },
+  {
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "label": "Items",
+   "options": "Treatment Plan Template Item"
+  },
+  {
+   "fieldname": "drugs",
+   "fieldtype": "Table",
+   "label": "Drugs",
+   "options": "Drug Prescription"
+  },
+  {
+   "default": "0",
+   "fieldname": "disabled",
+   "fieldtype": "Check",
+   "label": "Disabled"
+  },
+  {
+   "fieldname": "column_break_1",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-18 02:41:58.354296",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Treatment Plan Template",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Physician",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Healthcare Administrator",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "template_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py
new file mode 100644
index 0000000..a92e266
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class TreatmentPlanTemplate(Document):
+	def validate(self):
+		self.validate_age()
+
+	def validate_age(self):
+		if self.patient_age_from and self.patient_age_from < 0:
+			frappe.throw(_('Patient Age From cannot be less than 0'))
+		if self.patient_age_to and self.patient_age_to < 0:
+			frappe.throw(_('Patient Age To cannot be less than 0'))
+		if self.patient_age_to and self.patient_age_from and \
+			self.patient_age_to < self.patient_age_from:
+			frappe.throw(_('Patient Age To cannot be less than Patient Age From'))
diff --git a/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js
new file mode 100644
index 0000000..7ab31df
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template/treatment_plan_template_list.js
@@ -0,0 +1,10 @@
+frappe.listview_settings['Treatment Plan Template'] = {
+	get_indicator: function(doc) {
+		var colors = {
+			1: 'gray',
+			0: 'blue',
+		};
+		let label  = doc.disabled == 1 ? 'Disabled' : 'Enabled';
+		return [__(label), colors[doc.disabled], 'disable,=,' + doc.disabled];
+	}
+};
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/healthcare/doctype/treatment_plan_template_item/__init__.py
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json
new file mode 100644
index 0000000..20a9d67
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.json
@@ -0,0 +1,55 @@
+{
+ "actions": [],
+ "creation": "2021-06-10 11:47:29.194795",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "type",
+  "template",
+  "qty",
+  "instructions"
+ ],
+ "fields": [
+  {
+   "fieldname": "type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Type",
+   "options": "DocType",
+   "reqd": 1
+  },
+  {
+   "fieldname": "template",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Template",
+   "options": "type",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "qty",
+   "fieldtype": "Int",
+   "label": "Qty"
+  },
+  {
+   "fieldname": "instructions",
+   "fieldtype": "Small Text",
+   "in_list_view": 1,
+   "label": "Instructions"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-17 11:19:03.515441",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Treatment Plan Template Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py
new file mode 100644
index 0000000..5f58b06
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_item/treatment_plan_template_item.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class TreatmentPlanTemplateItem(Document):
+	pass
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/healthcare/doctype/treatment_plan_template_practitioner/__init__.py
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json
new file mode 100644
index 0000000..04da387
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.json
@@ -0,0 +1,32 @@
+{
+ "actions": [],
+ "creation": "2021-06-10 10:37:56.669416",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "practitioner"
+ ],
+ "fields": [
+  {
+   "fieldname": "practitioner",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Practitioner",
+   "options": "Healthcare Practitioner",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-11 16:05:06.733299",
+ "modified_by": "Administrator",
+ "module": "Healthcare",
+ "name": "Treatment Plan Template Practitioner",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py
new file mode 100644
index 0000000..6d34568
--- /dev/null
+++ b/erpnext/healthcare/doctype/treatment_plan_template_practitioner/treatment_plan_template_practitioner.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class TreatmentPlanTemplatePractitioner(Document):
+	pass
diff --git a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js b/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js
deleted file mode 100644
index f4ab446..0000000
--- a/erpnext/healthcare/doctype/vital_signs/test_vital_signs.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Vital Signs", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Vital Signs
-		() => frappe.tests.make('Vital Signs', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
index 56c3c13..0aa8f9a 100644
--- a/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
+++ b/erpnext/healthcare/module_onboarding/healthcare/healthcare.json
@@ -10,7 +10,7 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/healthcare",
  "idx": 0,
  "is_complete": 0,
- "modified": "2020-07-08 14:06:19.512946",
+ "modified": "2021-01-30 19:22:20.273766",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare",
diff --git a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json
index c45a347..3f25a9d 100644
--- a/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json
+++ b/erpnext/healthcare/onboarding_step/create_healthcare_practitioner/create_healthcare_practitioner.json
@@ -5,14 +5,14 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-26 23:16:31.965521",
+ "modified": "2021-01-30 12:02:22.849260",
  "modified_by": "Administrator",
  "name": "Create Healthcare Practitioner",
  "owner": "Administrator",
  "reference_document": "Healthcare Practitioner",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create Healthcare Practitioner",
  "validate_action": 1
diff --git a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
index 77bc5bd..b46bb15 100644
--- a/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
+++ b/erpnext/healthcare/onboarding_step/create_patient/create_patient.json
@@ -5,14 +5,14 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:26:24.023418",
- "modified_by": "Administrator",
+ "modified": "2021-01-30 00:09:28.786428",
+ "modified_by": "ruchamahabal2@gmail.com",
  "name": "Create Patient",
  "owner": "Administrator",
  "reference_document": "Patient",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create Patient",
  "validate_action": 1
diff --git a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
index 65980ef..7ce122d 100644
--- a/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
+++ b/erpnext/healthcare/onboarding_step/create_practitioner_schedule/create_practitioner_schedule.json
@@ -5,14 +5,14 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:27:09.437825",
- "modified_by": "Administrator",
+ "modified": "2021-01-30 00:09:28.794602",
+ "modified_by": "ruchamahabal2@gmail.com",
  "name": "Create Practitioner Schedule",
  "owner": "Administrator",
  "reference_document": "Practitioner Schedule",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create Practitioner Schedule",
  "validate_action": 1
diff --git a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
index 697b761..dfe9f71 100644
--- a/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
+++ b/erpnext/healthcare/onboarding_step/explore_clinical_procedure_templates/explore_clinical_procedure_templates.json
@@ -5,14 +5,14 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-26 23:10:24.504030",
+ "modified": "2021-01-30 19:22:08.257160",
  "modified_by": "Administrator",
  "name": "Explore Clinical Procedure Templates",
  "owner": "Administrator",
  "reference_document": "Clinical Procedure Template",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Explore Clinical Procedure Templates",
  "validate_action": 1
diff --git a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
index b2d5aef..2d952f3 100644
--- a/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
+++ b/erpnext/healthcare/onboarding_step/explore_healthcare_settings/explore_healthcare_settings.json
@@ -5,14 +5,14 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 1,
  "is_skipped": 0,
- "modified": "2020-05-26 23:10:24.507648",
+ "modified": "2021-01-30 19:22:07.275735",
  "modified_by": "Administrator",
  "name": "Explore Healthcare Settings",
  "owner": "Administrator",
  "reference_document": "Healthcare Settings",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Explore Healthcare Settings",
  "validate_action": 1
diff --git a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json
index fa4c903..baa8358 100644
--- a/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json
+++ b/erpnext/healthcare/onboarding_step/introduction_to_healthcare_practitioner/introduction_to_healthcare_practitioner.json
@@ -6,14 +6,14 @@
  "field": "schedule",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-26 22:07:07.482530",
- "modified_by": "Administrator",
+ "modified": "2021-01-30 00:09:28.807129",
+ "modified_by": "ruchamahabal2@gmail.com",
  "name": "Introduction to Healthcare Practitioner",
  "owner": "Administrator",
  "reference_document": "Healthcare Practitioner",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Introduction to Healthcare Practitioner",
  "validate_action": 0
diff --git a/erpnext/healthcare/page/patient_history/patient_history.css b/erpnext/healthcare/page/patient_history/patient_history.css
index 1bb5891..74b5e7e 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.css
+++ b/erpnext/healthcare/page/patient_history/patient_history.css
@@ -9,6 +9,26 @@
 	cursor: pointer;
 }
 
+.patient-image-container {
+	margin-top: 17px;
+  }
+
+.patient-image {
+	display: inline-block;
+	width: 100%;
+	height: 0;
+	padding: 50% 0px;
+	background-size: cover;
+	background-repeat: no-repeat;
+	background-position: center center;
+	border-radius: 4px;
+}
+
+.patient-name {
+	font-size: 20px;
+	margin-top: 25px;
+}
+
 .medical_record-label {
 	max-width: 100px;
 	margin-bottom: -4px;
@@ -19,19 +39,19 @@
 }
 
 .date-indicator {
-    background:none;
-    font-size:12px;
-    vertical-align:middle;
-    font-weight:bold;
-    color:#6c7680;
+	background:none;
+	font-size:12px;
+	vertical-align:middle;
+	font-weight:bold;
+	color:#6c7680;
 }
 .date-indicator::after {
-    margin:0 -4px 0 12px;
-    content:'';
-    display:inline-block;
-    height:8px;
-    width:8px;
-    border-radius:8px;
+	margin:0 -4px 0 12px;
+	content:'';
+	display:inline-block;
+	height:8px;
+	width:8px;
+	border-radius:8px;
 	background: #d1d8dd;
 }
 
diff --git a/erpnext/healthcare/page/patient_history/patient_history.html b/erpnext/healthcare/page/patient_history/patient_history.html
index f170655..d16b386 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.html
+++ b/erpnext/healthcare/page/patient_history/patient_history.html
@@ -1,26 +1,18 @@
-<div class="col-sm-12">
-	<div class="col-sm-3">
-	<p class="patient" style="margin: auto; max-width: 300px; margin-bottom: 20px;"></p>
-	<div class="patient_details" style="z-index=0"></div>
+<div class="row patient-documents">
+	<div class="col-sm-12">
+		<div class="col-sm-12 show_chart_btns" align="center">
+		</div>
+		<div id="chart" class="col-sm-12 patient_vital_charts">
+		</div>
 	</div>
-	<div class="col-sm-9 patient_documents">
-		<div class="col-sm-12">
-			<div class="col-sm-12 show_chart_btns" align="center">
-			</div>
-			<div id="chart" class="col-sm-12 patient_vital_charts">
-			</div>
-		</div>
-		<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
-		<div class="row">
-			<div class="col-sm-12 d-flex">
-				<div class="patient-history-filter doctype-filter"></div>
-				<div class="patient-history-filter date-filter"></div>
-			</div>
-		</div>
-		<div class="col-sm-12 patient_documents_list">
-		</div>
-		<div class="col-sm-12 text-center py-3">
-			<a class="btn btn-sm btn-default btn-get-records" style="display:none">More..</a>
-		</div>
+	<div class="header-separator col-sm-12 d-flex border-bottom py-3" style="display:none"></div>
+	<div class="col-sm-12 d-flex">
+		<div class="patient-history-filter doctype-filter"></div>
+		<div class="patient-history-filter date-filter"></div>
+	</div>
+	<div class="col-sm-12 patient_documents_list">
+	</div>
+	<div class="col-sm-12 text-center py-3">
+		<a class="btn btn-sm btn-default btn-get-records" style="display:none">More..</a>
 	</div>
 </div>
diff --git a/erpnext/healthcare/page/patient_history/patient_history.js b/erpnext/healthcare/page/patient_history/patient_history.js
index 54343aa..bf947ca 100644
--- a/erpnext/healthcare/page/patient_history/patient_history.js
+++ b/erpnext/healthcare/page/patient_history/patient_history.js
@@ -1,403 +1,455 @@
 frappe.provide('frappe.patient_history');
 frappe.pages['patient_history'].on_page_load = function(wrapper) {
-	let me = this;
-	let page = frappe.ui.make_app_page({
+	frappe.ui.make_app_page({
 		parent: wrapper,
-		title: 'Patient History',
-		single_column: true
+		title: __('Patient History')
 	});
 
-	frappe.breadcrumbs.add('Healthcare');
-	let pid = '';
-	page.main.html(frappe.render_template('patient_history', {}));
-	page.main.find('.header-separator').hide();
-
-	let patient = frappe.ui.form.make_control({
-		parent: page.main.find('.patient'),
-		df: {
-			fieldtype: 'Link',
-			options: 'Patient',
-			fieldname: 'patient',
-			placeholder: __('Select Patient'),
-			only_select: true,
-			change: function() {
-				let patient_id = patient.get_value();
-				if (pid != patient_id && patient_id) {
-					me.start = 0;
-					me.page.main.find('.patient_documents_list').html('');
-					setup_filters(patient_id, me);
-					get_documents(patient_id, me);
-					show_patient_info(patient_id, me);
-					show_patient_vital_charts(patient_id, me, 'bp', 'mmHg', 'Blood Pressure');
-				}
-				pid = patient_id;
-			}
-		},
-	});
-	patient.refresh();
-
-	if (frappe.route_options) {
-		patient.set_value(frappe.route_options.patient);
-	}
-
-	this.page.main.on('click', '.btn-show-chart', function() {
-		let	btn_show_id = $(this).attr('data-show-chart-id'), pts = $(this).attr('data-pts');
-		let title = $(this).attr('data-title');
-		show_patient_vital_charts(patient.get_value(), me, btn_show_id, pts, title);
-	});
-
-	this.page.main.on('click', '.btn-more', function() {
-		let	doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
-		if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
-			me.page.main.find('.'+docname).hide();
-			me.page.main.find('.'+docname).parent().find('.document-html').show();
-		} else {
-			if (doctype && docname) {
-				let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date'];
-				frappe.call({
-					method: 'erpnext.healthcare.utils.render_doc_as_html',
-					args:{
-						doctype: doctype,
-						docname: docname,
-						exclude_fields: exclude
-					},
-					freeze: true,
-					callback: function(r) {
-						if (r.message) {
-							me.page.main.find('.' + docname).hide();
-
-							me.page.main.find('.' + docname).parent().find('.document-html').html(
-								`${r.message.html}
-									<div align='center'>
-										<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
-											data-doctype='${doctype}'
-											data-docname='${docname}'>
-										</a>
-									</div>
-								`);
-
-							me.page.main.find('.' + docname).parent().find('.document-html').show();
-							me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
-						}
-					}
-				});
-			}
-		}
-	});
-
-	this.page.main.on('click', '.btn-less', function() {
-		let docname = $(this).attr('data-docname');
-		me.page.main.find('.' + docname).parent().find('.document-id').show();
-		me.page.main.find('.' + docname).parent().find('.document-html').hide();
-	});
-	me.start = 0;
-	me.page.main.on('click', '.btn-get-records', function() {
-		get_documents(patient.get_value(), me);
+	let patient_history = new PatientHistory(wrapper);
+	$(wrapper).bind('show', ()=> {
+		patient_history.show();
 	});
 };
 
-let setup_filters = function(patient, me) {
-	$('.doctype-filter').empty();
-	frappe.xcall(
-		'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
-	).then(document_types => {
-		let doctype_filter = frappe.ui.form.make_control({
-			parent: $('.doctype-filter'),
+class PatientHistory {
+	constructor(wrapper) {
+		this.wrapper = $(wrapper);
+		this.page = wrapper.page;
+		this.sidebar = this.wrapper.find('.layout-side-section');
+		this.main_section = this.wrapper.find('.layout-main-section');
+		this.start = 0;
+	}
+
+	show() {
+		frappe.breadcrumbs.add('Healthcare');
+		this.sidebar.empty();
+
+		let me = this;
+		let patient = frappe.ui.form.make_control({
+			parent: me.sidebar,
 			df: {
-				fieldtype: 'MultiSelectList',
-				fieldname: 'document_type',
-				placeholder: __('Select Document Type'),
-				input_class: 'input-xs',
+				fieldtype: 'Link',
+				options: 'Patient',
+				fieldname: 'patient',
+				placeholder: __('Select Patient'),
+				only_select: true,
 				change: () => {
-					me.start = 0;
-					me.page.main.find('.patient_documents_list').html('');
-					get_documents(patient, me, doctype_filter.get_value(), date_range_field.get_value());
-				},
-				get_data: () => {
-					return document_types.map(document_type => {
-						return {
-							description: document_type,
-							value: document_type
-						};
-					});
-				},
+					me.patient_id = '';
+					if (me.patient_id != patient.get_value() && patient.get_value()) {
+						me.start = 0;
+						me.patient_id = patient.get_value();
+						me.make_patient_profile();
+					}
+				}
 			}
 		});
-		doctype_filter.refresh();
+		patient.refresh();
 
-		$('.date-filter').empty();
-		let date_range_field = frappe.ui.form.make_control({
-			df: {
-				fieldtype: 'DateRange',
-				fieldname: 'date_range',
-				placeholder: __('Date Range'),
-				input_class: 'input-xs',
-				change: () => {
-					let selected_date_range = date_range_field.get_value();
-					if (selected_date_range && selected_date_range.length === 2) {
+		if (frappe.route_options && !this.patient_id) {
+			patient.set_value(frappe.route_options.patient);
+			this.patient_id = frappe.route_options.patient;
+		}
+
+		this.sidebar.find('[data-fieldname="patient"]').append('<div class="patient-info"></div>');
+	}
+
+	make_patient_profile() {
+		this.page.set_title(__('Patient History'));
+		this.main_section.empty().append(frappe.render_template('patient_history'));
+		this.setup_filters();
+		this.setup_documents();
+		this.show_patient_info();
+		this.setup_buttons();
+		this.show_patient_vital_charts('bp', 'mmHg', 'Blood Pressure');
+	}
+
+	setup_filters() {
+		$('.doctype-filter').empty();
+		let me = this;
+
+		frappe.xcall(
+			'erpnext.healthcare.page.patient_history.patient_history.get_patient_history_doctypes'
+		).then(document_types => {
+			let doctype_filter = frappe.ui.form.make_control({
+				parent: $('.doctype-filter'),
+				df: {
+					fieldtype: 'MultiSelectList',
+					fieldname: 'document_type',
+					placeholder: __('Select Document Type'),
+					change: () => {
 						me.start = 0;
 						me.page.main.find('.patient_documents_list').html('');
-						get_documents(patient, me, doctype_filter.get_value(), selected_date_range);
-					}
+						this.setup_documents(doctype_filter.get_value(), date_range_field.get_value());
+					},
+					get_data: () => {
+						return document_types.map(document_type => {
+							return {
+								description: document_type,
+								value: document_type
+							};
+						});
+					},
 				}
-			},
-			parent: $('.date-filter')
+			});
+			doctype_filter.refresh();
+
+			$('.date-filter').empty();
+			let date_range_field = frappe.ui.form.make_control({
+				df: {
+					fieldtype: 'DateRange',
+					fieldname: 'date_range',
+					placeholder: __('Date Range'),
+					input_class: 'input-xs',
+					change: () => {
+						let selected_date_range = date_range_field.get_value();
+						if (selected_date_range && selected_date_range.length === 2) {
+							me.start = 0;
+							me.page.main.find('.patient_documents_list').html('');
+							this.setup_documents(doctype_filter.get_value(), date_range_field.get_value());
+						}
+					}
+				},
+				parent: $('.date-filter')
+			});
+			date_range_field.refresh();
 		});
-		date_range_field.refresh();
-	});
-};
+	}
 
-let get_documents = function(patient, me, document_types="", selected_date_range="") {
-	let filters = {
-		name: patient,
-		start: me.start,
-		page_length: 20
-	};
-	if (document_types)
-		filters['document_types'] = document_types;
-	if (selected_date_range)
-		filters['date_range'] = selected_date_range;
+	setup_documents(document_types="", selected_date_range="") {
+		let filters = {
+			name: this.patient_id,
+			start: this.start,
+			page_length: 20
+		};
+		if (document_types)
+			filters['document_types'] = document_types;
+		if (selected_date_range)
+			filters['date_range'] = selected_date_range;
 
-	frappe.call({
-		'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
-		args: filters,
-		callback: function(r) {
-			let data = r.message;
-			if (data.length) {
-				add_to_records(me, data);
-			} else {
-				me.page.main.find('.patient_documents_list').append(`
-					<div class='text-muted' align='center'>
-						<br><br>${__('No more records..')}<br><br>
-					</div>`);
-				me.page.main.find('.btn-get-records').hide();
+		let me = this;
+		frappe.call({
+			'method': 'erpnext.healthcare.page.patient_history.patient_history.get_feed',
+			args: filters,
+			callback: function(r) {
+				let data = r.message;
+				if (data.length) {
+					me.add_to_records(data);
+				} else {
+					me.page.main.find('.patient_documents_list').append(`
+						<div class='text-muted' align='center'>
+							<br><br>${__('No more records..')}<br><br>
+						</div>`);
+					me.page.main.find('.btn-get-records').hide();
+				}
 			}
-		}
-	});
-};
+		});
+	}
 
-let add_to_records = function(me, data) {
-	let details = "<ul class='nav nav-pills nav-stacked'>";
-	let i;
-	for (i=0; i<data.length; i++) {
-		if (data[i].reference_doctype) {
-			let label = '';
-			if (data[i].subject) {
-				label += "<br/>" + data[i].subject;
-			}
-			data[i] = add_date_separator(data[i]);
+	add_to_records(data) {
+		let details = "";
+		let i;
+		for (i=0; i<data.length; i++) {
+			if (data[i].reference_doctype) {
+				let label = '';
+				if (data[i].subject) {
+					label += "<br/>" + data[i].subject;
+				}
+				data[i] = this.add_date_separator(data[i]);
 
-			if (frappe.user_info(data[i].owner).image) {
-				data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
-			} else {
-				data[i].imgsrc = false;
-			}
+				if (frappe.user_info(data[i].owner).image) {
+					data[i].imgsrc = frappe.utils.get_file_link(frappe.user_info(data[i].owner).image);
+				} else {
+					data[i].imgsrc = false;
+				}
 
-			let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
-			time_line_heading += data[i].reference_doctype + " - " +
-				`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
-					${data[i].reference_name}
-				</a>`;
+				let time_line_heading = data[i].practitioner ? `${data[i].practitioner} ` : ``;
+				time_line_heading += data[i].reference_doctype + " - " +
+					`<a onclick="frappe.set_route('Form', '${data[i].reference_doctype}', '${data[i].reference_name}');">
+						${data[i].reference_name}
+					</a>`;
 
-			details += `
-				<li data-toggle='pill' class='patient_doc_menu'
-					data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
-					<div class='col-sm-12 d-flex border-bottom py-3'>`;
-
-			if (data[i].imgsrc) {
 				details += `
-					<span class='mr-3'>
-						<img class='avtar' src='${data[i].imgsrc}' width='32' height='32'></img>
-					</span>`;
-			} else {
-				details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
-					<div align='center' class='standard-image' style='background-color: #fafbfc;'>
-						${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
-					</div>
-				</span>`;
-			}
+					<div data-toggle='pill' class='patient_doc_menu'
+						data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
+						<div class='col-sm-12 d-flex border-bottom py-3'>`;
 
-			details += `<div class='d-flex flex-column width-full'>
-					<div>
-						`+time_line_heading+`
-							<span>
-								${data[i].date_sep}
+				if (data[i].imgsrc) {
+					details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
+							<img class='avatar-frame' src='${data[i].imgsrc}' width='32' height='32'></img>
+						</span>`;
+				} else {
+					details += `<span class='mr-3 avatar avatar-small' style='width:32px; height:32px;'>
+						<div align='center' class='avatar-frame' style='background-color: #fafbfc;'>
+							${data[i].practitioner ? data[i].practitioner.charAt(0) : 'U'}
+						</div>
+					</span>`;
+				}
+
+				details += `<div class='d-flex flex-column width-full'>
+						<div>
+							`+time_line_heading+`
+								<span>
+									${data[i].date_sep}
+								</span>
+						</div>
+						<div class='frappe-card p-5 mt-3'>
+							<span class='${data[i].reference_name} document-id'>${label}
+							<br>
+								<div align='center'>
+									<a class='btn octicon octicon-chevron-down btn-default btn-xs btn-more'
+										data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
+									</a>
+								</div>
 							</span>
-					</div>
-					<div class='Box p-3 mt-2'>
-						<span class='${data[i].reference_name} document-id'>${label}
-							<div align='center'>
-								<a class='btn octicon octicon-chevron-down btn-default btn-xs btn-more'
-									data-doctype='${data[i].reference_doctype}' data-docname='${data[i].reference_name}'>
-								</a>
-							</div>
-						</span>
-						<span class='document-html' hidden  data-fetched="0">
-						</span>
+
+							<span class='document-html' hidden data-fetched='0'>
+							</span>
+						</div>
 					</div>
 				</div>
-			</div>
-			</li>`;
+				</div>`;
+			}
+		}
+
+		this.page.main.find('.patient_documents_list').append(details);
+		this.start += data.length;
+
+		if (data.length === 20) {
+			this.page.main.find(".btn-get-records").show();
+		} else {
+			this.page.main.find(".btn-get-records").hide();
+			this.page.main.find(".patient_documents_list").append(`
+				<div class='text-muted' align='center'>
+					<br><br>${__('No more records..')}<br><br>
+				</div>`);
 		}
 	}
 
-	details += '</ul>';
-	me.page.main.find('.patient_documents_list').append(details);
-	me.start += data.length;
+	add_date_separator(data) {
+		let date = frappe.datetime.str_to_obj(data.communication_date);
+		let pdate = '';
+		let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(),
+			frappe.datetime.obj_to_str(date));
 
-	if (data.length === 20) {
-		me.page.main.find(".btn-get-records").show();
-	} else {
-		me.page.main.find(".btn-get-records").hide();
-		me.page.main.find(".patient_documents_list").append(`
-			<div class='text-muted' align='center'>
-				<br><br>${__('No more records..')}<br><br>
-			</div>`);
-	}
-};
-
-let add_date_separator = function(data) {
-	let date = frappe.datetime.str_to_obj(data.communication_date);
-	let pdate = '';
-	let diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
-
-	if (diff < 1) {
-		pdate = __('Today');
-	} else if (diff < 2) {
-		pdate = __('Yesterday');
-	} else {
-		pdate = __('on ') + frappe.datetime.global_date_format(date);
-	}
-	data.date_sep = pdate;
-	return data;
-};
-
-let show_patient_info = function(patient, me) {
-	frappe.call({
-		'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
-		args: {
-			patient: patient
-		},
-		callback: function(r) {
-			let data = r.message;
-			let details = '';
-			if (data.image) {
-				details += `<div><img class='thumbnail' width=75% src='${data.image}'></div>`;
-			}
-
-			details += `<b> ${data.patient_name} </b><br> ${data.sex}`;
-			if (data.email) details += `<br> ${data.email}`;
-			if (data.mobile) details += `<br> ${data.mobile}`;
-			if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
-			if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
-			if (data.allergies) details +=  `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
-			if (data.medication) details +=  `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
-			if (data.alcohol_current_use) details +=  `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
-			if (data.alcohol_past_use) details +=  `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
-			if (data.tobacco_current_use) details +=  `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
-			if (data.tobacco_past_use) details +=  `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
-			if (data.medical_history) details +=  `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
-			if (data.surgical_history) details +=  `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
-			if (data.surrounding_factors) details +=  `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
-			if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
-			if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
-
-			if (details) {
-				details = `<div style='padding-left:10px; font-size:13px;' align='left'>` + details + `</div>`;
-			}
-			me.page.main.find('.patient_details').html(details);
+		if (diff < 1) {
+			pdate = __('Today');
+		} else if (diff < 2) {
+			pdate = __('Yesterday');
+		} else {
+			pdate = __('on {0}', [frappe.datetime.global_date_format(date)]);
 		}
-	});
-};
+		data.date_sep = pdate;
+		return data;
+	}
 
-let show_patient_vital_charts = function(patient, me, btn_show_id, pts, title) {
-	frappe.call({
-		method: 'erpnext.healthcare.utils.get_patient_vitals',
-		args:{
-			patient: patient
-		},
-		callback: function(r) {
-			if (r.message) {
-				let show_chart_btns_html = `
-					<div style='padding-top:10px;'>
-						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
-							${__('Blood Pressure')}
-						</a>
-						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
-							${__('Respiratory/Pulse Rate')}
-						</a>
-						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
-							${__('Temperature')}
-						</a>
-						<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
-							${__('BMI')}
-						</a>
-					</div>`;
+	show_patient_info() {
+		this.get_patient_info().then(() => {
+			$('.patient-info').empty().append(frappe.render_template('patient_history_sidebar', {
+				patient_image: this.patient.image,
+				patient_name: this.patient.patient_name,
+				patient_gender: this.patient.sex,
+				patient_mobile: this.patient.mobile
+			}));
+			this.show_patient_details();
+		});
+	}
 
-				me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
+	show_patient_details() {
+		let me = this;
+		frappe.call({
+			'method': 'erpnext.healthcare.doctype.patient.patient.get_patient_detail',
+			args: {
+				patient: me.patient_id
+			},
+			callback: function(r) {
 				let data = r.message;
-				let labels = [], datasets = [];
-				let bp_systolic = [], bp_diastolic = [], temperature = [];
-				let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
+				let details = ``;
 
-				for (let i=0; i<data.length; i++) {
-					labels.push(data[i].signs_date+'||'+data[i].signs_time);
+				if (data.occupation) details += `<br><br><b> ${__('Occupation')} : </b> ${data.occupation}`;
+				if (data.blood_group) details += `<br><b> ${__('Blood Group')} : </b> ${data.blood_group}`;
+				if (data.allergies) details +=  `<br><br><b> ${__('Allerigies')} : </b> ${data.allergies.replace("\n", ", ")}`;
+				if (data.medication) details +=  `<br><b> ${__('Medication')} : </b> ${data.medication.replace("\n", ", ")}`;
+				if (data.alcohol_current_use) details +=  `<br><br><b> ${__('Alcohol use')} : </b> ${data.alcohol_current_use}`;
+				if (data.alcohol_past_use) details +=  `<br><b> ${__('Alcohol past use')} : </b> ${data.alcohol_past_use}`;
+				if (data.tobacco_current_use) details +=  `<br><b> ${__('Tobacco use')} : </b> ${data.tobacco_current_use}`;
+				if (data.tobacco_past_use) details +=  `<br><b> ${__('Tobacco past use')} : </b> ${data.tobacco_past_use}`;
+				if (data.medical_history) details +=  `<br><br><b> ${__('Medical history')} : </b> ${data.medical_history.replace("\n", ", ")}`;
+				if (data.surgical_history) details +=  `<br><b> ${__('Surgical history')} : </b> ${data.surgical_history.replace("\n", ", ")}`;
+				if (data.surrounding_factors) details +=  `<br><br><b> ${__('Occupational hazards')} : </b> ${data.surrounding_factors.replace("\n", ", ")}`;
+				if (data.other_risk_factors) details += `<br><b> ${__('Other risk factors')} : </b> ${data.other_risk_factors.replace("\n", ", ")}`;
+				if (data.patient_details) details += `<br><br><b> ${__('More info')} : </b> ${data.patient_details.replace("\n", ", ")}`;
 
-					if (btn_show_id === 'bp') {
-						bp_systolic.push(data[i].bp_systolic);
-						bp_diastolic.push(data[i].bp_diastolic);
-					}
-					if (btn_show_id === 'temperature') {
-						temperature.push(data[i].temperature);
-					}
-					if (btn_show_id === 'pulse_rate') {
-						pulse.push(data[i].pulse);
-						respiratory_rate.push(data[i].respiratory_rate);
-					}
-					if (btn_show_id === 'bmi') {
-						bmi.push(data[i].bmi);
-						height.push(data[i].height);
-						weight.push(data[i].weight);
-					}
+				if (details) {
+					details = `<div style='font-size:13px;' align='left'>` + details + `</div>`;
 				}
-				if (btn_show_id === 'temperature') {
-					datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
-				}
-				if (btn_show_id === 'bmi') {
-					datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
-					datasets.push({name: 'Height', values: height, chartType: 'line'});
-					datasets.push({name: 'Weight', values: weight, chartType: 'line'});
-				}
-				if (btn_show_id === 'bp') {
-					datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
-					datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
-				}
-				if (btn_show_id === 'pulse_rate') {
-					datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
-					datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
-				}
-				new frappe.Chart('.patient_vital_charts', {
-					data: {
-						labels: labels,
-						datasets: datasets
-					},
 
-					title: title,
-					type: 'axis-mixed',
-					height: 200,
-					colors: ['purple', '#ffa3ef', 'light-blue'],
-
-					tooltipOptions: {
-						formatTooltipX: d => (d + '').toUpperCase(),
-						formatTooltipY: d => d + ' ' + pts,
-					}
-				});
-				me.page.main.find('.header-separator').show();
-			} else {
-				me.page.main.find('.patient_vital_charts').html('');
-				me.page.main.find('.show_chart_btns').html('');
-				me.page.main.find('.header-separator').hide();
+				me.sidebar.find('.patient-details').html(details);
 			}
-		}
-	});
-};
+		});
+	}
+
+	get_patient_info() {
+		return frappe.xcall('frappe.client.get', {
+			doctype: 'Patient',
+			name: this.patient_id,
+		}).then((patient) => {
+			if (patient) {
+				this.patient = patient;
+			}
+		});
+	}
+
+	setup_buttons() {
+		let me = this;
+		this.page.main.on("click", ".btn-show-chart", function() {
+			let btn_id = $(this).attr("data-show-chart-id"), scale_unit = $(this).attr("data-pts");
+			let title = $(this).attr("data-title");
+			me.show_patient_vital_charts(btn_id, scale_unit, title);
+		});
+
+		this.page.main.on('click', '.btn-more', function() {
+			let	doctype = $(this).attr('data-doctype'), docname = $(this).attr('data-docname');
+			if (me.page.main.find('.'+docname).parent().find('.document-html').attr('data-fetched') == '1') {
+				me.page.main.find('.'+docname).hide();
+				me.page.main.find('.'+docname).parent().find('.document-html').show();
+			} else {
+				if (doctype && docname) {
+					let exclude = ['patient', 'patient_name', 'patient_sex', 'encounter_date', 'naming_series'];
+					frappe.call({
+						method: 'erpnext.healthcare.utils.render_doc_as_html',
+						args: {
+							doctype: doctype,
+							docname: docname,
+							exclude_fields: exclude
+						},
+						freeze: true,
+						callback: function(r) {
+							if (r.message) {
+								me.page.main.find('.' + docname).hide();
+
+								me.page.main.find('.' + docname).parent().find('.document-html').html(
+									`${r.message.html}
+									<br>
+										<div align='center'>
+											<a class='btn octicon octicon-chevron-up btn-default btn-xs btn-less'
+												data-doctype='${doctype}'
+												data-docname='${docname}'>
+											</a>
+										</div>
+									`);
+
+								me.page.main.find('.' + docname).parent().find('.document-html').attr('hidden', false);
+								me.page.main.find('.' + docname).parent().find('.document-html').attr('data-fetched', '1');
+							}
+						}
+					});
+				}
+			}
+		});
+
+		this.page.main.on('click', '.btn-less', function() {
+			let docname = $(this).attr('data-docname');
+			me.page.main.find('.' + docname).parent().find('.document-id').show();
+			me.page.main.find('.' + docname).parent().find('.document-html').hide();
+		});
+
+		me.page.main.on('click', '.btn-get-records', function() {
+			this.setup_documents();
+		});
+	}
+
+	show_patient_vital_charts(btn_id, scale_unit, title) {
+		let me = this;
+
+		frappe.call({
+			method: 'erpnext.healthcare.utils.get_patient_vitals',
+			args: {
+				patient: me.patient_id
+			},
+			callback: function(r) {
+				if (r.message) {
+					let show_chart_btns_html = `
+						<div style='padding-top:10px;'>
+							<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bp' data-pts='mmHg' data-title='Blood Pressure'>
+								${__('Blood Pressure')}
+							</a>
+							<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='pulse_rate' data-pts='per Minutes' data-title='Respiratory/Pulse Rate'>
+								${__('Respiratory/Pulse Rate')}
+							</a>
+							<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='temperature' data-pts='°C or °F' data-title='Temperature'>
+								${__('Temperature')}
+							</a>
+							<a class='btn btn-default btn-xs btn-show-chart' data-show-chart-id='bmi' data-pts='' data-title='BMI'>
+								${__('BMI')}
+							</a>
+						</div>`;
+
+					me.page.main.find('.show_chart_btns').html(show_chart_btns_html);
+					let data = r.message;
+					let labels = [], datasets = [];
+					let bp_systolic = [], bp_diastolic = [], temperature = [];
+					let pulse = [], respiratory_rate = [], bmi = [], height = [], weight = [];
+
+					for (let i=0; i<data.length; i++) {
+						labels.push(data[i].signs_date+' | '+data[i].signs_time);
+
+						if (btn_id === 'bp') {
+							bp_systolic.push(data[i].bp_systolic);
+							bp_diastolic.push(data[i].bp_diastolic);
+						}
+						if (btn_id === 'temperature') {
+							temperature.push(data[i].temperature);
+						}
+						if (btn_id === 'pulse_rate') {
+							pulse.push(data[i].pulse);
+							respiratory_rate.push(data[i].respiratory_rate);
+						}
+						if (btn_id === 'bmi') {
+							bmi.push(data[i].bmi);
+							height.push(data[i].height);
+							weight.push(data[i].weight);
+						}
+					}
+					if (btn_id === 'temperature') {
+						datasets.push({name: 'Temperature', values: temperature, chartType: 'line'});
+					}
+					if (btn_id === 'bmi') {
+						datasets.push({name: 'BMI', values: bmi, chartType: 'line'});
+						datasets.push({name: 'Height', values: height, chartType: 'line'});
+						datasets.push({name: 'Weight', values: weight, chartType: 'line'});
+					}
+					if (btn_id === 'bp') {
+						datasets.push({name: 'BP Systolic', values: bp_systolic, chartType: 'line'});
+						datasets.push({name: 'BP Diastolic', values: bp_diastolic, chartType: 'line'});
+					}
+					if (btn_id === 'pulse_rate') {
+						datasets.push({name: 'Heart Rate / Pulse', values: pulse, chartType: 'line'});
+						datasets.push({name: 'Respiratory Rate', values: respiratory_rate, chartType: 'line'});
+					}
+
+					new frappe.Chart('.patient_vital_charts', {
+						data: {
+							labels: labels,
+							datasets: datasets
+						},
+
+						title: title,
+						type: 'axis-mixed',
+						height: 200,
+						colors: ['purple', '#ffa3ef', 'light-blue'],
+
+						tooltipOptions: {
+							formatTooltipX: d => (d + '').toUpperCase(),
+							formatTooltipY: d => d + ' ' + scale_unit,
+						}
+					});
+					me.page.main.find('.header-separator').show();
+				} else {
+					me.page.main.find('.patient_vital_charts').html('');
+					me.page.main.find('.show_chart_btns').html('');
+					me.page.main.find('.header-separator').hide();
+				}
+			}
+		});
+	}
+}
\ No newline at end of file
diff --git a/erpnext/healthcare/page/patient_history/patient_history_sidebar.html b/erpnext/healthcare/page/patient_history/patient_history_sidebar.html
new file mode 100644
index 0000000..4560e7e
--- /dev/null
+++ b/erpnext/healthcare/page/patient_history/patient_history_sidebar.html
@@ -0,0 +1,21 @@
+<div class="patient-history-sidebar">
+    <div class="patient-image-container">
+		{% if patient_image %}
+			<div class="patient-image" src={{patient_image}} style="background-image: url(\'{%= patient_image %}\')"></div>
+		{% endif %}
+    </div>
+    <div class="patient-intro">
+		{% if patient_name %}
+		<p class="patient-name bold">{{patient_name}}</p>
+		{% endif %}
+		{% if patient_gender %}
+		<p class="patient-gender text-muted">{%=__("Gender: ") %} {{patient_gender}}</p>
+		{% endif %}
+		{% if patient_mobile %}
+		<p class="patient-mobile text-muted">{%=__("Contact: ") %} {{patient_mobile}}</p>
+		{% endif %}
+	</div>
+    <div class="patient-details">
+    </div>
+</div>
+
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.css b/erpnext/healthcare/page/patient_progress/patient_progress.css
index 5d85a74..737b2e0 100644
--- a/erpnext/healthcare/page/patient_progress/patient_progress.css
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.css
@@ -29,6 +29,7 @@
 
 .patient-name {
   font-size: 20px;
+  margin-top: 25px;
 }
 
 /* heatmap */
@@ -55,6 +56,7 @@
 }
 
 .heatmap-container .chart-filter {
+  z-index: 1;
   position: relative;
   top: 5px;
   margin-right: 10px;
@@ -111,10 +113,13 @@
 }
 
 .chart-column-container {
-  border-bottom: 1px solid #d1d8dd;
   margin: 5px 0;
 }
 
+.progress-graphs .progress-container {
+  margin-bottom: var(--margin-xl);
+}
+
 .line-chart-container .frappe-chart {
   margin-top: -20px;
 }
@@ -146,6 +151,7 @@
   }
 
   .percentage-chart-container .chart-filter {
+    z-index: 1;
     position: relative;
     top: 12px;
     margin-right: 10px;
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.html b/erpnext/healthcare/page/patient_progress/patient_progress.html
index 30064bd..ee60065 100644
--- a/erpnext/healthcare/page/patient_progress/patient_progress.html
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.html
@@ -1,14 +1,15 @@
 <div class="row patient-progress">
 	<div class="col-md-12">
 		<div class="progress-graphs">
-			<div class="chart-column-container heatmap-container hidden-xs hidden-sm">
+			<div class="progress-container chart-column-container heatmap-container hidden-xs hidden-sm frappe-card">
 				<div class="patient-heatmap"></div>
 			</div>
-			<div class="chart-column-container percentage-chart-container">
+
+			<div class="progress-container chart-column-container percentage-chart-container frappe-card">
 				<div class="therapy-session-percentage-chart"></div>
 			</div>
 
-			<div class="therapy-progress">
+			<div class="progress-container therapy-progress frappe-card">
 				<div class="chart-head">
 					<text class="title" text-anchor="start">Therapy Progress</text>
 					<div class="chart-control pull-right"></div>
@@ -22,7 +23,7 @@
 				</div>
 			</div>
 
-			<div class="assessment-results">
+			<div class="progress-container assessment-results frappe-card">
 				<div class="chart-head">
 					<text class="title" text-anchor="start">Assessment Results</text>
 					<div class="chart-control pull-right"></div>
@@ -36,7 +37,7 @@
 				</div>
 			</div>
 
-			<div class="therapy-assessment-correlation progress-line-chart">
+			<div class="progress-container therapy-assessment-correlation progress-line-chart frappe-card">
 				<div class="chart-head">
 					<text class="title" text-anchor="start">Therapy Type and Assessment Correlation</text>
 					<div class="chart-control pull-right"></div>
@@ -50,7 +51,7 @@
 				</div>
 			</div>
 
-			<div class="assessment-parameter-progress progress-line-chart">
+			<div class="progress-container assessment-parameter-progress progress-line-chart frappe-card">
 				<div class="chart-head">
 					<text class="title" text-anchor="start">Assessment Parameter Wise Progress</text>
 					<div class="chart-control pull-right"></div>
diff --git a/erpnext/healthcare/page/patient_progress/patient_progress.js b/erpnext/healthcare/page/patient_progress/patient_progress.js
index 4b7599d..3f06f1f 100644
--- a/erpnext/healthcare/page/patient_progress/patient_progress.js
+++ b/erpnext/healthcare/page/patient_progress/patient_progress.js
@@ -133,8 +133,11 @@
 			type: 'heatmap',
 			countLabel: 'Interactions',
 			data: {},
-			discreteDomains: 0
+			discreteDomains: 1,
+			radius: 3,
+			height: 150
 		});
+
 		this.update_heatmap_data();
 		this.create_heatmap_chart_filters();
 	}
@@ -164,33 +167,35 @@
 	}
 
 	render_percentage_chart(field, title) {
-		frappe.xcall(
-			'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', {
-				patient: this.patient_id,
-				field: field
-			}
-		).then(chart => {
-			if (chart.labels.length) {
-				this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', {
-					title: title,
-					type: 'percentage',
-					data: {
-						labels: chart.labels,
-						datasets: chart.datasets
-					},
-					truncateLegends: 1,
-					barOptions: {
-						height: 11,
-						depth: 1
-					},
-					height: 160,
-					maxSlices: 8,
-					colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'],
-				});
-			} else {
-				this.wrapper.find('.percentage-chart-container').hide();
-			}
-		});
+		// REDESIGN-TODO: chart seems to be broken. Enable this once fixed.
+		this.wrapper.find('.percentage-chart-container').hide();
+		// frappe.xcall(
+		// 	'erpnext.healthcare.page.patient_progress.patient_progress.get_therapy_sessions_distribution_data', {
+		// 		patient: this.patient_id,
+		// 		field: field
+		// 	}
+		// ).then(chart => {
+		// 	if (chart.labels.length) {
+		// 		this.percentage_chart = new frappe.Chart('.therapy-session-percentage-chart', {
+		// 			title: title,
+		// 			type: 'percentage',
+		// 			data: {
+		// 				labels: chart.labels,
+		// 				datasets: chart.datasets
+		// 			},
+		// 			truncateLegends: 1,
+		// 			barOptions: {
+		// 				height: 11,
+		// 				depth: 1
+		// 			},
+		// 			height: 160,
+		// 			maxSlices: 8,
+		// 			colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'],
+		// 		});
+		// 	} else {
+		// 		this.wrapper.find('.percentage-chart-container').hide();
+		// 	}
+		// });
 	}
 
 	create_percentage_chart_filters() {
@@ -311,7 +316,7 @@
 						},
 						axisOptions: {
 							xIsSeries: 1
-						},
+						}
 					});
 				} else {
 					$(parent).find('.chart-container').show();
@@ -377,7 +382,7 @@
 							xIsSeries: 1
 						},
 						tooltipOptions: {
-							formatTooltipY: d => d + __(' out of ') + chart.max_score
+							formatTooltipY: d => __('{0} out of {1}', [d, chart.max_score])
 						}
 					});
 				} else {
diff --git a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
index 4b461f1..fae5ece 100644
--- a/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
+++ b/erpnext/healthcare/report/inpatient_medication_orders/test_inpatient_medication_orders.py
@@ -25,7 +25,7 @@
 			'from_date': getdate(),
 			'to_date': getdate(),
 			'patient': '_Test IPD Patient',
-			'service_unit': 'Test Service Unit Ip Occupancy - _TC'
+			'service_unit': '_Test Service Unit Ip Occupancy - _TC'
 		}
 
 		report = execute(filters)
@@ -42,7 +42,7 @@
 				'date': getdate(),
 				'time': datetime.timedelta(seconds=32400),
 				'is_completed': 0,
-				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+				'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
 			},
 			{
 				'patient': '_Test IPD Patient',
@@ -55,7 +55,7 @@
 				'date': getdate(),
 				'time': datetime.timedelta(seconds=50400),
 				'is_completed': 0,
-				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+				'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
 			},
 			{
 				'patient': '_Test IPD Patient',
@@ -68,7 +68,7 @@
 				'date': getdate(),
 				'time': datetime.timedelta(seconds=75600),
 				'is_completed': 0,
-				'healthcare_service_unit': 'Test Service Unit Ip Occupancy - _TC'
+				'healthcare_service_unit': '_Test Service Unit Ip Occupancy - _TC'
 			}
 		]
 
@@ -83,7 +83,7 @@
 			'from_date': getdate(),
 			'to_date': getdate(),
 			'patient': '_Test IPD Patient',
-			'service_unit': 'Test Service Unit Ip Occupancy - _TC',
+			'service_unit': '_Test Service Unit Ip Occupancy - _TC',
 			'show_completed_orders': 0
 		}
 
@@ -119,7 +119,7 @@
 	ip_record.expected_length_of_stay = 0
 	ip_record.save()
 	ip_record.reload()
-	service_unit = get_healthcare_service_unit('Test Service Unit Ip Occupancy')
+	service_unit = get_healthcare_service_unit('_Test Service Unit Ip Occupancy')
 	admit_patient(ip_record, service_unit, now_datetime())
 
 	ipmo = create_ipmo(patient)
diff --git a/erpnext/healthcare/report/lab_test_report/lab_test_report.py b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
index 2e59bed..ba4ca41 100644
--- a/erpnext/healthcare/report/lab_test_report/lab_test_report.py
+++ b/erpnext/healthcare/report/lab_test_report/lab_test_report.py
@@ -169,7 +169,7 @@
 			'labels': labels,
 			'datasets': datasets
 		},
-		'type': 'donut',
+		'type': 'bar',
 		'height': 300,
 	}
 
diff --git a/erpnext/healthcare/utils.py b/erpnext/healthcare/utils.py
index d3d22c8..7c80bdb 100644
--- a/erpnext/healthcare/utils.py
+++ b/erpnext/healthcare/utils.py
@@ -8,8 +8,7 @@
 import json
 from frappe import _
 from frappe.utils.formatters import format_value
-from frappe.utils import time_diff_in_hours, rounded
-from six import string_types
+from frappe.utils import time_diff_in_hours, rounded, cstr
 from erpnext.healthcare.doctype.healthcare_settings.healthcare_settings import get_income_account
 from erpnext.healthcare.doctype.fee_validity.fee_validity import create_fee_validity
 from erpnext.healthcare.doctype.lab_test.lab_test import create_multiple
@@ -181,9 +180,9 @@
 
 			service_item = frappe.db.get_single_value('Healthcare Settings', 'clinical_procedure_consumable_item')
 			if not service_item:
-				msg = _('Please Configure Clinical Procedure Consumable Item in ')
-				msg += '''<b><a href='/app/Form/Healthcare Settings'>Healthcare Settings</a></b>'''
-				frappe.throw(msg, title=_('Missing Configuration'))
+				frappe.throw(_('Please configure Clinical Procedure Consumable Item in {0}').format(
+					frappe.utils.get_link_to_form('Healthcare Settings', 'Healthcare Settings')),
+					title=_('Missing Configuration'))
 
 			clinical_procedures_to_invoice.append({
 				'reference_type': 'Clinical Procedure',
@@ -312,7 +311,7 @@
 
 @frappe.whitelist()
 def get_service_item_and_practitioner_charge(doc):
-	if isinstance(doc, string_types):
+	if isinstance(doc, str):
 		doc = json.loads(doc)
 		doc = frappe.get_doc(doc)
 
@@ -543,58 +542,43 @@
 
 
 @frappe.whitelist()
-def get_children(doctype, parent, company, is_root=False):
-	parent_fieldname = "parent_" + doctype.lower().replace(" ", "_")
+def get_children(doctype, parent=None, company=None, is_root=False):
+	parent_fieldname = 'parent_' + doctype.lower().replace(' ', '_')
 	fields = [
-		"name as value",
-		"is_group as expandable",
-		"lft",
-		"rgt"
+		'name as value',
+		'is_group as expandable',
+		'lft',
+		'rgt'
 	]
-	# fields = [ "name", "is_group", "lft", "rgt" ]
-	filters = [["ifnull(`{0}`,'')".format(parent_fieldname), "=", "" if is_root else parent]]
+
+	filters = [["ifnull(`{0}`,'')".format(parent_fieldname),
+		'=', '' if is_root else parent]]
 
 	if is_root:
-		fields += ["service_unit_type"] if doctype == "Healthcare Service Unit" else []
-		filters.append(["company", "=", company])
-
+		fields += ['service_unit_type'] if doctype == 'Healthcare Service Unit' else []
+		filters.append(['company', '=', company])
 	else:
-		fields += ["service_unit_type", "allow_appointments", "inpatient_occupancy", "occupancy_status"] if doctype == "Healthcare Service Unit" else []
-		fields += [parent_fieldname + " as parent"]
+		fields += ['service_unit_type', 'allow_appointments', 'inpatient_occupancy',
+			'occupancy_status'] if doctype == 'Healthcare Service Unit' else []
+		fields += [parent_fieldname + ' as parent']
 
-	hc_service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+	service_units = frappe.get_list(doctype, fields=fields, filters=filters)
+	for each in service_units:
+		if each['expandable'] == 1:  # group node
+			available_count = frappe.db.count('Healthcare Service Unit',  filters={
+				'parent_healthcare_service_unit': each['value'],
+				'inpatient_occupancy': 1})
 
-	if doctype == "Healthcare Service Unit":
-		for each in hc_service_units:
-			occupancy_msg = ""
-			if each["expandable"] == 1:
-				occupied = False
-				vacant = False
-				child_list = frappe.db.sql(
-					'''
-						SELECT
-							name, occupancy_status
-						FROM
-							`tabHealthcare Service Unit`
-						WHERE
-							inpatient_occupancy = 1
-							and lft > %s and rgt < %s
-					''', (each['lft'], each['rgt']))
+			if available_count > 0:
+				occupied_count = frappe.db.count('Healthcare Service Unit',  {
+					'parent_healthcare_service_unit': each['value'],
+					'inpatient_occupancy': 1,
+					'occupancy_status': 'Occupied'})
+				# set occupancy status of group node
+				each['occupied_of_available'] = str(
+					occupied_count) + ' Occupied of ' + str(available_count)
 
-				for child in child_list:
-					if not occupied:
-						occupied = 0
-					if child[1] == "Occupied":
-						occupied += 1
-					if not vacant:
-						vacant = 0
-					if child[1] == "Vacant":
-						vacant += 1
-				if vacant and occupied:
-					occupancy_total = vacant + occupied
-					occupancy_msg = str(occupied) + " Occupied out of " + str(occupancy_total)
-			each["occupied_out_of_vacant"] = occupancy_msg
-	return hc_service_units
+	return service_units
 
 
 @frappe.whitelist()
@@ -622,98 +606,181 @@
 
 @frappe.whitelist()
 def render_doc_as_html(doctype, docname, exclude_fields = []):
-	#render document as html, three column layout will break
+	"""
+		Render document as HTML
+	"""
+
 	doc = frappe.get_doc(doctype, docname)
 	meta = frappe.get_meta(doctype)
-	doc_html = "<div class='col-md-12 col-sm-12'>"
-	section_html = ''
-	section_label = ''
-	html = ''
-	sec_on = False
+	doc_html = section_html = section_label = html = ""
+	sec_on = has_data = False
 	col_on = 0
-	has_data = False
+
 	for df in meta.fields:
-		#on section break append append previous section and html to doc html
+		# on section break append previous section and html to doc html
 		if df.fieldtype == "Section Break":
 			if has_data and col_on and sec_on:
 				doc_html += section_html + html + "</div>"
+
 			elif has_data and not col_on and sec_on:
-				doc_html += "<div class='col-md-12 col-sm-12'\
-				><div class='col-md-12 col-sm-12'>" \
-				+ section_html + html +"</div></div>"
+				doc_html += """
+					<br>
+					<div class='row'>
+						<div class='col-md-12 col-sm-12'>
+							<b>{0}</b>
+						</div>
+					</div>
+					<div class='row'>
+						<div class='col-md-12 col-sm-12'>
+							{1} {2}
+						</div>
+					</div>
+				""".format(section_label, section_html, html)
+
+			# close divs for columns
 			while col_on:
 				doc_html += "</div>"
 				col_on -= 1
+
 			sec_on = True
-			has_data= False
+			has_data = False
 			col_on = 0
-			section_html = ''
-			html = ''
+			section_html = html = ""
+
 			if df.label:
 				section_label = df.label
 			continue
-		#on column break append html to section html or doc html
+
+		# on column break append html to section html or doc html
 		if df.fieldtype == "Column Break":
-			if sec_on and has_data:
-				section_html += "<div class='col-md-12 col-sm-12'\
-				><div class='col-md-6 col\
-				-sm-6'><b>" + section_label + "</b>" + html + "</div><div \
-				class='col-md-6 col-sm-6'>"
-			elif has_data:
-				doc_html += "<div class='col-md-12 col-sm-12'><div class='col-m\
-				d-6 col-sm-6'>" + html + "</div><div class='col-md-6 col-sm-6'>"
-			elif sec_on and not col_on:
-				section_html += "<div class='col-md-6 col-sm-6'>"
-			html = ''
+			if sec_on and not col_on and has_data:
+				section_html += """
+					<br>
+					<div class='row'>
+						<div class='col-md-12 col-sm-12'>
+							<b>{0}</b>
+						</div>
+					</div>
+					<div class='row'>
+						<div class='col-md-4 col-sm-4'>
+							{1}
+						</div>
+				""".format(section_label, html)
+			elif col_on == 1 and has_data:
+				section_html += "<div class='col-md-4 col-sm-4'>" + html + "</div>"
+			elif col_on > 1 and has_data:
+				doc_html += "<div class='col-md-4 col-sm-4'>" + html + "</div>"
+			else:
+				doc_html += """
+					<div class='row'>
+						<div class='col-md-12 col-sm-12'>
+							{0}
+						</div>
+					</div>
+				""".format(html)
+
+			html = ""
 			col_on += 1
+
 			if df.label:
-				html += '<br>' + df.label
+				html += "<br>" + df.label
 			continue
-		#on table iterate in items and create table based on in_list_view, append to section html or doc html
-		if df.fieldtype == 'Table':
+
+		# on table iterate through items and create table
+		# based on the in_list_view property
+		# append to section html or doc html
+		if df.fieldtype == "Table":
 			items = doc.get(df.fieldname)
-			if not items: continue
+			if not items:
+				continue
 			child_meta = frappe.get_meta(df.options)
-			if not has_data : has_data = True
-			table_head = ''
-			table_row = ''
+
+			if not has_data:
+				has_data = True
+			table_head = table_row = ""
 			create_head = True
+
 			for item in items:
-				table_row += '<tr>'
+				table_row += "<tr>"
 				for cdf in child_meta.fields:
 					if cdf.in_list_view:
 						if create_head:
-							table_head += '<th>' + cdf.label + '</th>'
+							table_head += "<th class='text-muted'>" + cdf.label + "</th>"
 						if item.get(cdf.fieldname):
-							table_row += '<td>' + str(item.get(cdf.fieldname)) \
-							+ '</td>'
+							table_row += "<td>" + cstr(item.get(cdf.fieldname)) + "</td>"
 						else:
-							table_row += '<td></td>'
+							table_row += "<td></td>"
+
 				create_head = False
-				table_row += '</tr>'
+				table_row += "</tr>"
+
 			if sec_on:
-				section_html += "<table class='table table-condensed \
-				bordered'>" + table_head +  table_row + '</table>'
+				section_html += """
+					<table class='table table-condensed bordered'>
+						{0} {1}
+					</table>
+				""".format(table_head, table_row)
 			else:
-				html += "<table class='table table-condensed table-bordered'>" \
-				+ table_head +  table_row + "</table>"
+				html += """
+					<table class='table table-condensed table-bordered'>
+						{0} {1}
+					</table>
+				""".format(table_head, table_row)
 			continue
 
-		#on other field types add label and value to html
+		# on any other field type add label and value to html
 		if not df.hidden and not df.print_hide and doc.get(df.fieldname) and df.fieldname not in exclude_fields:
-			if doc.get(df.fieldname):
-				formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
-				html +=  '<br>{0} : {1}'.format(df.label or df.fieldname, formatted_value)
+			formatted_value = format_value(doc.get(df.fieldname), meta.get_field(df.fieldname), doc)
+			html += "<br>{0} : {1}".format(df.label or df.fieldname, formatted_value)
 
 			if not has_data : has_data = True
 
 	if sec_on and col_on and has_data:
-		doc_html += section_html + html + '</div></div>'
+		doc_html += section_html + html + "</div></div>"
 	elif sec_on and not col_on and has_data:
-		doc_html += "<div class='col-md-12 col-sm-12'\
-		><div class='col-md-12 col-sm-12'>" \
-		+ section_html + html +'</div></div>'
-	if doc_html:
-		doc_html = "<div class='small'><div class='col-md-12 text-right'><a class='btn btn-default btn-xs' href='/app/Form/%s/%s'></a></div>" %(doctype, docname) + doc_html + '</div>'
+		doc_html += """
+			<div class='col-md-12 col-sm-12'>
+				<div class='col-md-12 col-sm-12'>
+					{0} {1}
+				</div>
+			</div>
+		""".format(section_html, html)
 
-	return {'html': doc_html}
+	return {"html": doc_html}
+
+
+def update_address_links(address, method):
+	'''
+	Hook validate Address
+	If Patient is linked in Address, also link the associated Customer
+	'''
+	if 'Healthcare' not in frappe.get_active_domains():
+		return
+
+	patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', address.links))
+
+	for link in patient_links:
+		customer = frappe.db.get_value('Patient', link.get('link_name'), 'customer')
+		if customer and not address.has_link('Customer', customer):
+			address.append('links', dict(link_doctype = 'Customer', link_name = customer))
+
+
+def update_patient_email_and_phone_numbers(contact, method):
+	'''
+	Hook validate Contact
+	Update linked Patients' primary mobile and phone numbers
+	'''
+	if 'Healthcare' not in frappe.get_active_domains():
+		return
+
+	if contact.is_primary_contact and (contact.email_id or contact.mobile_no or contact.phone):
+		patient_links = list(filter(lambda link: link.get('link_doctype') == 'Patient', contact.links))
+
+		for link in patient_links:
+			contact_details = frappe.db.get_value('Patient', link.get('link_name'), ['email', 'mobile', 'phone'], as_dict=1)
+			if contact.email_id and contact.email_id != contact_details.get('email'):
+				frappe.db.set_value('Patient', link.get('link_name'), 'email', contact.email_id)
+			if contact.mobile_no and contact.mobile_no != contact_details.get('mobile'):
+				frappe.db.set_value('Patient', link.get('link_name'), 'mobile', contact.mobile_no)
+			if contact.phone and contact.phone != contact_details.get('phone'):
+				frappe.db.set_value('Patient', link.get('link_name'), 'phone', contact.phone)
diff --git a/erpnext/healthcare/workspace/healthcare/healthcare.json b/erpnext/healthcare/workspace/healthcare/healthcare.json
index 55132f3..f69604c 100644
--- a/erpnext/healthcare/workspace/healthcare/healthcare.json
+++ b/erpnext/healthcare/workspace/healthcare/healthcare.json
@@ -7,7 +7,7 @@
   }
  ],
  "charts_label": "",
- "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Healthcare\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Patient Appointments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient Appointment\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Service Unit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Practitioner\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient History\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Rehabilitation and Physiotherapy\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Records and History\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
+ "content": "[{\"type\": \"onboarding\", \"data\": {\"onboarding_name\":\"Healthcare\", \"col\": 12}}, {\"type\": \"chart\", \"data\": {\"chart_name\": \"Patient Appointments\", \"col\": 12}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Your Shortcuts\", \"level\": 4, \"col\": 12}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient Appointment\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Service Unit\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Healthcare Practitioner\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Patient History\", \"col\": 4}}, {\"type\": \"shortcut\", \"data\": {\"shortcut_name\": \"Dashboard\", \"col\": 4}}, {\"type\": \"spacer\", \"data\": {\"col\": 12}}, {\"type\": \"header\", \"data\": {\"text\": \"Reports & Masters\", \"level\": 4, \"col\": 12}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Masters\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Consultation\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Facility Management\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Settings\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Inpatient\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory Setup\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Laboratory\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Rehabilitation and Physiotherapy\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Records and History\", \"col\": 4}}, {\"type\": \"card\", \"data\": {\"card_name\": \"Reports\", \"col\": 4}}]",
  "creation": "2020-03-02 17:23:17.919682",
  "developer_mode_only": 0,
  "disable_user_customization": 0,
@@ -76,54 +76,9 @@
    "type": "Link"
   },
   {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Healthcare Service Unit Type",
-   "link_count": 0,
-   "link_to": "Healthcare Service Unit Type",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Healthcare Service Unit",
-   "link_count": 0,
-   "link_to": "Healthcare Service Unit",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Medical Code Standard",
-   "link_count": 0,
-   "link_to": "Medical Code Standard",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Medical Code",
-   "link_count": 0,
-   "link_to": "Medical Code",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Consultation Setup",
-   "link_count": 0,
    "onboard": 0,
    "type": "Card Break"
   },
@@ -132,7 +87,6 @@
    "hidden": 0,
    "is_query_report": 0,
    "label": "Appointment Type",
-   "link_count": 0,
    "link_to": "Appointment Type",
    "link_type": "DocType",
    "onboard": 0,
@@ -143,7 +97,6 @@
    "hidden": 0,
    "is_query_report": 0,
    "label": "Clinical Procedure Template",
-   "link_count": 0,
    "link_to": "Clinical Procedure Template",
    "link_type": "DocType",
    "onboard": 0,
@@ -154,7 +107,6 @@
    "hidden": 0,
    "is_query_report": 0,
    "label": "Prescription Dosage",
-   "link_count": 0,
    "link_to": "Prescription Dosage",
    "link_type": "DocType",
    "onboard": 0,
@@ -165,7 +117,6 @@
    "hidden": 0,
    "is_query_report": 0,
    "label": "Prescription Duration",
-   "link_count": 0,
    "link_to": "Prescription Duration",
    "link_type": "DocType",
    "onboard": 0,
@@ -176,70 +127,16 @@
    "hidden": 0,
    "is_query_report": 0,
    "label": "Antibiotic",
-   "link_count": 0,
    "link_to": "Antibiotic",
    "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
   },
   {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Consultation",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Patient Appointment",
-   "link_count": 0,
-   "link_to": "Patient Appointment",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Clinical Procedure",
-   "link_count": 0,
-   "link_to": "Clinical Procedure",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Patient Encounter",
-   "link_count": 0,
-   "link_to": "Patient Encounter",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Vital Signs",
-   "link_count": 0,
-   "link_to": "Vital Signs",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
    "label": "Complaint",
-   "link_count": 0,
    "link_to": "Complaint",
    "link_type": "DocType",
    "onboard": 0,
@@ -250,18 +147,63 @@
    "hidden": 0,
    "is_query_report": 0,
    "label": "Diagnosis",
-   "link_count": 0,
    "link_to": "Diagnosis",
    "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
   },
   {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Consultation",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Patient Appointment",
+   "link_to": "Patient Appointment",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Clinical Procedure",
+   "link_to": "Clinical Procedure",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Patient Encounter",
+   "link_to": "Patient Encounter",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Vital Signs",
+   "link_to": "Vital Signs",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
    "label": "Fee Validity",
-   "link_count": 0,
    "link_to": "Fee Validity",
    "link_type": "DocType",
    "onboard": 0,
@@ -270,6 +212,62 @@
   {
    "hidden": 0,
    "is_query_report": 0,
+   "label": "Facility Management",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Healthcare Service Unit Type",
+   "link_to": "Healthcare Service Unit Type",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Healthcare Service Unit",
+   "link_to": "Healthcare Service Unit",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Medical Coding",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Medical Code Standard",
+   "link_to": "Medical Code Standard",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Medical Code",
+   "link_to": "Medical Code",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
    "label": "Settings",
    "link_count": 0,
    "onboard": 0,
@@ -339,6 +337,16 @@
    "type": "Link"
   },
   {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Dosage Form",
+   "link_to": "Dosage Form",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Laboratory",
@@ -369,12 +377,36 @@
    "type": "Link"
   },
   {
-   "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Dosage Form",
-   "link_count": 0,
-   "link_to": "Dosage Form",
+   "label": "Inpatient",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Inpatient Medication Order",
+   "link_to": "Inpatient Medication Order",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Inpatient Record",
+   "link_to": "Inpatient Record",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Inpatient Medication Entry",
+   "link_to": "Inpatient Medication Entry",
    "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
@@ -536,7 +568,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-08-05 12:15:59.434612",
+ "modified": "2021-08-30 17:37:45.316999",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Healthcare",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 4854bfd..b1a64f9 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -290,7 +290,12 @@
 		"on_trash": "erpnext.regional.check_deletion_permission"
 	},
 	'Address': {
-		'validate': ['erpnext.regional.india.utils.validate_gstin_for_india', 'erpnext.regional.italy.utils.set_state_code', 'erpnext.regional.india.utils.update_gst_category']
+		'validate': [
+			'erpnext.regional.india.utils.validate_gstin_for_india',
+			'erpnext.regional.italy.utils.set_state_code',
+			'erpnext.regional.india.utils.update_gst_category',
+			'erpnext.healthcare.utils.update_address_links'
+		],
 	},
 	'Supplier': {
 		'validate': 'erpnext.regional.india.utils.validate_pan_for_india'
@@ -301,7 +306,7 @@
 	"Contact": {
 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
 		"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
-		"validate": "erpnext.crm.utils.update_lead_phone_numbers"
+		"validate": ["erpnext.crm.utils.update_lead_phone_numbers", "erpnext.healthcare.utils.update_patient_email_and_phone_numbers"]
 	},
 	"Email Unsubscribe": {
 		"after_insert": "erpnext.crm.doctype.email_campaign.email_campaign.unsubscribe_recipient"
diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js b/erpnext/hotels/doctype/hotel_room/test_hotel_room.js
deleted file mode 100644
index 8b2b833..0000000
--- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Room
-		() => frappe.tests.make('Hotel Room', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js
deleted file mode 100644
index f1ebad4..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Package", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Room Package
-		() => frappe.tests.make('Hotel Room Package', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js
deleted file mode 100644
index ba0d1fd..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Pricing", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Room Pricing
-		() => frappe.tests.make('Hotel Room Pricing', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js
deleted file mode 100644
index 73a561c..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Pricing Package", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Room Pricing Package
-		() => frappe.tests.make('Hotel Room Pricing Package', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js
deleted file mode 100644
index 2897139..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Reservation", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Room Reservation
-		() => frappe.tests.make('Hotel Room Reservation', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js
deleted file mode 100644
index e2dd578..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Room Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Room Type
-		() => frappe.tests.make('Hotel Room Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js
deleted file mode 100644
index bc0b7f8..0000000
--- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Hotel Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Hotel Settings
-		() => frappe.tests.make('Hotel Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.js b/erpnext/hr/doctype/attendance_request/test_attendance_request.js
deleted file mode 100644
index d40ec61..0000000
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Attendance Request", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Attendance Request
-		() => frappe.tests.make('Attendance Request', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/branch/test_branch.js b/erpnext/hr/doctype/branch/test_branch.js
deleted file mode 100644
index 82a6ae1..0000000
--- a/erpnext/hr/doctype/branch/test_branch.js
+++ /dev/null
@@ -1,23 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("Test: Branch [HR]", function (assert) {
-	assert.expect(1);
-	let done = assert.async();
-
-	frappe.run_serially([
-		// test branch creation
-		() => frappe.set_route("List", "Branch", "List"),
-		() => frappe.new_doc("Branch"),
-		() => frappe.timeout(1),
-		() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
-		() => frappe.timeout(1),
-		() => cur_frm.set_value("branch", "Test Branch"),
-
-		// save form
-		() => cur_frm.save(),
-		() => frappe.timeout(1),
-		() => assert.equal("Test Branch", cur_frm.doc.branch,
-			'name of branch correctly saved'),
-		() => done()
-	]);
-});
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js
deleted file mode 100644
index bebcaac..0000000
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Compensatory Leave Request", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Compensatory Leave Request
-		() => frappe.tests.make('Compensatory Leave Request', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js b/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
deleted file mode 100644
index 1533517..0000000
--- a/erpnext/hr/doctype/daily_work_summary/test_daily_work_summary.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Daily Work Summary", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Daily Work Summary
-		() => frappe.tests.make('Daily Work Summary', [
-			// values to be set
-			{ key: 'value' }
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/department/test_department.js b/erpnext/hr/doctype/department/test_department.js
deleted file mode 100644
index e73779c..0000000
--- a/erpnext/hr/doctype/department/test_department.js
+++ /dev/null
@@ -1,23 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("Test: Department [HR]", function (assert) {
-	assert.expect(1);
-	let done = assert.async();
-
-	frappe.run_serially([
-		// test department creation
-		() => frappe.set_route("List", "Department", "List"),
-		() => frappe.new_doc("Department"),
-		() => frappe.timeout(1),
-		() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
-		() => frappe.timeout(1),
-		() => cur_frm.set_value("department_name", "Test Department"),
-		() => cur_frm.set_value("leave_block_list", "Test Leave block list"),
-		// save form
-		() => cur_frm.save(),
-		() => frappe.timeout(1),
-		() => assert.equal("Test Department", cur_frm.doc.department_name,
-			'name of department correctly saved'),
-		() => done()
-	]);
-});
diff --git a/erpnext/hr/doctype/designation/test_designation.js b/erpnext/hr/doctype/designation/test_designation.js
deleted file mode 100644
index 00adf82..0000000
--- a/erpnext/hr/doctype/designation/test_designation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-QUnit.module('hr');
-
-QUnit.test("Test: Designation [HR]", function (assert) {
-	assert.expect(1);
-	let done = assert.async();
-
-	frappe.run_serially([
-		// test designation creation
-		() => frappe.set_route("List", "Designation", "List"),
-		() => frappe.new_doc("Designation"),
-		() => frappe.timeout(1),
-		() => frappe.quick_entry.dialog.$wrapper.find('.edit-full').click(),
-		() => frappe.timeout(1),
-		() => cur_frm.set_value("designation_name", "Test Designation"),
-		() => cur_frm.set_value("description", "This designation is just for testing."),
-		// save form
-		() => cur_frm.save(),
-		() => frappe.timeout(1),
-		() => assert.equal("Test Designation", cur_frm.doc.designation_name,
-			'name of designation correctly saved'),
-		() => done()
-	]);
-});
diff --git a/erpnext/hr/doctype/driver/test_driver.js b/erpnext/hr/doctype/driver/test_driver.js
deleted file mode 100644
index ff9f61e..0000000
--- a/erpnext/hr/doctype/driver/test_driver.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Driver", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Driver
-		() => frappe.tests.make('Driver', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_advance/test_employee_advance.js b/erpnext/hr/doctype/employee_advance/test_employee_advance.js
deleted file mode 100644
index 1b9ec6f..0000000
--- a/erpnext/hr/doctype/employee_advance/test_employee_advance.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Advance", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Advance
-		() => frappe.tests.make('Employee Advance', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
index 65792b4..044a5a9 100644
--- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
+++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "creation": "2018-05-09 05:37:18.439763",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -7,6 +8,8 @@
   "activity_name",
   "user",
   "role",
+  "begin_on",
+  "duration",
   "column_break_3",
   "task",
   "task_weight",
@@ -16,12 +19,16 @@
  ],
  "fields": [
   {
+   "columns": 3,
    "fieldname": "activity_name",
    "fieldtype": "Data",
    "in_list_view": 1,
-   "label": "Activity Name"
+   "label": "Activity Name",
+   "reqd": 1
   },
   {
+   "columns": 2,
+   "depends_on": "eval:!doc.role",
    "fieldname": "user",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -29,9 +36,10 @@
    "options": "User"
   },
   {
+   "columns": 1,
+   "depends_on": "eval:!doc.user",
    "fieldname": "role",
    "fieldtype": "Link",
-   "in_list_view": 1,
    "label": "Role",
    "options": "Role"
   },
@@ -67,10 +75,25 @@
    "fieldname": "description",
    "fieldtype": "Text Editor",
    "label": "Description"
+  },
+  {
+   "columns": 2,
+   "fieldname": "duration",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Duration (Days)"
+  },
+  {
+   "columns": 2,
+   "fieldname": "begin_on",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Begin On (Days)"
   }
  ],
  "istable": 1,
- "modified": "2019-06-03 19:22:42.965762",
+ "links": [],
+ "modified": "2021-07-30 15:55:22.470102",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Boarding Activity",
diff --git a/erpnext/hr/doctype/employee_grade/test_employee_grade.js b/erpnext/hr/doctype/employee_grade/test_employee_grade.js
deleted file mode 100644
index d684fb2..0000000
--- a/erpnext/hr/doctype/employee_grade/test_employee_grade.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Grade", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Grade
-		() => frappe.tests.make('Employee Grade', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js b/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js
deleted file mode 100644
index 245cb32..0000000
--- a/erpnext/hr/doctype/employee_health_insurance/test_employee_health_insurance.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Health Insurance", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Health Insurance
-		() => frappe.tests.make('Employee Health Insurance', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index 673e228..fd877a6 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -8,20 +8,24 @@
  "field_order": [
   "job_applicant",
   "job_offer",
-  "employee_name",
-  "employee",
-  "date_of_joining",
-  "boarding_status",
-  "notify_users_by_email",
-  "column_break_7",
   "employee_onboarding_template",
+  "column_break_7",
   "company",
+  "boarding_status",
+  "project",
+  "details_section",
+  "employee",
+  "employee_name",
   "department",
   "designation",
   "employee_grade",
-  "project",
+  "holiday_list",
+  "column_break_13",
+  "date_of_joining",
+  "boarding_begins_on",
   "table_for_activity",
   "activities",
+  "notify_users_by_email",
   "amended_from"
  ],
  "fields": [
@@ -58,7 +62,8 @@
    "fieldname": "date_of_joining",
    "fieldtype": "Date",
    "in_list_view": 1,
-   "label": "Date of Joining"
+   "label": "Date of Joining",
+   "reqd": 1
   },
   {
    "allow_on_submit": 1,
@@ -90,7 +95,8 @@
    "fieldname": "company",
    "fieldtype": "Link",
    "label": "Company",
-   "options": "Company"
+   "options": "Company",
+   "reqd": 1
   },
   {
    "fieldname": "department",
@@ -121,7 +127,8 @@
   },
   {
    "fieldname": "table_for_activity",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Onboarding Activities"
   },
   {
    "allow_on_submit": 1,
@@ -138,11 +145,32 @@
    "options": "Employee Onboarding",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "details_section",
+   "fieldtype": "Section Break",
+   "label": "Employee Details"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "boarding_begins_on",
+   "fieldtype": "Date",
+   "label": "Onboarding Begins On",
+   "reqd": 1
+  },
+  {
+   "fieldname": "holiday_list",
+   "fieldtype": "Link",
+   "label": "Holiday List",
+   "options": "Holiday List"
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-03 18:01:51.097927",
+ "modified": "2021-07-30 14:55:04.560683",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Onboarding",
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js
deleted file mode 100644
index d15cef7..0000000
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Onboarding", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Onboarding
-		() => frappe.tests.make('Employee Onboarding', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 0445270..ea46aa2 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -5,8 +5,9 @@
 
 import frappe
 import unittest
-from frappe.utils import nowdate
+from frappe.utils import getdate
 from erpnext.hr.doctype.employee_onboarding.employee_onboarding import make_employee
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
 from erpnext.hr.doctype.employee_onboarding.employee_onboarding import IncompleteTaskError
 from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
 
@@ -46,7 +47,7 @@
 		onboarding.reload()
 		employee = make_employee(onboarding.name)
 		employee.first_name = employee.employee_name
-		employee.date_of_joining = nowdate()
+		employee.date_of_joining = getdate()
 		employee.date_of_birth = '1990-05-08'
 		employee.gender = 'Female'
 		employee.insert()
@@ -82,11 +83,14 @@
 def create_employee_onboarding():
 	applicant = get_job_applicant()
 	job_offer = get_job_offer(applicant.name)
+	holiday_list = make_holiday_list()
 
 	onboarding = frappe.new_doc('Employee Onboarding')
 	onboarding.job_applicant = applicant.name
 	onboarding.job_offer = job_offer.name
+	onboarding.date_of_joining = onboarding.boarding_begins_on = getdate()
 	onboarding.company = '_Test Company'
+	onboarding.holiday_list = holiday_list
 	onboarding.designation = 'Researcher'
 	onboarding.append('activities', {
 		'activity_name': 'Assign ID Card',
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json
deleted file mode 100644
index 4e91b72..0000000
--- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.json
+++ /dev/null
@@ -1,290 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-05-09 05:37:18.439763", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "activity_name", 
-   "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": "Activity Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "user", 
-   "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": "User", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "User", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "role", 
-   "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": "Role", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Role", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval: doc.parenttype == \"Employee Onboarding\"", 
-   "fieldname": "completed", 
-   "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": "Completed", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "required_for_employee_creation", 
-   "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": "Required for Employee Creation", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_6", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "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-05-09 06:15:41.768236", 
- "modified_by": "Administrator", 
- "module": "HR", 
- "name": "Employee Onboarding Activity", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py b/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py
deleted file mode 100644
index d170631..0000000
--- a/erpnext/hr/doctype/employee_onboarding_activity/employee_onboarding_activity.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.model.document import Document
-
-class EmployeeOnboardingActivity(Document):
-	pass
diff --git a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js b/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js
deleted file mode 100644
index 10912ed..0000000
--- a/erpnext/hr/doctype/employee_onboarding_template/test_employee_onboarding_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Onboarding Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Onboarding Template
-		() => frappe.tests.make('Employee Onboarding Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js b/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js
deleted file mode 100644
index 5f0a5ba..0000000
--- a/erpnext/hr/doctype/employee_promotion/test_employee_promotion.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Promotion", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Promotion
-		() => frappe.tests.make('Employee Promotion', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.js b/erpnext/hr/doctype/employee_referral/employee_referral.js
index 9c99bbb..8722019 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.js
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.js
@@ -43,8 +43,6 @@
 			});
 		}
 
-
-
 	},
 	create_job_applicant: function(frm) {
 		frappe.model.open_mapped_doc({
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index c10da5c..c240493 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -15,6 +15,7 @@
   "company",
   "boarding_status",
   "resignation_letter_date",
+  "boarding_begins_on",
   "project",
   "table_for_activity",
   "employee_separation_template",
@@ -144,11 +145,17 @@
    "options": "Employee Separation",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fieldname": "boarding_begins_on",
+   "fieldtype": "Date",
+   "label": "Separation Begins On",
+   "reqd": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-03 18:02:54.007313",
+ "modified": "2021-07-30 14:03:51.218791",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Separation",
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.js b/erpnext/hr/doctype/employee_separation/test_employee_separation.js
deleted file mode 100644
index d6c6359..0000000
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Separation", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Separation
-		() => frappe.tests.make('Employee Separation', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index d63501a..2c11cbb 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -4,6 +4,7 @@
 from __future__ import unicode_literals
 
 import frappe
+from frappe.utils import getdate
 import unittest
 
 test_dependencies = ['Employee Onboarding']
@@ -34,9 +35,10 @@
 			doc.delete()
 
 def create_employee_separation():
-	employee = frappe.db.get_value('Employee', {'status': 'Active'})
+	employee = frappe.db.get_value('Employee', {'status': 'Active', 'company': '_Test Company'})
 	separation = frappe.new_doc('Employee Separation')
 	separation.employee = employee
+	separation.boarding_begins_on = getdate()
 	separation.company = '_Test Company'
 	separation.append('activities', {
 		'activity_name': 'Deactivate Employee',
diff --git a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js b/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js
deleted file mode 100644
index 66fd450..0000000
--- a/erpnext/hr/doctype/employee_separation_template/test_employee_separation_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Separation Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Separation Template
-		() => frappe.tests.make('Employee Separation Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js b/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js
deleted file mode 100644
index 05a3e1a..0000000
--- a/erpnext/hr/doctype/employee_transfer/test_employee_transfer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Transfer", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Transfer
-		() => frappe.tests.make('Employee Transfer', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js b/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js
deleted file mode 100644
index 00a334a..0000000
--- a/erpnext/hr/doctype/employee_transfer_property/test_employee_transfer_property.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Transfer Property", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Transfer Property
-		() => frappe.tests.make('Employee Transfer Property', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 95e2806..4dc089c 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -77,7 +77,7 @@
 		self.make_gl_entries()
 
 		if self.is_paid:
-			update_reimbursed_amount(self)
+			update_reimbursed_amount(self, self.grand_total)
 
 		self.set_status(update=True)
 		self.update_claimed_amount_in_employee_advance()
@@ -89,7 +89,7 @@
 			self.make_gl_entries(cancel=True)
 
 		if self.is_paid:
-			update_reimbursed_amount(self)
+			update_reimbursed_amount(self, -1 * self.grand_total)
 
 		self.update_claimed_amount_in_employee_advance()
 
@@ -270,20 +270,10 @@
 			if not expense.default_account or not validate:
 				expense.default_account = get_expense_claim_account(expense.expense_type, self.company)["account"]
 
-def update_reimbursed_amount(doc, jv=None):
+def update_reimbursed_amount(doc, amount):
 
-	condition = ""
-
-	if jv:
-		condition += "and voucher_no = '{0}'".format(jv)
-
-	amt = frappe.db.sql("""select ifnull(sum(debit_in_account_currency), 0) - ifnull(sum(credit_in_account_currency), 0)as amt
-		from `tabGL Entry` where against_voucher_type = 'Expense Claim' and against_voucher = %s
-		and party = %s {condition}""".format(condition=condition), #nosec
-		(doc.name, doc.employee) ,as_dict=1)[0].amt
-
-	doc.total_amount_reimbursed = amt
-	frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", amt)
+	doc.total_amount_reimbursed += amount
+	frappe.db.set_value("Expense Claim", doc.name , "total_amount_reimbursed", doc.total_amount_reimbursed)
 
 	doc.set_status()
 	frappe.db.set_value("Expense Claim", doc.name , "status", doc.status)
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index c2bd1e9..b5fc1fb 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -4,7 +4,7 @@
 
 import frappe
 import unittest
-from frappe.utils import random_string, nowdate
+from frappe.utils import random_string, nowdate, flt
 from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
 from erpnext.accounts.doctype.account.test_account import create_account
 from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -138,6 +138,31 @@
 		expense_claim.submit()
 		frappe.set_user("Administrator")
 
+	def test_multiple_payment_entries_against_expense(self):
+		# Creating expense claim
+		payable_account = get_payable_account("_Test Company")
+		expense_claim = make_expense_claim(payable_account, 5500, 5500, "_Test Company", "Travel Expenses - _TC")
+		expense_claim.save()
+		expense_claim.submit()
+		
+		# Payment entry 1: paying 500
+		make_payment_entry(expense_claim, payable_account,500)
+		outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+		self.assertEqual(outstanding_amount, 5000)
+		self.assertEqual(total_amount_reimbursed, 500)
+		
+		# Payment entry 1: paying 2000
+		make_payment_entry(expense_claim, payable_account,2000)
+		outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+		self.assertEqual(outstanding_amount, 3000)
+		self.assertEqual(total_amount_reimbursed, 2500)
+		
+		# Payment entry 1: paying 3000		
+		make_payment_entry(expense_claim, payable_account,3000)
+		outstanding_amount, total_amount_reimbursed = get_outstanding_and_total_reimbursed_amounts(expense_claim)
+		self.assertEqual(outstanding_amount, 0)
+		self.assertEqual(total_amount_reimbursed, 5500)
+
 
 def get_payable_account(company):
 	return frappe.get_cached_value('Company', company, 'default_payable_account')
@@ -191,3 +216,23 @@
 		return expense_claim
 	expense_claim.submit()
 	return expense_claim
+
+def get_outstanding_and_total_reimbursed_amounts(expense_claim):
+	outstanding_amount = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_sanctioned_amount")) - \
+			flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
+	total_amount_reimbursed = flt(frappe.db.get_value("Expense Claim", expense_claim.name, "total_amount_reimbursed"))
+	
+	return outstanding_amount,total_amount_reimbursed
+
+def make_payment_entry(expense_claim, payable_account, amt):
+	from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+
+	pe = get_payment_entry("Expense Claim", expense_claim.name, bank_account="_Test Bank USD - _TC", bank_amount=amt)
+	pe.reference_no = "1"
+	pe.reference_date = nowdate()
+	pe.source_exchange_rate = 1
+	pe.paid_to = payable_account
+	pe.references[0].allocated_amount = amt
+	pe.insert()
+	pe.submit() 
+	
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/hr/doctype/full_and_final_asset/__init__.py
similarity index 100%
rename from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
rename to erpnext/hr/doctype/full_and_final_asset/__init__.py
diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js
new file mode 100644
index 0000000..1965b46
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Full and Final Asset', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json
new file mode 100644
index 0000000..3ad8335
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.json
@@ -0,0 +1,64 @@
+{
+ "actions": [],
+ "creation": "2021-06-28 13:36:58.658985",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "reference",
+  "asset_name",
+  "date",
+  "status",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "reference",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Reference",
+   "options": "Asset Movement",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Owned\nReturned",
+   "reqd": 1
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "label": "Description"
+  },
+  {
+   "fieldname": "asset_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Asset Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "date",
+   "fieldtype": "Datetime",
+   "in_list_view": 1,
+   "label": "Date",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-15 15:17:31.309834",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Full and Final Asset",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py
new file mode 100644
index 0000000..2337224
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_asset/full_and_final_asset.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class FullandFinalAsset(Document):
+	pass
diff --git a/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py b/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py
new file mode 100644
index 0000000..6d59c99
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_asset/test_full_and_final_asset.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestFullandFinalAsset(unittest.TestCase):
+	pass
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/hr/doctype/full_and_final_outstanding_statement/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/hr/doctype/full_and_final_outstanding_statement/__init__.py
diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json
new file mode 100644
index 0000000..be242e2
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.json
@@ -0,0 +1,96 @@
+{
+ "actions": [],
+ "creation": "2021-06-28 13:32:02.167317",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "component",
+  "reference_document_type",
+  "reference_document",
+  "account",
+  "paid_via_salary_slip",
+  "column_break_4",
+  "amount",
+  "status",
+  "remark"
+ ],
+ "fields": [
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "columns": 2,
+   "default": "Unsettled",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Status",
+   "options": "Settled\nUnsettled"
+  },
+  {
+   "fieldname": "remark",
+   "fieldtype": "Small Text",
+   "label": "Remark"
+  },
+  {
+   "columns": 2,
+   "depends_on": "reference_document_type",
+   "fieldname": "reference_document",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Reference Document",
+   "mandatory_depends_on": "reference_document_type",
+   "options": "reference_document_type",
+   "search_index": 1
+  },
+  {
+   "columns": 2,
+   "fieldname": "component",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Component",
+   "reqd": 1
+  },
+  {
+   "fieldname": "account",
+   "fieldtype": "Link",
+   "label": "Account",
+   "options": "Account"
+  },
+  {
+   "columns": 2,
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Amount"
+  },
+  {
+   "columns": 2,
+   "fieldname": "reference_document_type",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Reference Document Type",
+   "options": "DocType"
+  },
+  {
+   "default": "0",
+   "fieldname": "paid_via_salary_slip",
+   "fieldtype": "Check",
+   "label": "Paid via Salary Slip"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-20 16:59:34.447934",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Full and Final Outstanding Statement",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py
new file mode 100644
index 0000000..d53cd91
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_outstanding_statement/full_and_final_outstanding_statement.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class FullandFinalOutstandingStatement(Document):
+	pass
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/hr/doctype/full_and_final_statement/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/hr/doctype/full_and_final_statement/__init__.py
diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js
new file mode 100644
index 0000000..074d85b
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.js
@@ -0,0 +1,115 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Full and Final Statement', {
+	refresh: function(frm) {
+		frm.events.set_queries(frm, "payables");
+		frm.events.set_queries(frm, "receivables");
+
+		if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") {
+			frm.add_custom_button(__("Create Journal Entry"), function () {
+				frm.events.create_journal_entry(frm);
+			});
+		}
+	},
+
+	set_queries: function(frm, type) {
+		frm.set_query("reference_document_type", type, function () {
+			let modules = ["HR", "Payroll", "Loan Management"];
+			return {
+				filters: {
+					istable: 0,
+					issingle: 0,
+					module: ["In", modules]
+				}
+			};
+		});
+
+		let filters = {};
+
+		frm.set_query('reference_document', type, function(doc, cdt, cdn) {
+			let fnf_doc = frappe.get_doc(cdt, cdn);
+
+			frappe.model.with_doctype(fnf_doc.reference_document_type, function() {
+				if (frappe.model.is_tree(fnf_doc.reference_document_type)) {
+					filters['is_group'] = 0;
+				}
+
+				if (frappe.meta.has_field(fnf_doc.reference_document_type, 'company')) {
+					filters['company'] = frm.doc.company;
+				}
+
+				if (frappe.meta.has_field(fnf_doc.reference_document_type, 'employee')) {
+					filters['employee'] = frm.doc.employee;
+				}
+			});
+
+			return {
+				filters: filters
+			};
+		});
+	},
+
+	employee: function(frm) {
+		frm.events.get_outstanding_statements(frm);
+	},
+
+	get_outstanding_statements: function(frm) {
+		if (frm.doc.employee) {
+			frappe.call({
+				method: "get_outstanding_statements",
+				doc: frm.doc,
+				callback: function() {
+					frm.refresh();
+				}
+			});
+		}
+	},
+
+	create_journal_entry: function(frm) {
+		frappe.call({
+			method: "create_journal_entry",
+			doc: frm.doc,
+			callback: function(r) {
+				var doclist = frappe.model.sync(r.message);
+				frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+			}
+		});
+	}
+});
+
+frappe.ui.form.on("Full and Final Outstanding Statement", {
+	reference_document: function(frm, cdt, cdn) {
+		var child = locals[cdt][cdn];
+		if (child.reference_document_type && child.reference_document) {
+			frappe.call({
+				method: "erpnext.hr.doctype.full_and_final_statement.full_and_final_statement.get_account_and_amount",
+				args: {
+					ref_doctype: child.reference_document_type,
+					ref_document: child.reference_document
+				},
+				callback: function(r) {
+					if (r.message) {
+						frappe.model.set_value(cdt, cdn, "account", r.message[0]);
+						frappe.model.set_value(cdt, cdn, "amount", r.message[1]);
+					}
+				}
+			});
+		}
+	},
+
+	amount: function(frm) {
+		var total_payable_amount = 0;
+		var total_receivable_amount = 0;
+
+		frm.doc.payables.forEach(element => {
+			total_payable_amount = total_payable_amount + element.amount;
+		});
+
+		frm.doc.receivables.forEach(element => {
+			total_receivable_amount = total_receivable_amount + element.amount;
+		});
+		frm.set_value("total_payable_amount", flt(total_payable_amount));
+		frm.set_value("total_receivable_amount", flt(total_receivable_amount));
+	}
+});
diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json
new file mode 100644
index 0000000..ebcf36d
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.json
@@ -0,0 +1,231 @@
+{
+ "actions": [],
+ "autoname": "HR-FNF-.YYYY.-.#####",
+ "creation": "2021-06-28 13:17:36.050459",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "employee",
+  "employee_name",
+  "transaction_date",
+  "column_break_12",
+  "company",
+  "status",
+  "amended_from",
+  "employee_details_section",
+  "date_of_joining",
+  "relieving_date",
+  "column_break_4",
+  "designation",
+  "department",
+  "section_break_8",
+  "payables",
+  "section_break_10",
+  "receivables",
+  "totals_section",
+  "total_payable_amount",
+  "column_break_21",
+  "total_receivable_amount",
+  "section_break_15",
+  "assets_allocated"
+ ],
+ "fields": [
+  {
+   "fieldname": "employee",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Employee",
+   "options": "Employee",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.employee_name",
+   "fieldname": "employee_name",
+   "fieldtype": "Data",
+   "label": "Employee Name",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "employee.designation",
+   "fieldname": "designation",
+   "fieldtype": "Link",
+   "label": "Designation",
+   "options": "Designation",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "Unpaid",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "label": "Status",
+   "options": "Paid\nUnpaid",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "employee.department",
+   "fieldname": "department",
+   "fieldtype": "Link",
+   "label": "Department",
+   "options": "Department",
+   "read_only": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Full and Final Statement",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_8",
+   "fieldtype": "Section Break",
+   "label": "Payables"
+  },
+  {
+   "fieldname": "section_break_10",
+   "fieldtype": "Section Break",
+   "label": "Receivables"
+  },
+  {
+   "fieldname": "assets_allocated",
+   "fieldtype": "Table",
+   "options": "Full and Final Asset"
+  },
+  {
+   "fetch_from": "employee.relieving_date",
+   "fieldname": "relieving_date",
+   "fieldtype": "Date",
+   "label": "Relieving Date ",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fetch_from": "employee.date_of_joining",
+   "fieldname": "date_of_joining",
+   "fieldtype": "Date",
+   "label": "Date of Joining",
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_15",
+   "fieldtype": "Section Break",
+   "label": "Assets Allocated"
+  },
+  {
+   "fetch_from": "employee.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Company",
+   "options": "Company",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_12",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "payables",
+   "fieldtype": "Table",
+   "options": "Full and Final Outstanding Statement"
+  },
+  {
+   "fieldname": "receivables",
+   "fieldtype": "Table",
+   "options": "Full and Final Outstanding Statement"
+  },
+  {
+   "fieldname": "employee_details_section",
+   "fieldtype": "Section Break",
+   "label": "Employee Details"
+  },
+  {
+   "fieldname": "transaction_date",
+   "fieldtype": "Date",
+   "in_standard_filter": 1,
+   "label": "Transaction Date",
+   "reqd": 1
+  },
+  {
+   "fieldname": "totals_section",
+   "fieldtype": "Section Break",
+   "label": "Totals"
+  },
+  {
+   "fieldname": "total_payable_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Total Payable Amount",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_21",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "total_receivable_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Total Receivable Amount",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2021-08-30 21:11:09.892560",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Full and Final Statement",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "employee_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
new file mode 100644
index 0000000..4c98cf1
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement.py
@@ -0,0 +1,176 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.utils import get_link_to_form, today, flt
+from frappe.model.document import Document
+
+class FullandFinalStatement(Document):
+	def validate(self):
+		self.get_outstanding_statements()
+		if self.docstatus == 1:
+			self.validate_settlement("payables")
+			self.validate_settlement("receivables")
+			self.validate_asset()
+
+	def validate_settlement(self, component_type):
+		for data in self.get(component_type, []):
+			if data.status == "Unsettled":
+				frappe.throw(_("Settle all Payables and Receivables before submission"))
+
+	def validate_asset(self):
+		for data in self.assets_allocated:
+			if data.status == "Owned":
+				frappe.throw(_("All allocated assets should be returned before submission"))
+
+	@frappe.whitelist()
+	def get_outstanding_statements(self):
+		if self.relieving_date:
+			if not len(self.get("payables", [])):
+				components = self.get_payable_component()
+				self.create_component_row(components, "payables")
+			if not len(self.get("receivables", [])):
+				components = self.get_receivable_component()
+				self.create_component_row(components, "receivables")
+
+			if not len(self.get("assets_allocated", [])):
+				for data in self.get_assets_movement():
+					self.append("assets_allocated", data)
+		else:
+			frappe.throw(_("Set Relieving Date for Employee: {0}").format(get_link_to_form("Employee", self.employee)))
+
+	def create_component_row(self, components, component_type):
+		for component in components:
+			self.append(component_type, {
+				"status": "Unsettled",
+				"reference_document_type": component if component != "Bonus" else "Additional Salary",
+				"component": component
+			})
+
+
+	def get_payable_component(self):
+		return [
+			"Salary Slip",
+			"Gratuity",
+			"Expense Claim",
+			"Bonus",
+			"Leave Encashment",
+		]
+
+	def get_receivable_component(self):
+		return [
+			"Loan",
+			"Employee Advance",
+		]
+
+	def get_assets_movement(self):
+		asset_movements = frappe.get_all("Asset Movement Item",
+			filters = {"docstatus": 1},
+			fields = ["asset", "from_employee", "to_employee", "parent", "asset_name"],
+			or_filters = {
+				"from_employee": self.employee,
+				"to_employee": self.employee
+			}
+		)
+
+		data = []
+		inward_movements = []
+		outward_movements = []
+		for movement in asset_movements:
+			if movement.to_employee and movement.to_employee == self.employee:
+				inward_movements.append(movement)
+
+			if movement.from_employee and movement.from_employee == self.employee:
+				outward_movements.append(movement)
+
+		for movement in inward_movements:
+			outwards_count = [movement.asset for movement in outward_movements].count(movement.asset)
+			inwards_counts = [movement.asset for movement in inward_movements].count(movement.asset)
+
+			if inwards_counts > outwards_count:
+				data.append({
+					"reference": movement.parent,
+					"asset_name": movement.asset_name,
+					"date": frappe.db.get_value("Asset Movement", movement.parent, "transaction_date"),
+					"status": "Owned"
+				})
+		return data
+
+	@frappe.whitelist()
+	def create_journal_entry(self):
+		precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
+		jv = frappe.new_doc("Journal Entry")
+		jv.company = self.company
+		jv.voucher_type = "Bank Entry"
+		jv.posting_date = today()
+
+		difference = self.total_payable_amount - self.total_receivable_amount
+
+		for data in self.payables:
+			if data.amount > 0 and not data.paid_via_salary_slip:
+				account_dict = {
+					"account": data.account,
+					"debit_in_account_currency": flt(data.amount, precision)
+				}
+				if data.reference_document_type == "Expense Claim":
+					account_dict["party_type"] = "Employee"
+					account_dict["party"] = self.employee
+
+				jv.append("accounts", account_dict)
+
+		for data in self.receivables:
+			if data.amount > 0:
+				account_dict = {
+					"account": data.account,
+					"credit_in_account_currency": flt(data.amount, precision)
+				}
+				if data.reference_document_type == "Employee Advance":
+					account_dict["party_type"] = "Employee"
+					account_dict["party"] = self.employee
+
+				jv.append("accounts", account_dict)
+
+		jv.append("accounts", {
+			"credit_in_account_currency": difference if difference > 0 else 0,
+			"debit_in_account_currency": -(difference) if difference < 0 else 0,
+			"reference_type": self.doctype,
+			"reference_name": self.name
+		})
+		return jv
+
+@frappe.whitelist()
+def get_account_and_amount(ref_doctype, ref_document):
+	if not ref_doctype or not ref_document:
+		return None
+
+	if ref_doctype == "Salary Slip":
+		salary_details = frappe.db.get_value("Salary Slip", ref_document, ["payroll_entry", "net_pay"], as_dict=1)
+		amount = salary_details.net_pay
+		payable_account = frappe.db.get_value("Payroll Entry", salary_details.payroll_entry, "payroll_payable_account") if salary_details.payroll_entry else None
+		return [payable_account, amount]
+
+	if ref_doctype == "Gratuity":
+		payable_account, amount = frappe.db.get_value("Gratuity", ref_document, ["payable_account", "amount"])
+		return [payable_account, amount]
+
+	if ref_doctype == "Expense Claim":
+		details = frappe.db.get_value("Expense Claim", ref_document,
+			["payable_account", "grand_total", "total_amount_reimbursed", "total_advance_amount"], as_dict=True)
+		payable_account = details.payable_account
+		amount = details.grand_total - (details.total_amount_reimbursed + details.total_advance_amount)
+		return [payable_account, amount]
+
+	if ref_doctype == "Loan":
+		details = frappe.db.get_value("Loan", ref_document,
+			["payment_account", "total_payment", "total_amount_paid"], as_dict=1)
+		payment_account = details.payment_account
+		amount = details.total_payment - details.total_amount_paid
+		return [payment_account, amount]
+
+	if ref_doctype == "Employee Advance":
+		details = frappe.db.get_value("Employee Advance", ref_document,
+			["advance_account","paid_amount", "claimed_amount", "return_amount"], as_dict = 1)
+		payment_account = details.advance_account
+		amount = details.paid_amount - (details.claimed_amount + details.return_amount)
+		return [payment_account, amount]
diff --git a/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js
new file mode 100644
index 0000000..4aedec7
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_statement/full_and_final_statement_list.js
@@ -0,0 +1,11 @@
+frappe.listview_settings["Full and Final Statement"] = {
+	get_indicator: function(doc) {
+		var colors = {
+			"Draft": "red",
+			"Unpaid": "orange",
+			"Paid": "green",
+			"Cancelled": "red"
+		};
+		return [__(doc.status), colors[doc.status], "status,=," + doc.status];
+	}
+};
diff --git a/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
new file mode 100644
index 0000000..8ecc129
--- /dev/null
+++ b/erpnext/hr/doctype/full_and_final_statement/test_full_and_final_statement.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from erpnext.hr.doctype.employee.test_employee import make_employee
+from erpnext.assets.doctype.asset.test_asset import create_asset_data
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from frappe.utils import today, add_days
+import unittest
+
+class TestFullandFinalStatement(unittest.TestCase):
+
+	def setUp(self):
+		create_asset_data()
+
+	def tearDown(self):
+		frappe.db.sql("Delete from `tabFull and Final Statement`")
+		frappe.db.sql("Delete from `tabAsset`")
+		frappe.db.sql("Delete from `tabAsset Movement`")
+
+	def test_check_bootstraped_data_asset_movement_and_jv_creation(self):
+		employee = make_employee("test_fnf@example.com", company="_Test Company")
+		movement = create_asset_movement(employee)
+		frappe.db.set_value("Employee", employee, "relieving_date", add_days(today(), 30))
+		fnf = create_full_and_final_statement(employee)
+
+		payables_bootstraped_component = ["Salary Slip", "Gratuity",
+			"Expense Claim", "Bonus", "Leave Encashment"]
+
+		receivable_bootstraped_component = ["Loan", "Employee Advance"]
+
+		#checking payable s and receivables bootstraped value
+		self.assertEqual([payable.component for payable in fnf.payables], payables_bootstraped_component)
+		self.assertEqual([receivable.component for receivable in fnf.receivables], receivable_bootstraped_component)
+
+		#checking allocated asset
+		self.assertIn(movement, [asset.reference for asset in fnf.assets_allocated])
+
+def create_full_and_final_statement(employee):
+	fnf = frappe.new_doc("Full and Final Statement")
+	fnf.employee = employee
+	fnf.transaction_date = today()
+	fnf.save()
+	return fnf
+
+def create_asset_movement(employee):
+	asset_name = create_asset()
+	movement = frappe.new_doc("Asset Movement")
+	movement.company = "_Test Company"
+	movement.purpose = "Issue"
+	movement.transaction_date = today()
+
+	movement.append("assets", {
+		"asset": asset_name,
+		"to_employee": employee
+	})
+
+	movement.save()
+	movement.submit()
+	return movement.name
+
+def create_asset():
+	pr = make_purchase_receipt(item_code="Macbook Pro",
+			qty=1, rate=100000.0, location="Test Location")
+
+	asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
+	asset = frappe.get_doc("Asset", asset_name)
+	asset.calculate_depreciation = 0
+	asset.available_for_use_date = today()
+	asset.submit()
+	return asset_name
diff --git a/erpnext/hr/doctype/hr_settings/test_hr_settings.js b/erpnext/hr/doctype/hr_settings/test_hr_settings.js
deleted file mode 100644
index f32640b..0000000
--- a/erpnext/hr/doctype/hr_settings/test_hr_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: HR Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new HR Settings
-		() => frappe.tests.make('HR Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js b/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js
deleted file mode 100644
index 6587909..0000000
--- a/erpnext/hr/doctype/identification_document_type/test_identification_document_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Identification Document Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Identification Document Type
-		() => frappe.tests.make('Identification Document Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js b/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js
deleted file mode 100644
index c093928..0000000
--- a/erpnext/hr/doctype/job_applicant_source/test_job_applicant_source.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Job Applicant Source", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Job Applicant Source
-		() => frappe.tests.make('Job Applicant Source', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 93fb19f..2e37c13 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -662,26 +662,30 @@
 
 @frappe.whitelist()
 def get_events(start, end, filters=None):
+	from frappe.desk.reportview import get_filters_cond
 	events = []
 
-	employee = frappe.db.get_value("Employee", {"user_id": frappe.session.user}, ["name", "company"],
-		as_dict=True)
+	employee = frappe.db.get_value("Employee",
+		filters={"user_id": frappe.session.user},
+		fieldname=["name", "company"],
+		as_dict=True
+	)
+
 	if employee:
 		employee, company = employee.name, employee.company
 	else:
-		employee=''
-		company=frappe.db.get_value("Global Defaults", None, "default_company")
+		employee = ''
+		company = frappe.db.get_value("Global Defaults", None, "default_company")
 
-	from frappe.desk.reportview import get_filters_cond
 	conditions = get_filters_cond("Leave Application", filters, [])
 	# show department leaves for employee
 	if "Employee" in frappe.get_roles():
 		add_department_leaves(events, start, end, employee, company)
 
 	add_leaves(events, start, end, conditions)
-
 	add_block_dates(events, start, end, employee, company)
 	add_holidays(events, start, end, employee, company)
+
 	return events
 
 def add_department_leaves(events, start, end, employee, company):
@@ -697,26 +701,37 @@
 	filter_conditions = " and employee in (\"%s\")" % '", "'.join(department_employees)
 	add_leaves(events, start, end, filter_conditions=filter_conditions)
 
+
 def add_leaves(events, start, end, filter_conditions=None):
+	from frappe.desk.reportview import build_match_conditions
 	conditions = []
 
-
 	if not cint(frappe.db.get_value("HR Settings", None, "show_leaves_of_all_department_members_in_calendar")):
-		from frappe.desk.reportview import build_match_conditions
 		match_conditions = build_match_conditions("Leave Application")
 
 		if match_conditions:
 			conditions.append(match_conditions)
 
-	query = """select name, from_date, to_date, employee_name, half_day,
-		status, employee, docstatus
-		from `tabLeave Application` where
-		from_date <= %(end)s and to_date >= %(start)s <= to_date
-		and docstatus < 2
-		and status!='Rejected' """
+	query = """SELECT
+		docstatus,
+		name,
+		employee,
+		employee_name,
+		leave_type,
+		from_date,
+		to_date,
+		half_day,
+		status,
+		color
+	FROM `tabLeave Application`
+	WHERE
+		from_date <= %(end)s AND to_date >= %(start)s <= to_date
+		AND docstatus < 2
+		AND status != 'Rejected'
+	"""
 
 	if conditions:
-		query += ' and ' + ' and '.join(conditions)
+		query += ' AND ' + ' AND '.join(conditions)
 
 	if filter_conditions:
 		query += filter_conditions
@@ -729,11 +744,13 @@
 			"to_date": d.to_date,
 			"docstatus": d.docstatus,
 			"color": d.color,
-			"title": cstr(d.employee_name) + (' ' + _('(Half Day)') if d.half_day else ''),
+			"all_day": int(not d.half_day),
+			"title": cstr(d.employee_name) + f' ({cstr(d.leave_type)})' + (' ' + _('(Half Day)') if d.half_day else ''),
 		}
 		if e not in events:
 			events.append(e)
 
+
 def add_block_dates(events, start, end, employee, company):
 	# block days
 	from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
diff --git a/erpnext/hr/doctype/leave_application/leave_application_calendar.js b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
index 31faadb..0ba0285 100644
--- a/erpnext/hr/doctype/leave_application/leave_application_calendar.js
+++ b/erpnext/hr/doctype/leave_application/leave_application_calendar.js
@@ -7,7 +7,9 @@
 		"end": "to_date",
 		"id": "name",
 		"title": "title",
-		"docstatus": 1
+		"docstatus": 1,
+		"color": "color",
+		"allDay": "all_day"
 	},
 	options: {
 		header: {
diff --git a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js b/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js
deleted file mode 100644
index cafd960..0000000
--- a/erpnext/hr/doctype/leave_encashment/test_leave_encashment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Encashment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Leave Encashment
-		() => frappe.tests.make('Leave Encashment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/leave_period/test_leave_period.js b/erpnext/hr/doctype/leave_period/test_leave_period.js
deleted file mode 100644
index ec0a809..0000000
--- a/erpnext/hr/doctype/leave_period/test_leave_period.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Period", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Leave Period
-		() => frappe.tests.make('Leave Period', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.js b/erpnext/hr/doctype/leave_policy/test_leave_policy.js
deleted file mode 100644
index 5404a63..0000000
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Policy", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Leave Policy
-		() => frappe.tests.make('Leave Policy', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js b/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js
deleted file mode 100644
index 1c8995b..0000000
--- a/erpnext/hr/doctype/leave_policy_detail/test_leave_policy_detail.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Leave Policy Detail", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Leave Policy Detail
-		() => frappe.tests.make('Leave Policy Detail', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js b/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js
deleted file mode 100644
index 936c21c..0000000
--- a/erpnext/hr/doctype/purpose_of_travel/test_purpose_of_travel.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Purpose of Travel", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Purpose of Travel
-		() => frappe.tests.make('Purpose of Travel', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js b/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js
deleted file mode 100644
index 7727287..0000000
--- a/erpnext/hr/doctype/shift_assignment/test_shift_assignment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shift Assignment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shift Assignment
-		() => frappe.tests.make('Shift Assignment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.js b/erpnext/hr/doctype/shift_request/test_shift_request.js
deleted file mode 100644
index 9c8cd70..0000000
--- a/erpnext/hr/doctype/shift_request/test_shift_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shift Request", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shift Request
-		() => frappe.tests.make('Shift Request', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.js b/erpnext/hr/doctype/shift_type/test_shift_type.js
deleted file mode 100644
index 846f931..0000000
--- a/erpnext/hr/doctype/shift_type/test_shift_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shift Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shift Type
-		() => frappe.tests.make('Shift Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js b/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js
deleted file mode 100644
index 64320bc..0000000
--- a/erpnext/hr/doctype/staffing_plan/test_staffing_plan.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Staffing Plan", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Staffing Plan
-		() => frappe.tests.make('Staffing Plan', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/training_program/test_training_program.js b/erpnext/hr/doctype/training_program/test_training_program.js
deleted file mode 100644
index 3a62b2f..0000000
--- a/erpnext/hr/doctype/training_program/test_training_program.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Training Program", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Training Program
-		() => frappe.tests.make('Training Program', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/training_result/test_training_result.js b/erpnext/hr/doctype/training_result/test_training_result.js
deleted file mode 100644
index cb1d7fb..0000000
--- a/erpnext/hr/doctype/training_result/test_training_result.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Training Result", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Training Result
-		() => frappe.tests.make('Training Result', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/travel_request/test_travel_request.js b/erpnext/hr/doctype/travel_request/test_travel_request.js
deleted file mode 100644
index 7e64591..0000000
--- a/erpnext/hr/doctype/travel_request/test_travel_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Travel Request", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Travel Request
-		() => frappe.tests.make('Travel Request', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/vehicle/test_vehicle.js b/erpnext/hr/doctype/vehicle/test_vehicle.js
deleted file mode 100644
index 4d40cce..0000000
--- a/erpnext/hr/doctype/vehicle/test_vehicle.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Vehicle", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Vehicle
-		() => frappe.tests.make('Vehicle', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index 575fa7b..9c5d0c1 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -224,6 +224,17 @@
    "type": "Link"
   },
   {
+   "dependencies": "Employee",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Full and Final Statement",
+   "link_count": 0,
+   "link_to": "Full and Final Statement",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Shift Management",
@@ -931,7 +942,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-08-05 12:15:59.842918",
+ "modified": "2021-08-31 12:18:59.842918",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR",
diff --git a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js b/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js
deleted file mode 100644
index fba3e09..0000000
--- a/erpnext/hub_node/doctype/marketplace_settings/test_marketplace_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Marketplace Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Marketplace Settings
-		() => frappe.tests.make('Marketplace Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 57aec2e..b67b4c4 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -107,12 +107,13 @@
 				lia = frappe.db.get_value('Loan Interest Accrual', {'process_loan_interest_accrual':
 					process}, ['name', 'interest_amount', 'payable_principal_amount'], as_dict=1)
 
-				self.append('repayment_details', {
-					'loan_interest_accrual': lia.name,
-					'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision),
-					'paid_principal_amount': 0.0,
-					'accrual_type': 'Repayment'
-				})
+				if lia:
+					self.append('repayment_details', {
+						'loan_interest_accrual': lia.name,
+						'paid_interest_amount': flt(self.total_interest_paid - self.interest_payable, precision),
+						'paid_principal_amount': 0.0,
+						'accrual_type': 'Repayment'
+					})
 
 	def update_paid_amount(self):
 		loan = frappe.get_doc("Loan", self.against_loan)
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index cd7694b..61f1778 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -71,7 +71,7 @@
 				- flt(loan.total_principal_paid)
 
 		pledged_securities = get_pledged_security_qty(loan.name)
-		ltv_ratio = ''
+		ltv_ratio = 0.0
 		security_value = 0.0
 
 		for security, qty in pledged_securities.items():
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js
deleted file mode 100644
index e0f05b1..0000000
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Maintenance Schedule", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Maintenance Schedule
-		() => frappe.tests.make('Maintenance Schedule', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index 53ecdf5..6b3f184 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -73,12 +73,16 @@
 		if (this.frm.doc.docstatus === 0) {
 			this.frm.add_custom_button(__('Maintenance Schedule'),
 				function () {
+					if (!me.frm.doc.customer) {
+						frappe.msgprint(__('Please select Customer first'));
+						return;
+					}
 					erpnext.utils.map_current_doc({
 						method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.make_maintenance_visit",
 						source_doctype: "Maintenance Schedule",
 						target: me.frm,
 						setters: {
-							customer: me.frm.doc.customer || undefined,
+							customer: me.frm.doc.customer,
 						},
 						get_query_filters: {
 							docstatus: 1,
@@ -104,12 +108,16 @@
 				}, __("Get Items From"));
 			this.frm.add_custom_button(__('Sales Order'),
 				function () {
+					if (!me.frm.doc.customer) {
+						frappe.msgprint(__('Please select Customer first'));
+						return;
+					}
 					erpnext.utils.map_current_doc({
 						method: "erpnext.selling.doctype.sales_order.sales_order.make_maintenance_visit",
 						source_doctype: "Sales Order",
 						target: me.frm,
 						setters: {
-							customer: me.frm.doc.customer || undefined,
+							customer: me.frm.doc.customer,
 						},
 						get_query_filters: {
 							docstatus: 1,
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js
deleted file mode 100644
index 51a0d94..0000000
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Blanket Order", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Blanket Order
-		() => frappe.tests.make('Blanket Order', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index e72c8eb..05123d5 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -446,6 +446,11 @@
 			},
 			callback: function(r) {
 				d = locals[cdt][cdn];
+				if (d.is_process_loss) {
+					r.message.rate = 0;
+					r.message.base_rate = 0;
+				}
+
 				$.extend(d, r.message);
 				refresh_field("items");
 				refresh_field("scrap_items");
@@ -655,3 +660,58 @@
 		frm.set_value("operations", []);
 	}
 });
+
+frappe.tour['BOM'] = [
+	{
+		fieldname: "item",
+		title: "Item",
+		description: __("Select the Item to be manufactured. The Item name, UoM, Company, and Currency will be fetched automatically.")
+	},
+	{
+		fieldname: "quantity",
+		title: "Quantity",
+		description: __("Enter the quantity of the Item that will be manufactured from this Bill of Materials.")
+	},
+	{
+		fieldname: "with_operations",
+		title: "With Operations",
+		description: __("To add Operations tick the 'With Operations' checkbox.")
+	},
+	{
+		fieldname: "items",
+		title: "Raw Materials",
+		description: __("Select the raw materials (Items) required to manufacture the Item")
+	}
+];
+
+frappe.ui.form.on("BOM Scrap Item", {
+	item_code(frm, cdt, cdn) {
+		const { item_code } = locals[cdt][cdn];
+		if (item_code === frm.doc.item) {
+			locals[cdt][cdn].is_process_loss = 1;
+			trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code);
+		}
+	},
+});
+
+function trigger_process_loss_qty_prompt(frm, cdt, cdn, item_code) {
+	frappe.prompt(
+		{
+			fieldname: "percent",
+			fieldtype: "Percent",
+			label: __("% Finished Item Quantity"),
+			description:
+				__("Set quantity of process loss item:") +
+				` ${item_code} ` +
+				__("as a percentage of finished item quantity"),
+		},
+		(data) => {
+			const row = locals[cdt][cdn];
+			row.stock_qty = (frm.doc.quantity * data.percent) / 100;
+			row.qty = row.stock_qty / (row.conversion_factor || 1);
+			refresh_field("scrap_items");
+		},
+		__("Set Process Loss Item Quantity"),
+		__("Set Quantity")
+	);
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index eb1dfc8..6e1c7dd 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -156,6 +156,7 @@
 		self.update_stock_qty()
 		self.update_cost(update_parent=False, from_child_bom=True, update_hour_rate = False, save=False)
 		self.set_bom_level()
+		self.validate_scrap_items()
 
 	def get_context(self, context):
 		context.parents = [{'name': 'boms', 'title': _('All BOMs') }]
@@ -230,7 +231,7 @@
 			}
 			ret = self.get_bom_material_detail(args)
 			for key, value in ret.items():
-				if not item.get(key):
+				if item.get(key) is None:
 					item.set(key, value)
 
 	@frappe.whitelist()
@@ -446,25 +447,29 @@
 				frappe.throw(_("Quantity required for Item {0} in row {1}").format(m.item_code, m.idx))
 			check_list.append(m)
 
-	def check_recursion(self, bom_list=[]):
+	def check_recursion(self, bom_list=None):
 		""" Check whether recursion occurs in any bom"""
+		def _throw_error(bom_name):
+			frappe.throw(_("BOM recursion: {0} cannot be parent or child of {0}").format(bom_name))
+
 		bom_list = self.traverse_tree()
-		bom_nos = frappe.get_all('BOM Item', fields=["bom_no"],
-			filters={'parent': ('in', bom_list), 'parenttype': 'BOM'})
+		child_items = frappe.get_all('BOM Item', fields=["bom_no", "item_code"],
+			filters={'parent': ('in', bom_list), 'parenttype': 'BOM'}) or []
 
-		raise_exception = False
-		if bom_nos and self.name in [d.bom_no for d in bom_nos]:
-			raise_exception = True
+		child_bom = {d.bom_no for d in child_items}
+		child_items_codes = {d.item_code for d in child_items}
 
-		if not raise_exception:
-			bom_nos = frappe.get_all('BOM Item', fields=["parent"],
-				filters={'bom_no': self.name, 'parenttype': 'BOM'})
+		if self.name in child_bom:
+			_throw_error(self.name)
 
-			if self.name in [d.parent for d in bom_nos]:
-				raise_exception = True
+		if self.item in child_items_codes:
+			_throw_error(self.item)
 
-		if raise_exception:
-			frappe.throw(_("BOM recursion: {0} cannot be parent or child of {1}").format(self.name, self.name))
+		bom_nos = frappe.get_all('BOM Item', fields=["parent"],
+			filters={'bom_no': self.name, 'parenttype': 'BOM'}) or []
+
+		if self.name in {d.parent for d in bom_nos}:
+			_throw_error(self.name)
 
 	def traverse_tree(self, bom_list=None):
 		def _get_children(bom_no):
@@ -512,17 +517,21 @@
 	def update_rate_and_time(self, row, update_hour_rate = False):
 		if not row.hour_rate or update_hour_rate:
 			hour_rate = flt(frappe.get_cached_value("Workstation", row.workstation, "hour_rate"))
-			row.hour_rate = (hour_rate / flt(self.conversion_rate)
-				if self.conversion_rate and hour_rate else hour_rate)
+
+			if hour_rate:
+				row.hour_rate = (hour_rate / flt(self.conversion_rate)
+					if self.conversion_rate and hour_rate else hour_rate)
 
 			if self.routing:
-				row.time_in_mins = flt(frappe.db.get_value("BOM Operation", {
+				time_in_mins = flt(frappe.db.get_value("BOM Operation", {
 						"workstation": row.workstation,
 						"operation": row.operation,
-						"sequence_id": row.sequence_id,
 						"parent": self.routing
 				}, ["time_in_mins"]))
 
+				if time_in_mins:
+					row.time_in_mins = time_in_mins
+
 		if row.hour_rate and row.time_in_mins:
 			row.base_hour_rate = flt(row.hour_rate) * flt(self.conversion_rate)
 			row.operating_cost = flt(row.hour_rate) * flt(row.time_in_mins) / 60.0
@@ -705,6 +714,32 @@
 		if update:
 			self.db_set("bom_level", self.bom_level)
 
+	def validate_scrap_items(self):
+		for item in self.scrap_items:
+			msg = ""
+			if item.item_code == self.item and not item.is_process_loss:
+				msg = _('Scrap/Loss Item: {0} should have Is Process Loss checked as it is the same as the item to be manufactured or repacked.') \
+					.format(frappe.bold(item.item_code))
+			elif item.item_code != self.item and item.is_process_loss:
+				msg = _('Scrap/Loss Item: {0} should not have Is Process Loss checked as it is different from  the item to be manufactured or repacked') \
+					.format(frappe.bold(item.item_code))
+
+			must_be_whole_number = frappe.get_value("UOM", item.stock_uom, "must_be_whole_number")
+			if item.is_process_loss and must_be_whole_number:
+				msg = _("Item: {0} with Stock UOM: {1} cannot be a Scrap/Loss Item as {1} is a whole UOM.") \
+					.format(frappe.bold(item.item_code), frappe.bold(item.stock_uom))
+
+			if item.is_process_loss and (item.stock_qty >= self.quantity):
+				msg = _("Scrap/Loss Item: {0} should have Qty less than finished goods Quantity.") \
+					.format(frappe.bold(item.item_code))
+
+			if item.is_process_loss and (item.rate > 0):
+				msg = _("Scrap/Loss Item: {0} should have Rate set to 0 because Is Process Loss is checked.") \
+					.format(frappe.bold(item.item_code))
+
+			if msg:
+				frappe.throw(msg, title=_("Note"))
+
 def get_bom_item_rate(args, bom_doc):
 	if bom_doc.rm_cost_as_per == 'Valuation Rate':
 		rate = get_valuation_rate(args) * (args.get("conversion_factor") or 1)
@@ -822,8 +857,11 @@
 
 		items = frappe.db.sql(query, { "parent": bom, "qty": qty, "bom": bom, "company": company }, as_dict=True)
 	elif fetch_scrap_items:
-		query = query.format(table="BOM Scrap Item", where_conditions="",
-			select_columns=", bom_item.idx, item.description", is_stock_item=is_stock_item, qty_field="stock_qty")
+		query = query.format(
+			table="BOM Scrap Item", where_conditions="",
+			select_columns=", bom_item.idx, item.description, is_process_loss",
+			is_stock_item=is_stock_item, qty_field="stock_qty"
+		)
 
 		items = frappe.db.sql(query, { "qty": qty, "bom": bom, "company": company }, as_dict=True)
 	else:
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index c89f7d6..8408f10 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -280,13 +280,82 @@
 			self.assertEqual(reqd_item.qty, created_item.qty)
 			self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty)
 
+	def test_bom_recursion_1st_level(self):
+		"""BOM should not allow BOM item again in child"""
+		item_code = "_Test BOM Recursion"
+		make_item(item_code, {'is_stock_item': 1})
+
+		bom = frappe.new_doc("BOM")
+		bom.item = item_code
+		bom.append("items", frappe._dict(item_code=item_code))
+		with self.assertRaises(frappe.ValidationError) as err:
+			bom.save()
+
+		self.assertTrue("recursion" in str(err.exception).lower())
+		frappe.delete_doc("BOM", bom.name, ignore_missing=True)
+
+	def test_bom_recursion_transitive(self):
+		item1 = "_Test BOM Recursion"
+		item2 = "_Test BOM Recursion 2"
+		make_item(item1, {'is_stock_item': 1})
+		make_item(item2, {'is_stock_item': 1})
+
+		bom1 = frappe.new_doc("BOM")
+		bom1.item = item1
+		bom1.append("items", frappe._dict(item_code=item2))
+		bom1.save()
+		bom1.submit()
+
+		bom2 = frappe.new_doc("BOM")
+		bom2.item = item2
+		bom2.append("items", frappe._dict(item_code=item1))
+
+		with self.assertRaises(frappe.ValidationError) as err:
+			bom2.save()
+			bom2.submit()
+
+		self.assertTrue("recursion" in str(err.exception).lower())
+
+		bom1.cancel()
+		frappe.delete_doc("BOM", bom1.name, ignore_missing=True, force=True)
+		frappe.delete_doc("BOM", bom2.name, ignore_missing=True, force=True)
+
+	def test_bom_with_process_loss_item(self):
+		fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
+
+		if not frappe.db.exists("BOM", f"BOM-{fg_item_non_whole.item_code}-001"):
+			bom_doc = create_bom_with_process_loss_item(
+				fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, fg_qty=1
+			)
+			bom_doc.submit()
+
+		bom_doc = create_bom_with_process_loss_item(
+			fg_item_non_whole, bom_item, scrap_qty=2, scrap_rate=0
+		)
+		#  PL Item qty can't be >= FG Item qty
+		self.assertRaises(frappe.ValidationError, bom_doc.submit)
+
+		bom_doc = create_bom_with_process_loss_item(
+			fg_item_non_whole, bom_item, scrap_qty=1, scrap_rate=100
+		)
+		# PL Item rate has to be 0
+		self.assertRaises(frappe.ValidationError, bom_doc.submit)
+
+		bom_doc = create_bom_with_process_loss_item(
+			fg_item_whole, bom_item, scrap_qty=0.25, scrap_rate=0
+		)
+		#  Items with whole UOMs can't be PL Items
+		self.assertRaises(frappe.ValidationError, bom_doc.submit)
+
+		bom_doc = create_bom_with_process_loss_item(
+			fg_item_non_whole, bom_item, scrap_qty=0.25, scrap_rate=0, is_process_loss=0
+		)
+		# FG Items in Scrap/Loss Table should have Is Process Loss set
+		self.assertRaises(frappe.ValidationError, bom_doc.submit)
 
 def get_default_bom(item_code="_Test FG Item 2"):
 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
 
-
-
-
 def level_order_traversal(node):
 	traversal = []
 	q = deque()
@@ -332,6 +401,7 @@
 			bom = frappe.get_doc(doctype="BOM", item=bom_item_code)
 			for child_item in child_items.keys():
 				bom.append("items", {"item_code": prefix + child_item})
+			bom.currency = "INR"
 			bom.insert()
 			bom.submit()
 
@@ -353,3 +423,45 @@
 
 	for warehouse in warehouse_list:
 		create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=qty, rate=rate)
+
+def create_bom_with_process_loss_item(
+		fg_item, bom_item, scrap_qty, scrap_rate, fg_qty=2, is_process_loss=1):
+	bom_doc = frappe.new_doc("BOM")
+	bom_doc.item = fg_item.item_code
+	bom_doc.quantity = fg_qty
+	bom_doc.append("items", {
+		"item_code": bom_item.item_code,
+		"qty": 1,
+		"uom": bom_item.stock_uom,
+		"stock_uom": bom_item.stock_uom,
+		"rate": 100.0
+	})
+	bom_doc.append("scrap_items", {
+		"item_code": fg_item.item_code,
+		"qty": scrap_qty,
+		"stock_qty": scrap_qty,
+		"uom": fg_item.stock_uom,
+		"stock_uom": fg_item.stock_uom,
+		"rate": scrap_rate,
+		"is_process_loss": is_process_loss
+	})
+	bom_doc.currency = "INR"
+	return bom_doc
+
+def create_process_loss_bom_items():
+	item_list = [
+		("_Test Item - Non Whole UOM", "Kg"),
+		("_Test Item - Whole UOM", "Unit"),
+		("_Test PL BOM Item", "Unit")
+	]
+	return [create_process_loss_bom_item(it) for it in item_list]
+
+def create_process_loss_bom_item(item_tuple):
+	item_code, stock_uom = item_tuple
+	if frappe.db.exists("Item", item_code) is None:
+		return make_item(
+			item_code,
+			{'stock_uom':stock_uom, 'valuation_rate':100}
+		)
+	else:
+		return frappe.get_doc("Item", item_code)
diff --git a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
index 9f7091d..7018082 100644
--- a/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
+++ b/erpnext/manufacturing/doctype/bom_scrap_item/bom_scrap_item.json
@@ -1,345 +1,112 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2016-09-26 02:19:21.642081", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2016-09-26 02:19:21.642081",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_code",
+  "column_break_2",
+  "item_name",
+  "is_process_loss",
+  "quantity_and_rate",
+  "stock_qty",
+  "rate",
+  "amount",
+  "column_break_6",
+  "stock_uom",
+  "base_rate",
+  "base_amount"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "item_code", 
-   "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": "Item Code", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Item", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item Code",
+   "options": "Item",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "item_name", 
-   "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": "Item Name", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Item Name"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "quantity_and_rate", 
-   "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, 
-   "label": "Quantity and Rate", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "quantity_and_rate",
+   "fieldtype": "Section Break",
+   "label": "Quantity and Rate"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "stock_qty", 
-   "fieldtype": "Float", 
-   "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": "Qty", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "stock_qty",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Qty",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "rate", 
-   "fieldtype": "Currency", 
-   "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": "Rate", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "currency", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "rate",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Rate",
+   "options": "currency"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amount", 
-   "fieldtype": "Currency", 
-   "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": "Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "currency", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "label": "Amount",
+   "options": "currency",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_6", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "stock_uom", 
-   "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": "Stock UOM", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "UOM", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "stock_uom",
+   "fieldtype": "Link",
+   "label": "Stock UOM",
+   "options": "UOM",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "base_rate", 
-   "fieldtype": "Currency", 
-   "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": "Basic Rate (Company Currency)", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company:company:default_currency", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 1, 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "base_rate",
+   "fieldtype": "Currency",
+   "label": "Basic Rate (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "base_amount", 
-   "fieldtype": "Currency", 
-   "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": "Basic Amount (Company Currency)", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company:company:default_currency", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 1, 
-   "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, 
-   "unique": 0
+   "fieldname": "base_amount",
+   "fieldtype": "Currency",
+   "label": "Basic Amount (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_process_loss",
+   "fieldtype": "Check",
+   "label": "Is Process Loss"
   }
- ], 
- "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": "2017-07-04 16:04:32.442287", 
- "modified_by": "Administrator", 
- "module": "Manufacturing", 
- "name": "BOM Scrap Item", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-06-22 16:46:12.153311",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Scrap Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js
deleted file mode 100644
index d220df2..0000000
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: BOM Update Tool", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('BOM Update Tool', [
-		// insert a new BOM Update Tool
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.js b/erpnext/manufacturing/doctype/job_card/test_job_card.js
deleted file mode 100644
index 5dc7805..0000000
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Job Card", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Job Card
-		() => frappe.tests.make('Job Card', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js b/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js
deleted file mode 100644
index 2b2589e..0000000
--- a/erpnext/manufacturing/doctype/manufacturing_settings/test_manufacturing_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Manufacturing Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Manufacturing Settings', [
-		// insert a new Manufacturing Settings
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js b/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js
deleted file mode 100644
index 14c6e39..0000000
--- a/erpnext/manufacturing/doctype/material_request_plan_item/test_material_request_plan_item.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Material Request Plan Item", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Material Request Plan Item
-		() => frappe.tests.make('Material Request Plan Item', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/operation/operation.js b/erpnext/manufacturing/doctype/operation/operation.js
index 2936e33..95643bf 100644
--- a/erpnext/manufacturing/doctype/operation/operation.js
+++ b/erpnext/manufacturing/doctype/operation/operation.js
@@ -12,3 +12,21 @@
 		});
 	}
 });
+
+frappe.tour['Operation'] = [
+	{
+		fieldname: "__newname",
+		title: "Operation Name",
+		description: __("Enter a name for the Operation, for example, Cutting.")
+	},
+	{
+		fieldname: "workstation",
+		title: "Default Workstation",
+		description: __("Select the Default Workstation where the Operation will be performed. This will be fetched in BOMs and Work Orders.")
+	},
+	{
+		fieldname: "sub_operations",
+		title: "Sub Operations",
+		description: __("If an operation is divided into sub operations, they can be added here.")
+	}
+];
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index d198a69..847004f 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -428,3 +428,36 @@
 		]
 	}
 };
+
+frappe.tour['Production Plan'] = [
+	{
+		fieldname: "get_items_from",
+		title: "Get Items From",
+		description: __("Select whether to get items from a Sales Order or a Material Request. For now select <b>Sales Order</b>.\n A Production Plan can also be created manually where you can select the Items to manufacture.")
+	},
+	{
+		fieldname: "get_sales_orders",
+		title: "Get Sales Orders",
+		description: __("Click on Get Sales Orders to fetch sales orders based on the above filters.")
+	},
+	{
+		fieldname: "get_items",
+		title: "Get Finished Goods for Manufacture",
+		description: __("Click on 'Get Finished Goods for Manufacture' to fetch the items from the above Sales Orders. Items only for which a BOM is present will be fetched.")
+	},
+	{
+		fieldname: "po_items",
+		title: "Finished Goods",
+		description: __("On expanding a row in the Items to Manufacture table, you'll see an option to 'Include Exploded Items'. Ticking this includes raw materials of the sub-assembly items in the production process.")
+	},
+	{
+		fieldname: "include_non_stock_items",
+		title: "Include Non Stock Items",
+		description: __("To include non-stock items in the material request planning. i.e. Items for which 'Maintain Stock' checkbox is unticked.")
+	},
+	{
+		fieldname: "include_subcontracted_items",
+		title: "Include Subcontracted Items",
+		description: __("To add subcontracted Item's raw materials if include exploded items is disabled.")
+	}
+];
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index b4c6635..6b61c6d 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -213,7 +213,6 @@
 					})
 
 			pi = self.append('po_items', {
-				'include_exploded_items': 1,
 				'warehouse': data.warehouse,
 				'item_code': data.item_code,
 				'description': data.description or item_details.description,
@@ -224,6 +223,7 @@
 				'planned_start_date': now_datetime(),
 				'product_bundle_item': data.parent_item
 			})
+			pi._set_defaults()
 
 			if self.get_items_from == "Sales Order":
 				pi.sales_order = data.parent
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js
index c2e3e6d..8f94686 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan_list.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan_list.js
@@ -1,4 +1,5 @@
 frappe.listview_settings['Production Plan'] = {
+	hide_name_column: true,
 	add_fields: ["status"],
 	filters: [["status", "!=", "Closed"]],
 	get_indicator: function (doc) {
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js b/erpnext/manufacturing/doctype/production_plan/test_production_plan.js
deleted file mode 100644
index ef7d64c..0000000
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Production Plan", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Production Plan
-		() => frappe.tests.make('Production Plan', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index a5b9ff8..7802803 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -288,6 +288,7 @@
 		self.assertEqual(warehouses, expected_warehouses)
 
 	def test_get_sales_order_with_variant(self):
+		rm_item = create_item('PIV_RM', valuation_rate = 100)
 		if not frappe.db.exists('Item', {"item_code": 'PIV'}):
 			item = create_item('PIV', valuation_rate = 100)
 			variant_settings = {
@@ -300,20 +301,20 @@
 			}
 			item.update(variant_settings)
 			item.save()
-			parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV'])
+			parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
 		if not frappe.db.exists('BOM', {"item": 'PIV'}):
-			parent_bom = make_bom(item = 'PIV', raw_materials = ['PIV'])
+			parent_bom = make_bom(item = 'PIV', raw_materials = [rm_item.item_code])
 		else:
 			parent_bom = frappe.get_doc('BOM', {"item": 'PIV'})
 
 		if not frappe.db.exists('Item', {"item_code": 'PIV-RED'}):
 			variant = create_variant("PIV", {"Colour": "Red"})
 			variant.save()
-			variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code])
+			variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
 		else:
 			variant = frappe.get_doc('Item', 'PIV-RED')
 		if not frappe.db.exists('BOM', {"item": 'PIV-RED'}):
-			variant_bom = make_bom(item = variant.item_code, raw_materials = [variant.item_code])
+			variant_bom = make_bom(item = variant.item_code, raw_materials = [rm_item.item_code])
 
 		"""Testing when item variant has a BOM"""
 		so = make_sales_order(item_code="PIV-RED", qty=5)
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 032c9cd..c17a8e9 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -69,3 +69,17 @@
 		frm.events.calculate_operating_cost(frm, d);
 	}
 });
+
+frappe.tour['Routing'] = [
+	{
+		fieldname: "routing_name",
+		title: "Routing Name",
+		description: __("Enter a name for Routing.")
+	},
+	{
+		fieldname: "operations",
+		title: "BOM Operations",
+		description: __("Enter the Operation, the table will fetch the Operation details like Hourly Rate, Workstation automatically.\n\n After that, set the Operation Time in minutes and the table will calculate the Operation Costs based on the Hourly Rate and Operation Time.")
+	}
+];
+
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.js b/erpnext/manufacturing/doctype/routing/test_routing.js
deleted file mode 100644
index 6cb6549..0000000
--- a/erpnext/manufacturing/doctype/routing/test_routing.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Routing", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Routing
-		() => frappe.tests.make('Routing', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index bf1ccb7..c0ed611 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -675,13 +675,18 @@
 
 	def test_valuation_rate_missing_on_make_stock_entry(self):
 		item_name = 'Test Valuation Rate Missing'
+		rm_item = '_Test raw material item'
 		make_item(item_name, {
 			"is_stock_item": 1,
 			"include_item_in_manufacturing": 1,
 		})
+		make_item('_Test raw material item', {
+			"is_stock_item": 1,
+			"include_item_in_manufacturing": 1,
+		})
 
 		if not frappe.db.get_value('BOM', {'item': item_name}):
-			make_bom(item=item_name, raw_materials=[item_name], rm_qty=1)
+			make_bom(item=item_name, raw_materials=[rm_item], rm_qty=1)
 
 		company = "_Test Company with perpetual inventory"
 		source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company)
@@ -690,6 +695,71 @@
 
 		self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture')
 
+	def test_wo_completion_with_pl_bom(self):
+		from erpnext.manufacturing.doctype.bom.test_bom import create_process_loss_bom_items
+		from erpnext.manufacturing.doctype.bom.test_bom import create_bom_with_process_loss_item
+
+		qty = 4
+		scrap_qty = 0.25 # bom item qty = 1, consider as 25% of FG
+		source_warehouse = "Stores - _TC"
+		wip_warehouse = "_Test Warehouse - _TC"
+		fg_item_non_whole, _, bom_item = create_process_loss_bom_items()
+
+		test_stock_entry.make_stock_entry(item_code=bom_item.item_code,
+			target=source_warehouse, qty=4, basic_rate=100)
+
+		bom_no = f"BOM-{fg_item_non_whole.item_code}-001"
+		if not frappe.db.exists("BOM", bom_no):
+			bom_doc = create_bom_with_process_loss_item(
+				fg_item_non_whole, bom_item, scrap_qty=scrap_qty,
+				scrap_rate=0, fg_qty=1, is_process_loss=1
+			)
+			bom_doc.submit()
+
+		wo = make_wo_order_test_record(
+			production_item=fg_item_non_whole.item_code,
+			bom_no=bom_no,
+			wip_warehouse=wip_warehouse,
+			qty=qty,
+			skip_transfer=1,
+			stock_uom=fg_item_non_whole.stock_uom,
+		)
+
+		se = frappe.get_doc(
+			make_stock_entry(wo.name, "Material Transfer for Manufacture", qty)
+		)
+		se.get("items")[0].s_warehouse = "Stores - _TC"
+		se.insert()
+		se.submit()
+
+		se = frappe.get_doc(
+			make_stock_entry(wo.name, "Manufacture", qty)
+		)
+		se.insert()
+		se.submit()
+
+		# Testing stock entry values
+		items = se.get("items")
+		self.assertEqual(len(items), 3, "There should be 3 items including process loss.")
+
+		source_item, fg_item, pl_item = items
+
+		total_pl_qty = qty * scrap_qty
+		actual_fg_qty = qty - total_pl_qty
+
+		self.assertEqual(pl_item.qty, total_pl_qty)
+		self.assertEqual(fg_item.qty, actual_fg_qty)
+
+		# Testing Work Order values
+		self.assertEqual(
+			frappe.db.get_value("Work Order", wo.name, "produced_qty"),
+			qty
+		)
+		self.assertEqual(
+			frappe.db.get_value("Work Order", wo.name, "process_loss_qty"),
+			total_pl_qty
+		)
+
 def get_scrap_item_details(bom_no):
 	scrap_items = {}
 	for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 5120485..51c46f6 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -731,3 +731,63 @@
 		});
 	}
 };
+
+frappe.tour['Work Order'] = [
+	{
+		fieldname: "production_item",
+		title: "Item to Manufacture",
+		description: __("Select the Item to be manufactured.")
+	},
+	{
+		fieldname: "bom_no",
+		title: "BOM No",
+		description: __("The default BOM for that item will be fetched by the system. You can also change the BOM.")
+	},
+	{
+		fieldname: "qty",
+		title: "Qty to Manufacture",
+		description: __("Enter the quantity to manufacture. Raw material Items will be fetched only when this is set.")
+	},
+	{
+		fieldname: "use_multi_level_bom",
+		title: "Use Multi-Level BOM",
+		description: __("This is enabled by default. If you want to plan materials for sub-assemblies of the Item you're manufacturing leave this enabled. If you plan and manufacture the sub-assemblies separately, you can disable this checkbox.")
+	},
+	{
+		fieldname: "source_warehouse",
+		title: "Source Warehouse",
+		description: __("The warehouse where you store your raw materials. Each required item can have a separate source warehouse. Group warehouse also can be selected as source warehouse. On submission of the Work Order, the raw materials will be reserved in these warehouses for production usage.")
+	},
+	{
+		fieldname: "fg_warehouse",
+		title: "Target Warehouse",
+		description: __("The warehouse where you store finished Items before they are shipped.")
+	},
+	{
+		fieldname: "wip_warehouse",
+		title: "Work-in-Progress Warehouse",
+		description: __("The warehouse where your Items will be transferred when you begin production. Group Warehouse can also be selected as a Work in Progress warehouse.")
+	},
+	{
+		fieldname: "scrap_warehouse",
+		title: "Scrap Warehouse",
+		description: __("If the BOM results in Scrap material, the Scrap Warehouse needs to be selected.")
+	},
+	{
+		fieldname: "required_items",
+		title: "Required Items",
+		description: __("All the required items (raw materials) will be fetched from BOM and populated in this table. Here you can also change the Source Warehouse for any item. And during the production, you can track transferred raw materials from this table.")
+	},
+	{
+		fieldname: "planned_start_date",
+		title: "Planned Start Date",
+		description: __("Set the Planned Start Date (an Estimated Date at which you want the Production to begin)")
+	},
+	{
+		fieldname: "operations",
+		title: "Operations",
+		description: __("If the selected BOM has Operations mentioned in it, the system will fetch all Operations from BOM, these values can be changed.")
+	},
+
+
+];
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 3b56854..913fc85 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -19,6 +19,7 @@
   "qty",
   "material_transferred_for_manufacturing",
   "produced_qty",
+  "process_loss_qty",
   "sales_order",
   "project",
   "serial_no_and_batch_for_finished_good_section",
@@ -64,16 +65,12 @@
   "description",
   "stock_uom",
   "column_break2",
-  "references_section",
   "material_request",
   "material_request_item",
   "sales_order_item",
-  "column_break_61",
   "production_plan",
   "production_plan_item",
   "production_plan_sub_assembly_item",
-  "parent_work_order",
-  "bom_level",
   "product_bundle_item",
   "amended_from"
  ],
@@ -553,20 +550,29 @@
    "read_only": 1
   },
   {
-    "fieldname": "production_plan_sub_assembly_item",
-    "fieldtype": "Data",
-    "label": "Production Plan Sub-assembly Item",
-    "no_copy": 1,
-    "print_hide": 1,
-    "read_only": 1
-   }
+   "fieldname": "production_plan_sub_assembly_item",
+   "fieldtype": "Data",
+   "label": "Production Plan Sub-assembly Item",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.process_loss_qty",
+   "fieldname": "process_loss_qty",
+   "fieldtype": "Float",
+   "label": "Process Loss Qty",
+   "no_copy": 1,
+   "non_negative": 1,
+   "read_only": 1
+  }
  ],
  "icon": "fa fa-cogs",
  "idx": 1,
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2021-06-28 16:19:14.902699",
+ "modified": "2021-08-24 15:14:03.844937",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 5fe9fec..24b33d5 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -214,6 +214,7 @@
 					self.meta.get_label(fieldname), qty, completed_qty, self.name), StockOverProductionError)
 
 			self.db_set(fieldname, qty)
+			self.set_process_loss_qty()
 
 			from erpnext.selling.doctype.sales_order.sales_order import update_produced_qty_in_so_item
 
@@ -223,6 +224,22 @@
 		if self.production_plan:
 			self.update_production_plan_status()
 
+	def set_process_loss_qty(self):
+		process_loss_qty = flt(frappe.db.sql("""
+				SELECT sum(qty) FROM `tabStock Entry Detail`
+				WHERE
+					is_process_loss=1
+					AND parent IN (
+						SELECT name FROM `tabStock Entry`
+						WHERE
+							work_order=%s
+							AND purpose='Manufacture'
+							AND docstatus=1
+					)
+			""", (self.name, ))[0][0])
+		if process_loss_qty is not None:
+			self.db_set('process_loss_qty', process_loss_qty)
+
 	def update_production_plan_status(self):
 		production_plan = frappe.get_doc('Production Plan', self.production_plan)
 		produced_qty = 0
diff --git a/erpnext/manufacturing/doctype/workstation/workstation.js b/erpnext/manufacturing/doctype/workstation/workstation.js
index d8d25fc..4f5231e 100644
--- a/erpnext/manufacturing/doctype/workstation/workstation.js
+++ b/erpnext/manufacturing/doctype/workstation/workstation.js
@@ -16,4 +16,29 @@
 			})
 		}
 	}
-})
+});
+
+frappe.tour['Workstation'] = [
+	{
+		fieldname: "workstation_name",
+		title: "Workstation Name",
+		description: __("You can set it as a machine name or operation type. For example, stiching machine 12")
+	},
+	{
+		fieldname: "production_capacity",
+		title: "Production Capacity",
+		description: __("No. of parallel job cards which can be allowed on this workstation. Example: 2 would mean this workstation can process production for two Work Orders at a time.")
+	},
+	{
+		fieldname: "holiday_list",
+		title: "Holiday List",
+		description: __("A Holiday List can be added to exclude counting these days for the Workstation.")
+	},
+	{
+		fieldname: "working_hours",
+		title: "Working Hours",
+		description: __("Under Working Hours table, you can add start and end times for a Workstation. For example, a Workstation may be active from 9 am to 1 pm, then 2 pm to 5 pm. You can also specify the working hours based on shifts. While scheduling a Work Order, the system will check for the availability of the Workstation based on the working hours specified.")
+	},
+
+
+];
\ No newline at end of file
diff --git a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
index 7317152..032091f 100644
--- a/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/module_onboarding/manufacturing/manufacturing.json
@@ -19,14 +19,14 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/manufacturing",
  "idx": 0,
  "is_complete": 0,
- "modified": "2020-06-29 20:25:36.899106",
+ "modified": "2021-08-13 16:04:34.333812",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing",
  "owner": "Administrator",
  "steps": [
   {
-   "step": "Warehouse"
+   "step": "Explore Manufacturing Settings"
   },
   {
    "step": "Workstation"
@@ -35,22 +35,22 @@
    "step": "Operation"
   },
   {
-   "step": "Create Product"
+   "step": "Routing"
   },
   {
-   "step": "Create Raw Materials"
+   "step": "Create Product"
   },
   {
    "step": "Create BOM"
   },
   {
-   "step": "Work Order"
+   "step": "Production Planning"
   },
   {
-   "step": "Explore Manufacturing Settings"
+   "step": "Work Order"
   }
  ],
  "subtitle": "Products, Raw Materials, BOM, Work Order, and more.",
  "success_message": "Manufacturing module is all set up!",
  "title": "Let's Set Up the Manufacturing Module."
-}
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
index 84b4088..9d7a58c 100644
--- a/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
+++ b/erpnext/manufacturing/onboarding_step/create_bom/create_bom.json
@@ -1,19 +1,21 @@
 {
  "action": "Create Entry",
+ "action_label": "Create your first Bill of Materials",
  "creation": "2020-05-05 16:41:20.239696",
+ "description": "# Create a Bill of Materials\n\nA Bill of Materials (BOM) is a list of items and sub-assemblies with quantities required to manufacture an Item.\n\nBOM also provides cost estimation for the production of the item. It takes raw-materials cost based on valuation and operations to cost based on routing, which gives total costing for a BOM.",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:51:31.315686",
+ "modified": "2021-08-13 16:00:51.092671",
  "modified_by": "Administrator",
  "name": "Create BOM",
  "owner": "Administrator",
  "reference_document": "BOM",
+ "show_form_tour": 1,
  "show_full_form": 1,
- "title": "Create a BOM (Bill of Material)",
+ "title": "Bill of Materials",
  "validate_action": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_product/create_product.json b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
index 0ffa301..ad73452 100644
--- a/erpnext/manufacturing/onboarding_step/create_product/create_product.json
+++ b/erpnext/manufacturing/onboarding_step/create_product/create_product.json
@@ -1,19 +1,21 @@
 {
  "action": "Create Entry",
+ "action_label": "Create an Item",
  "creation": "2020-05-05 16:42:31.476275",
+ "description": "# Create Items for Bill of Materials\n\nOne of the prerequisites of a BOM is the creation of raw materials, sub-assembly, and finished items. Once these items are created, you will be able to proceed to the Bill of Materials master, which is composed of items and routing.\n",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 1,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:50:59.010439",
+ "modified": "2021-08-13 16:00:22.407811",
  "modified_by": "Administrator",
  "name": "Create Product",
  "owner": "Administrator",
  "reference_document": "Item",
- "show_full_form": 0,
- "title": "Create a Finished Good",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Finished Items",
  "validate_action": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
index 0764f2e..3f94764 100644
--- a/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
+++ b/erpnext/manufacturing/onboarding_step/create_raw_materials/create_raw_materials.json
@@ -5,7 +5,6 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
  "modified": "2020-05-19 11:53:25.147837",
@@ -13,6 +12,7 @@
  "name": "Create Raw Materials",
  "owner": "Administrator",
  "reference_document": "Item",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Create Raw Materials",
  "validate_action": 1
diff --git a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
index 7ef202e..1d2c27e 100644
--- a/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
+++ b/erpnext/manufacturing/onboarding_step/explore_manufacturing_settings/explore_manufacturing_settings.json
@@ -1,20 +1,22 @@
 {
  "action": "Show Form Tour",
+ "action_label": "Take a walk-through of Manufacturing Settings",
  "creation": "2020-05-19 11:55:11.378374",
+ "description": "# Review Manufacturing Settings\n\nIn ERPNext, the Manufacturing module\u2019s features are configurable as per your business needs. Manufacturing Settings is the place where you can set your preferences for:\n\n- Capacity planning for allocating jobs to workstations\n- Raw-material consumption based on BOM or actual\n- Default values and over-production allowance\n",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 1,
  "is_skipped": 0,
- "modified": "2020-05-26 20:28:03.558199",
+ "modified": "2021-08-13 15:59:32.145655",
  "modified_by": "Administrator",
  "name": "Explore Manufacturing Settings",
  "owner": "Administrator",
  "reference_document": "Manufacturing Settings",
+ "show_form_tour": 0,
  "show_full_form": 0,
- "title": "Explore Manufacturing Settings",
+ "title": "Manufacturing Settings",
  "validate_action": 0,
  "video_url": "https://www.youtube.com/watch?v=UVGfzwOOZC4"
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/operation/operation.json b/erpnext/manufacturing/onboarding_step/operation/operation.json
index b532e67..2e95921 100644
--- a/erpnext/manufacturing/onboarding_step/operation/operation.json
+++ b/erpnext/manufacturing/onboarding_step/operation/operation.json
@@ -1,19 +1,21 @@
 {
  "action": "Create Entry",
+ "action_label": "Let\u2019s create an Operation",
  "creation": "2020-05-12 16:15:31.706756",
+ "description": "# Create Operations\n\nAn Operation refers to any manufacturing operation performed on the raw materials to process it further in the manufacturing path. As an example, if you are into garments manufacturing, you will create Operations like fabric cutting, stitching, and washing as some of the operations.",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:50:41.642754",
+ "modified": "2021-08-13 15:59:53.313532",
  "modified_by": "Administrator",
  "name": "Operation",
  "owner": "Administrator",
  "reference_document": "Operation",
- "show_full_form": 0,
- "title": "Create a Operation",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Operation",
  "validate_action": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json
new file mode 100644
index 0000000..ff9fdb9
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/production_planning/production_planning.json
@@ -0,0 +1,21 @@
+{
+ "action": "Create Entry",
+ "action_label": "Learn more about Production Planning",
+ "creation": "2021-08-04 17:33:06.551077",
+ "description": "# How Production Planning Works\n\nProduction Plan helps in production and material planning for the Items planned for manufacturing. These production items can be committed via Sales Order (to Customers) or Material Requests (internally).\n",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 16:04:15.491414",
+ "modified_by": "Administrator",
+ "name": "Production Planning",
+ "owner": "Administrator",
+ "reference_document": "Production Plan",
+ "show_form_tour": 0,
+ "show_full_form": 1,
+ "title": "Production Planning",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/routing/routing.json b/erpnext/manufacturing/onboarding_step/routing/routing.json
new file mode 100644
index 0000000..be4fbff
--- /dev/null
+++ b/erpnext/manufacturing/onboarding_step/routing/routing.json
@@ -0,0 +1,21 @@
+{
+ "action": "Create Entry",
+ "action_label": "Check help to setup Routing",
+ "creation": "2021-08-04 11:56:42.455758",
+ "description": "# Setup Routing\n\nA Routing stores all Operations along with the description, hourly rate, operation time, batch size, etc. Click below to learn how the Routing template can be created, for quick selection in the BOM.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-13 16:00:07.391563",
+ "modified_by": "Administrator",
+ "name": "Routing",
+ "owner": "Administrator",
+ "reference_document": "Routing",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Routing",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/work_order/work_order.json b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
index c63363e..d2756c9 100644
--- a/erpnext/manufacturing/onboarding_step/work_order/work_order.json
+++ b/erpnext/manufacturing/onboarding_step/work_order/work_order.json
@@ -1,19 +1,21 @@
 {
  "action": "Create Entry",
+ "action_label": "Create your first Work Order",
  "creation": "2020-05-12 16:15:56.084682",
+ "description": "# Create a Work Order\n\nA Work Order or a Job order is given to the manufacturing shop floor by the Production Manager to initiate the manufacturing of a certain quantity of an item. Work Order carriers details of production Item, its BOM, quantities to be manufactured, and operations.\n\nThrough Work Order, you can track various production status like:\n\n- Issue of raw-material to shop material\n- Progress on each Workstation via Job Card\n- Manufactured Quantity against Work Order\n",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:51:38.133150",
+ "modified": "2021-08-13 16:02:04.223536",
  "modified_by": "Administrator",
  "name": "Work Order",
  "owner": "Administrator",
  "reference_document": "Work Order",
+ "show_form_tour": 1,
  "show_full_form": 1,
- "title": "Create a Work Order",
+ "title": "Work Order",
  "validate_action": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/onboarding_step/workstation/workstation.json b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
index df244bb..67dd52e 100644
--- a/erpnext/manufacturing/onboarding_step/workstation/workstation.json
+++ b/erpnext/manufacturing/onboarding_step/workstation/workstation.json
@@ -1,19 +1,21 @@
 {
  "action": "Create Entry",
+ "action_label": "Let\u2019s create a Workstation",
  "creation": "2020-05-12 16:14:14.930214",
+ "description": "# Create Workstations\n\nA Workstation stores information regarding the place where the workstation operations are performed. As an example, if you have ten sewing machines doing stitching jobs, each machine will be added as a workstation.",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-19 12:50:33.938176",
+ "modified": "2021-08-13 15:59:59.634802",
  "modified_by": "Administrator",
  "name": "Workstation",
  "owner": "Administrator",
  "reference_document": "Workstation",
- "show_full_form": 0,
- "title": "Create a Workstation / Machine",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Workstation",
  "validate_action": 1
 }
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/certification_application/test_certification_application.js b/erpnext/non_profit/doctype/certification_application/test_certification_application.js
deleted file mode 100644
index 40e9486..0000000
--- a/erpnext/non_profit/doctype/certification_application/test_certification_application.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Certification Application", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Certification Application
-		() => frappe.tests.make('Certification Application', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js
deleted file mode 100644
index f6a72a4..0000000
--- a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Certified Consultant", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Certified Consultant
-		() => frappe.tests.make('Certified Consultant', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/non_profit/doctype/chapter/test_chapter.js b/erpnext/non_profit/doctype/chapter/test_chapter.js
deleted file mode 100644
index e30d6a5..0000000
--- a/erpnext/non_profit/doctype/chapter/test_chapter.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Chapter", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Chapter
-		() => frappe.tests.make('Chapter', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/non_profit/doctype/donor_type/test_donor_type.js b/erpnext/non_profit/doctype/donor_type/test_donor_type.js
deleted file mode 100644
index 22dc18e..0000000
--- a/erpnext/non_profit/doctype/donor_type/test_donor_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Donor Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Member
-		() => frappe.tests.make('Donor Type', [
-			// values to be set
-			{donor_type: 'Test Organization'},
-		]),
-		() => {
-			assert.equal(cur_frm.doc.donor_type, 'Test Organization');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/non_profit/doctype/membership/test_membership.js b/erpnext/non_profit/doctype/membership/test_membership.js
deleted file mode 100644
index 24c85c6..0000000
--- a/erpnext/non_profit/doctype/membership/test_membership.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Membership", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Membership
-		() => frappe.tests.make('Membership', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0a6a8bd..ca72952 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -214,6 +214,7 @@
 execute:frappe.delete_doc_if_exists("DocType", "Bank Reconciliation")
 erpnext.patches.v13_0.move_doctype_reports_and_notification_from_hr_to_payroll #22-06-2020
 erpnext.patches.v13_0.move_payroll_setting_separately_from_hr_settings #22-06-2020
+erpnext.patches.v12_0.create_itc_reversal_custom_fields
 erpnext.patches.v13_0.check_is_income_tax_component #22-06-2020
 erpnext.patches.v13_0.loyalty_points_entry_for_pos_invoice #22-07-2020
 erpnext.patches.v12_0.add_taxjar_integration_field
@@ -266,7 +267,6 @@
 erpnext.patches.v12_0.create_taxable_value_field
 erpnext.patches.v12_0.add_gst_category_in_delivery_note
 erpnext.patches.v12_0.purchase_receipt_status
-erpnext.patches.v12_0.create_itc_reversal_custom_fields
 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
@@ -295,6 +295,10 @@
 erpnext.patches.v13_0.add_custom_field_for_south_africa #2
 erpnext.patches.v13_0.update_recipient_email_digest
 erpnext.patches.v13_0.shopify_deprecation_warning
+erpnext.patches.v13_0.migrate_stripe_api
 erpnext.patches.v13_0.reset_clearance_date_for_intracompany_payment_entries
 erpnext.patches.v13_0.einvoicing_deprecation_warning
+erpnext.patches.v13_0.custom_fields_for_taxjar_integration
 erpnext.patches.v14_0.delete_einvoicing_doctypes
+erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
+erpnext.patches.v13_0.validate_options_for_data_field
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
index a6471eb..5c3fa59 100644
--- a/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
+++ b/erpnext/patches/v12_0/move_item_tax_to_item_tax_template.py
@@ -91,8 +91,9 @@
 	item_tax_template.title = make_autoname("Item Tax Template-.####")
 
 	for tax_type, tax_rate in iteritems(item_tax_map):
-		account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type'], as_dict=1)
+		account_details = frappe.db.get_value("Account", tax_type, ['name', 'account_type', 'company'], as_dict=1)
 		if account_details:
+			item_tax_template.company = account_details.company
 			if account_details.account_type not in ('Tax', 'Chargeable', 'Income Account', 'Expense Account', 'Expenses Included In Valuation'):
 				frappe.db.set_value('Account', account_details.name, 'account_type', 'Chargeable')
 		else:
diff --git a/erpnext/patches/v13_0/check_is_income_tax_component.py b/erpnext/patches/v13_0/check_is_income_tax_component.py
index ebae3ad..7a52dc8 100644
--- a/erpnext/patches/v13_0/check_is_income_tax_component.py
+++ b/erpnext/patches/v13_0/check_is_income_tax_component.py
@@ -19,10 +19,10 @@
 	]
 
 	for doctype in doctypes:
-		frappe.reload_doc('Payroll', 'doctype', doctype)
+		frappe.reload_doc('Payroll', 'doctype', doctype, force=True)
 
 
-	reports = ['Professional Tax Deductions', 'Provident Fund Deductions']
+	reports = ['Professional Tax Deductions', 'Provident Fund Deductions', 'E-Invoice Summary']
 	for report in reports:
 		frappe.reload_doc('Regional', 'Report', report)
 		frappe.reload_doc('Regional', 'Report', report)
diff --git a/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py
new file mode 100644
index 0000000..1d57550
--- /dev/null
+++ b/erpnext/patches/v13_0/custom_fields_for_taxjar_integration.py
@@ -0,0 +1,29 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from erpnext.regional.united_states.setup import add_permissions
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'United States'}, fields=['name'])
+	if not company:
+		return
+
+	frappe.reload_doc("regional", "doctype", "product_tax_category")
+
+	custom_fields = {
+		'Sales Invoice Item': [
+			dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category',
+				label='Product Tax Category', fetch_from='item_code.product_tax_category'),
+			dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', 
+				label='Tax Collectable', read_only=1),
+			dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', 
+				label='Taxable Amount', read_only=1)
+		],
+		'Item': [
+			dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category',
+				label='Product Tax Category')
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+	add_permissions()
+	frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=True)
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/delete_old_purchase_reports.py b/erpnext/patches/v13_0/delete_old_purchase_reports.py
index c17aad0..57620d3 100644
--- a/erpnext/patches/v13_0/delete_old_purchase_reports.py
+++ b/erpnext/patches/v13_0/delete_old_purchase_reports.py
@@ -4,6 +4,7 @@
 from __future__ import unicode_literals
 
 import frappe
+from erpnext.accounts.utils import check_and_delete_linked_reports
 
 def execute():
 	reports_to_delete = ["Requested Items To Be Ordered",
@@ -13,6 +14,7 @@
 	for report in reports_to_delete:
 		if frappe.db.exists("Report", report):
 			delete_auto_email_reports(report)
+			check_and_delete_linked_reports(report)
 
 			frappe.delete_doc("Report", report)
 
diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py
index 671c012..905a42c 100644
--- a/erpnext/patches/v13_0/delete_old_sales_reports.py
+++ b/erpnext/patches/v13_0/delete_old_sales_reports.py
@@ -4,6 +4,7 @@
 from __future__ import unicode_literals
 
 import frappe
+from erpnext.accounts.utils import check_and_delete_linked_reports
 
 def execute():
 	reports_to_delete = ["Ordered Items To Be Delivered", "Ordered Items To Be Billed"]
@@ -11,6 +12,7 @@
 	for report in reports_to_delete:
 		if frappe.db.exists("Report", report):
 			delete_auto_email_reports(report)
+			check_and_delete_linked_reports(report)
 
 			frappe.delete_doc("Report", report)
 
diff --git a/erpnext/patches/v13_0/migrate_stripe_api.py b/erpnext/patches/v13_0/migrate_stripe_api.py
new file mode 100644
index 0000000..355421a
--- /dev/null
+++ b/erpnext/patches/v13_0/migrate_stripe_api.py
@@ -0,0 +1,7 @@
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+
+def execute():
+	frappe.reload_doc("accounts", "doctype", "subscription_plan")
+	rename_field("Subscription Plan", "payment_plan_id", "product_price_id")
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index 41c51c3..4885c0b 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -41,6 +41,7 @@
 		rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
 
 		# change fieldtype to duration
+		frappe.reload_doc('crm', 'doctype', 'opportunity', force=True)
 		count = 0
 		for entry in opportunities:
 			mins_to_first_response = convert_to_seconds(entry.mins_to_first_response, 'Minutes')
@@ -58,6 +59,8 @@
 
 def convert_to_seconds(value, unit):
 	seconds = 0
+	if value == 0:
+		return seconds
 	if unit == 'Hours':
 		seconds = value * 3600
 	if unit == 'Minutes':
diff --git a/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
new file mode 100644
index 0000000..4acbdd6
--- /dev/null
+++ b/erpnext/patches/v13_0/set_operation_time_based_on_operating_cost.py
@@ -0,0 +1,15 @@
+import frappe
+
+def execute():
+	frappe.reload_doc('manufacturing', 'doctype', 'bom')
+	frappe.reload_doc('manufacturing', 'doctype', 'bom_operation')
+
+	frappe.db.sql('''
+		UPDATE
+			`tabBOM Operation`
+		SET
+			time_in_mins = (operating_cost * 60) / hour_rate
+		WHERE
+			time_in_mins = 0 AND operating_cost > 0
+			AND hour_rate > 0 AND docstatus = 1 AND parenttype = "BOM"
+	''')
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
index e642547..a5769d2 100644
--- a/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
+++ b/erpnext/patches/v13_0/update_returned_qty_in_pr_dn.py
@@ -8,6 +8,7 @@
 	frappe.reload_doc('stock', 'doctype', 'purchase_receipt_item')
 	frappe.reload_doc('stock', 'doctype', 'delivery_note')
 	frappe.reload_doc('stock', 'doctype', 'delivery_note_item')
+	frappe.reload_doc('stock', 'doctype', 'stock_settings')
 
 	def update_from_return_docs(doctype):
 		for return_doc in frappe.get_all(doctype, filters={'is_return' : 1, 'docstatus' : 1}):
diff --git a/erpnext/patches/v13_0/validate_options_for_data_field.py b/erpnext/patches/v13_0/validate_options_for_data_field.py
new file mode 100644
index 0000000..568d1a4
--- /dev/null
+++ b/erpnext/patches/v13_0/validate_options_for_data_field.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2021, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe.model import data_field_options 
+
+def execute():
+
+    for field in frappe.get_all('Custom Field', 
+                            fields = ['name'],
+                            filters = {
+                                'fieldtype': 'Data',
+                                'options': ['!=', None]
+                            }):
+
+        if field not in data_field_options:
+            frappe.db.sql("""
+                UPDATE 
+                    `tabCustom Field`
+                SET
+                    options=NULL
+                WHERE
+                    name=%s
+            """, (field))
diff --git a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js b/erpnext/payroll/doctype/additional_salary/test_additional_salary.js
deleted file mode 100644
index c18e187..0000000
--- a/erpnext/payroll/doctype/additional_salary/test_additional_salary.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Additional Salary", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Additional Salary
-		() => frappe.tests.make('Additional Salary', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js b/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js
deleted file mode 100644
index b355e1c..0000000
--- a/erpnext/payroll/doctype/employee_benefit_application/test_employee_benefit_application.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Benefit Application", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Benefit Application
-		() => frappe.tests.make('Employee Benefit Application', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js
deleted file mode 100644
index 3c808c0..0000000
--- a/erpnext/payroll/doctype/employee_benefit_claim/test_employee_benefit_claim.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Benefit Claim", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Benefit Claim
-		() => frappe.tests.make('Employee Benefit Claim', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js b/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js
deleted file mode 100644
index 10bc037..0000000
--- a/erpnext/payroll/doctype/employee_incentive/test_employee_incentive.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Incentive", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Incentive
-		() => frappe.tests.make('Employee Incentive', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js b/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js
deleted file mode 100644
index e0e43c3..0000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_category/test_employee_tax_exemption_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Tax Exemption Category
-		() => frappe.tests.make('Employee Tax Exemption Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js
deleted file mode 100644
index 274a3a3..0000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/test_employee_tax_exemption_declaration.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Declaration", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Tax Exemption Declaration
-		() => frappe.tests.make('Employee Tax Exemption Declaration', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js
deleted file mode 100644
index cec7508..0000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/test_employee_tax_exemption_proof_submission.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Proof Submission", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Tax Exemption Proof Submission
-		() => frappe.tests.make('Employee Tax Exemption Proof Submission', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js b/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js
deleted file mode 100644
index 8a1a6d1..0000000
--- a/erpnext/payroll/doctype/employee_tax_exemption_sub_category/test_employee_tax_exemption_sub_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Employee Tax Exemption Sub Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Employee Tax Exemption Sub Category
-		() => frappe.tests.make('Employee Tax Exemption Sub Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js
index 377f3c6..d4f7c9c 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.js
+++ b/erpnext/payroll/doctype/gratuity/gratuity.js
@@ -3,13 +3,6 @@
 
 frappe.ui.form.on('Gratuity', {
 	setup: function (frm) {
-		frm.set_query('salary_component', function () {
-			return {
-				filters: {
-					type: "Earning"
-				}
-			};
-		});
 		frm.set_query("expense_account", function () {
 			return {
 				filters: {
@@ -31,7 +24,7 @@
 		});
 	},
 	refresh: function (frm) {
-		if (frm.doc.docstatus === 1 && frm.doc.pay_via_salary_slip === 0 && frm.doc.status === "Unpaid") {
+		if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") {
 			frm.add_custom_button(__("Create Payment Entry"), function () {
 				return frappe.call({
 					method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry',
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.json b/erpnext/payroll/doctype/gratuity/gratuity.json
index 5cffd7e..48a9ce4 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.json
+++ b/erpnext/payroll/doctype/gratuity/gratuity.json
@@ -16,9 +16,6 @@
   "company",
   "gratuity_rule",
   "section_break_5",
-  "pay_via_salary_slip",
-  "payroll_date",
-  "salary_component",
   "payable_account",
   "expense_account",
   "mode_of_payment",
@@ -50,26 +47,12 @@
    "reqd": 1
   },
   {
-   "default": "1",
-   "fieldname": "pay_via_salary_slip",
-   "fieldtype": "Check",
-   "label": "Pay via Salary Slip"
-  },
-  {
    "fieldname": "posting_date",
    "fieldtype": "Date",
    "label": "Posting date",
    "reqd": 1
   },
   {
-   "depends_on": "eval: doc.pay_via_salary_slip == 1",
-   "fieldname": "salary_component",
-   "fieldtype": "Link",
-   "label": "Salary Component",
-   "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1",
-   "options": "Salary Component"
-  },
-  {
    "default": "0",
    "fieldname": "current_work_experience",
    "fieldtype": "Int",
@@ -95,20 +78,18 @@
    "reqd": 1
   },
   {
-   "depends_on": "eval: doc.pay_via_salary_slip == 0",
    "fieldname": "expense_account",
    "fieldtype": "Link",
    "label": "Expense Account",
-   "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
-   "options": "Account"
+   "options": "Account",
+   "reqd": 1
   },
   {
-   "depends_on": "eval: doc.pay_via_salary_slip == 0",
    "fieldname": "mode_of_payment",
    "fieldtype": "Link",
    "label": "Mode of Payment",
-   "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
-   "options": "Mode of Payment"
+   "options": "Mode of Payment",
+   "reqd": 1
   },
   {
    "fieldname": "gratuity_rule",
@@ -162,13 +143,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "depends_on": "eval: doc.pay_via_salary_slip == 1",
-   "fieldname": "payroll_date",
-   "fieldtype": "Date",
-   "label": "Payroll Date",
-   "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 1"
-  },
-  {
    "default": "0",
    "depends_on": "eval:doc.pay_via_salary_slip == 0",
    "fieldname": "paid_amount",
@@ -177,26 +151,23 @@
    "read_only": 1
   },
   {
-   "depends_on": "eval: doc.pay_via_salary_slip == 0",
    "fieldname": "payable_account",
    "fieldtype": "Link",
    "label": "Payable Account",
-   "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
-   "options": "Account"
+   "options": "Account",
+   "reqd": 1
   },
   {
-   "depends_on": "eval: doc.pay_via_salary_slip == 0",
    "fieldname": "cost_center",
    "fieldtype": "Link",
    "label": "Cost Center",
-   "mandatory_depends_on": "eval: doc.pay_via_salary_slip == 0",
    "options": "Cost Center"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-02 18:21:11.971488",
+ "modified": "2021-07-02 15:05:57.396398",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Gratuity",
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py
index 8cb804d..8217bc3 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity.py
@@ -19,10 +19,7 @@
 			self.status = "Unpaid"
 
 	def on_submit(self):
-		if self.pay_via_salary_slip:
-			self.create_additional_salary()
-		else:
-			self.create_gl_entries()
+		self.create_gl_entries()
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ['GL Entry']
@@ -65,19 +62,6 @@
 
 		return gl_entry
 
-	def create_additional_salary(self):
-		if self.pay_via_salary_slip:
-			additional_salary = frappe.new_doc('Additional Salary')
-			additional_salary.employee = self.employee
-			additional_salary.salary_component = self.salary_component
-			additional_salary.overwrite_salary_structure_amount = 0
-			additional_salary.amount = self.amount
-			additional_salary.payroll_date = self.payroll_date
-			additional_salary.company = self.company
-			additional_salary.ref_doctype = self.doctype
-			additional_salary.ref_docname = self.name
-			additional_salary.submit()
-
 	def set_total_advance_paid(self):
 		paid_amount = frappe.db.sql("""
 			select ifnull(sum(debit_in_account_currency), 0) as paid_amount
@@ -242,7 +226,11 @@
 		order_by = "from_date desc")[0].salary_structure
 
 def get_last_salary_slip(employee):
-	return frappe.get_list("Salary Slip", filters = {
+	salary_slips = frappe.get_list("Salary Slip", filters = {
 			"employee": employee, 'docstatus': 1
 		},
-		order_by = "start_date desc")[0].name
+		order_by = "start_date desc"
+	)
+	if not salary_slips:
+		return
+	return salary_slips[0].name
diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
index 483e346..23c99b1 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
@@ -11,10 +11,6 @@
 			{
 				'label': _('Payment'),
 				'items': ['Payment Entry']
-			},
-			{
-				'label': _('Additional Salary'),
-				'items': ['Additional Salary']
 			}
 		]
 	}
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index 7daea2d..8cb4728 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -22,14 +22,18 @@
 
 	def setUp(self):
 		frappe.db.sql("DELETE FROM `tabGratuity`")
-		frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
 
-	def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(self):
+	def test_get_last_salary_slip_should_return_none_for_new_employee(self):
+		new_employee = make_employee("new_employee@salary.com", company='_Test Company')
+		salary_slip = get_last_salary_slip(new_employee)
+		assert salary_slip is None
+
+	def test_check_gratuity_amount_based_on_current_slab(self):
 		employee, sal_slip = create_employee_and_get_last_salary_slip()
 
 		rule = get_gratuity_rule("Rule Under Unlimited Contract on termination (UAE)")
 
-		gratuity = create_gratuity(pay_via_salary_slip = 1, employee=employee, rule=rule.name)
+		gratuity = create_gratuity(employee=employee, rule=rule.name)
 
 		#work experience calculation
 		date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date'])
@@ -57,9 +61,6 @@
 
 		self.assertEqual(flt(gratuity_amount, 2), flt(gratuity.amount, 2))
 
-		#additional salary creation (Pay via salary slip)
-		self.assertTrue(frappe.db.exists("Additional Salary", {"ref_docname": gratuity.name}))
-
 	def test_check_gratuity_amount_based_on_all_previous_slabs(self):
 		employee, sal_slip = create_employee_and_get_last_salary_slip()
 		rule = get_gratuity_rule("Rule Under Limited Contract (UAE)")
@@ -137,14 +138,9 @@
 	gratuity.employee = args.employee
 	gratuity.posting_date = getdate()
 	gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)"
-	gratuity.pay_via_salary_slip = args.pay_via_salary_slip or 0
-	if gratuity.pay_via_salary_slip:
-		gratuity.payroll_date = getdate()
-		gratuity.salary_component = "Performance Bonus"
-	else:
-		gratuity.expense_account =  args.expense_account or 'Payment Account - _TC'
-		gratuity.payable_account = args.payable_account or get_payable_account("_Test Company")
-		gratuity.mode_of_payment = args.mode_of_payment or 'Cash'
+	gratuity.expense_account = args.expense_account or 'Payment Account - _TC'
+	gratuity.payable_account = args.payable_account or get_payable_account("_Test Company")
+	gratuity.mode_of_payment = args.mode_of_payment or 'Cash'
 
 	gratuity.save()
 	gratuity.submit()
diff --git a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js b/erpnext/payroll/doctype/payroll_period/test_payroll_period.js
deleted file mode 100644
index 8c4ded9..0000000
--- a/erpnext/payroll/doctype/payroll_period/test_payroll_period.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Payroll Period", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Payroll Period
-		() => frappe.tests.make('Payroll Period', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js b/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js
deleted file mode 100644
index a4b95d3..0000000
--- a/erpnext/payroll/doctype/retention_bonus/test_retention_bonus.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Retention Bonus", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Retention Bonus
-		() => frappe.tests.make('Retention Bonus', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/salary_component/test_salary_component.js b/erpnext/payroll/doctype/salary_component/test_salary_component.js
deleted file mode 100644
index c47d32d..0000000
--- a/erpnext/payroll/doctype/salary_component/test_salary_component.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Salary Component", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Salary Component
-		() => frappe.tests.make('Salary Component', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index d730fcf..636ec0b 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -828,7 +828,8 @@
 
 def make_holiday_list():
 	fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
-	if not frappe.db.get_value("Holiday List", "Salary Slip Test Holiday List"):
+	holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List")
+	if not holiday_list:
 		holiday_list = frappe.get_doc({
 			"doctype": "Holiday List",
 			"holiday_list_name": "Salary Slip Test Holiday List",
@@ -838,3 +839,6 @@
 		}).insert()
 		holiday_list.get_weekly_off_dates()
 		holiday_list.save()
+		holiday_list = holiday_list.name
+
+	return holiday_list
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js b/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js
deleted file mode 100644
index 2f52576..0000000
--- a/erpnext/payroll/doctype/salary_structure_assignment/test_salary_structure_assignment.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Salary Structure Assignment", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Salary Structure Assignment
-		() => frappe.tests.make('Salary Structure Assignment', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.js b/erpnext/portal/doctype/products_settings/test_products_settings.js
deleted file mode 100644
index b7049b3..0000000
--- a/erpnext/portal/doctype/products_settings/test_products_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Products Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Products Settings
-		() => frappe.tests.make('Products Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/projects/doctype/project/test_project.js b/erpnext/projects/doctype/project/test_project.js
deleted file mode 100644
index 16494f6..0000000
--- a/erpnext/projects/doctype/project/test_project.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Project", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Project
-		() => frappe.tests.make('Project', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/projects/doctype/project_type/test_project_type.js b/erpnext/projects/doctype/project_type/test_project_type.js
deleted file mode 100644
index c2198c4..0000000
--- a/erpnext/projects/doctype/project_type/test_project_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Project Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Project Type', [
-		// insert a new Project Type
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/projects/doctype/project_update/test_project_update.js b/erpnext/projects/doctype/project_update/test_project_update.js
deleted file mode 100644
index bda510b..0000000
--- a/erpnext/projects/doctype/project_update/test_project_update.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Project Update", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Project Update
-		() => frappe.tests.make('Project Update', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/projects/doctype/projects_settings/test_projects_settings.js b/erpnext/projects/doctype/projects_settings/test_projects_settings.js
deleted file mode 100644
index f6feaa4..0000000
--- a/erpnext/projects/doctype/projects_settings/test_projects_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Projects Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Projects Settings
-		() => frappe.tests.make('Projects Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.js b/erpnext/projects/doctype/timesheet/test_timesheet.js
deleted file mode 100644
index c081d6f..0000000
--- a/erpnext/projects/doctype/timesheet/test_timesheet.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Timesheet", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Timesheet
-		() => frappe.tests.make('Timesheet', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index 3c60e3e..6b70dab 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -38,6 +38,7 @@
 		"public/js/templates/item_quick_entry.html",
 		"public/js/utils/item_quick_entry.js",
 		"public/js/utils/customer_quick_entry.js",
+		"public/js/utils/supplier_quick_entry.js",
 		"public/js/education/student_button.html",
 		"public/js/education/assessment_result_tool.html",
 		"public/js/hub/hub_factory.js",
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 2538852..5f8966f 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -866,21 +866,25 @@
 
 		if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
 			in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
-			erpnext.utils.get_shipping_address(this.frm, function(){
+			erpnext.utils.get_shipping_address(this.frm, function() {
 				set_party_account(set_pricing);
 			});
 
 			// Get default company billing address in Purchase Invoice, Order and Receipt
-			frappe.call({
-				'method': 'frappe.contacts.doctype.address.address.get_default_address',
-				'args': {
-					'doctype': 'Company',
-					'name': this.frm.doc.company
-				},
-				'callback': function(r) {
-					me.frm.set_value('billing_address', r.message);
-				}
-			});
+			if (this.frm.doc.company && frappe.meta.get_docfield(this.frm.doctype, "billing_address")) {
+				frappe.call({
+					method: "erpnext.setup.doctype.company.company.get_default_company_address",
+					args: {name: this.frm.doc.company, existing_address: this.frm.doc.billing_address || ""},
+					debounce: 2000,
+					callback: function(r) {
+						if (r.message) {
+							me.frm.set_value("billing_address", r.message);
+						} else {
+							me.frm.set_value("company_address", "");
+						}
+					}
+				});
+			}
 
 		} else {
 			set_party_account(set_pricing);
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index 9f7f29a..febdb24 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -15,6 +15,7 @@
 import "./templates/item_quick_entry.html";
 import "./utils/item_quick_entry";
 import "./utils/customer_quick_entry";
+import "./utils/supplier_quick_entry";
 import "./education/student_button.html";
 import "./education/assessment_result_tool.html";
 import "./hub/hub_factory";
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 4d432e3..a492b32 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -289,8 +289,8 @@
 				company: frm.doc.company,
 				address: frm.doc.shipping_address
 			},
-			callback: function(r){
-				if (r.message){
+			callback: function(r) {
+				if (r.message) {
 					frm.set_value("shipping_address", r.message[0]) //Address title or name
 					frm.set_value("shipping_address_display", r.message[1]) //Address to be displayed on the page
 				}
diff --git a/erpnext/public/js/utils/supplier_quick_entry.js b/erpnext/public/js/utils/supplier_quick_entry.js
new file mode 100644
index 0000000..8d591a9
--- /dev/null
+++ b/erpnext/public/js/utils/supplier_quick_entry.js
@@ -0,0 +1,77 @@
+frappe.provide('frappe.ui.form');
+
+frappe.ui.form.SupplierQuickEntryForm = class SupplierQuickEntryForm extends frappe.ui.form.QuickEntryForm {
+	constructor(doctype, after_insert, init_callback, doc, force) {
+		super(doctype, after_insert, init_callback, doc, force);
+		this.skip_redirect_on_error = true;
+	}
+
+	render_dialog() {
+		this.mandatory = this.mandatory.concat(this.get_variant_fields());
+		super.render_dialog();
+	}
+
+	get_variant_fields() {
+		var variant_fields = [
+			{
+				fieldtype: "Section Break",
+				label: __("Primary Contact Details"),
+				collapsible: 1
+			},
+			{
+				label: __("Email Id"),
+				fieldname: "email_id",
+				fieldtype: "Data"
+			},
+			{
+				fieldtype: "Column Break"
+			},
+			{
+				label: __("Mobile Number"),
+				fieldname: "mobile_no",
+				fieldtype: "Data"
+			},
+			{
+				fieldtype: "Section Break",
+				label: __("Primary Address Details"),
+				collapsible: 1
+			},
+			{
+				label: __("Address Line 1"),
+				fieldname: "address_line1",
+				fieldtype: "Data"
+			},
+			{
+				label: __("Address Line 2"),
+				fieldname: "address_line2",
+				fieldtype: "Data"
+			},
+			{
+				label: __("ZIP Code"),
+				fieldname: "pincode",
+				fieldtype: "Data"
+			},
+			{
+				fieldtype: "Column Break"
+			},
+			{
+				label: __("City"),
+				fieldname: "city",
+				fieldtype: "Data"
+			},
+			{
+				label: __("State"),
+				fieldname: "state",
+				fieldtype: "Data"
+			},
+			{
+				label: __("Country"),
+				fieldname: "country",
+				fieldtype: "Link",
+				options: "Country"
+			}
+		];
+
+		return variant_fields;
+	}
+};
diff --git a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js b/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js
deleted file mode 100644
index 24c5fd3..0000000
--- a/erpnext/regional/doctype/gst_hsn_code/test_gst_hsn_code.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GST HSN Code", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new GST HSN Code
-		() => frappe.tests.make('GST HSN Code', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/regional/doctype/gst_settings/test_gst_settings.js b/erpnext/regional/doctype/gst_settings/test_gst_settings.js
deleted file mode 100644
index 00fcca6..0000000
--- a/erpnext/regional/doctype/gst_settings/test_gst_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: GST Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new GST Settings
-		() => frappe.tests.make('GST Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/regional/doctype/product_tax_category/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/regional/doctype/product_tax_category/__init__.py
diff --git a/erpnext/regional/doctype/product_tax_category/product_tax_category.js b/erpnext/regional/doctype/product_tax_category/product_tax_category.js
new file mode 100644
index 0000000..9f8e795
--- /dev/null
+++ b/erpnext/regional/doctype/product_tax_category/product_tax_category.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Product Tax Category', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/regional/doctype/product_tax_category/product_tax_category.json b/erpnext/regional/doctype/product_tax_category/product_tax_category.json
new file mode 100644
index 0000000..147cb34
--- /dev/null
+++ b/erpnext/regional/doctype/product_tax_category/product_tax_category.json
@@ -0,0 +1,70 @@
+{
+ "actions": [],
+ "autoname": "field:product_tax_code",
+ "creation": "2021-08-23 12:33:37.910225",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "product_tax_code",
+  "column_break_2",
+  "category_name",
+  "section_break_4",
+  "description"
+ ],
+ "fields": [
+  {
+   "fieldname": "product_tax_code",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Product Tax Code",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "label": "Description"
+  },
+  {
+   "fieldname": "category_name",
+   "fieldtype": "Data",
+   "label": "Category Name",
+   "length": 255
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-24 09:10:25.313642",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "Product Tax Category",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "category_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/product_tax_category/product_tax_category.py b/erpnext/regional/doctype/product_tax_category/product_tax_category.py
new file mode 100644
index 0000000..e476361
--- /dev/null
+++ b/erpnext/regional/doctype/product_tax_category/product_tax_category.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class ProductTaxCategory(Document):
+	pass
diff --git a/erpnext/regional/doctype/product_tax_category/test_product_tax_category.py b/erpnext/regional/doctype/product_tax_category/test_product_tax_category.py
new file mode 100644
index 0000000..53a5624
--- /dev/null
+++ b/erpnext/regional/doctype/product_tax_category/test_product_tax_category.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestProductTaxCategory(unittest.TestCase):
+	pass
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
deleted file mode 100644
index 765b51f..0000000
--- a/erpnext/regional/india/e_invoice/utils.py
+++ /dev/null
@@ -1,1131 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import os
-import re
-import jwt
-import sys
-import json
-import base64
-import frappe
-import six
-import traceback
-import io
-from frappe import _, bold
-from pyqrcode import create as qrcreate
-from frappe.utils.background_jobs import enqueue
-from frappe.utils.scheduler import is_scheduler_inactive
-from frappe.core.page.background_jobs.background_jobs import get_info
-from frappe.integrations.utils import make_post_request, make_get_request
-from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
-from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours
-
-@frappe.whitelist()
-def validate_eligibility(doc):
-	if isinstance(doc, six.string_types):
-		doc = json.loads(doc)
-
-	invalid_doctype = doc.get('doctype') != 'Sales Invoice'
-	if invalid_doctype:
-		return False
-
-	einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
-	if not einvoicing_enabled:
-		return False
-
-	einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01'
-	if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from):
-		return False
-
-	invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
-	invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
-	company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
-
-	# if export invoice, then taxes can be empty
-	# invoice can only be ineligible if no taxes applied and is not an export invoice
-	no_taxes_applied = not doc.get('taxes') and not doc.get('gst_category') == 'Overseas'
-	has_non_gst_item = any(d for d in doc.get('items', []) if d.get('is_non_gst'))
-
-	if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied or has_non_gst_item:
-		return False
-
-	return True
-
-def validate_einvoice_fields(doc):
-	invoice_eligible = validate_eligibility(doc)
-
-	if not invoice_eligible:
-		return
-
-	if doc.docstatus == 0 and doc._action == 'save':
-		if doc.irn:
-			frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
-		if len(doc.name) > 16:
-			raise_document_name_too_long_error()
-
-		doc.einvoice_status = 'Pending'
-
-	elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
-		frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
-
-	elif doc.irn and doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
-		frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
-
-def raise_document_name_too_long_error():
-	title = _('Document ID Too Long')
-	msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice')
-	msg += ', '
-	msg += _('document id {} exceed 16 letters.').format(bold(_('should not')))
-	msg += '<br><br>'
-	msg += _('You must {} your {} in order to have document id of {} length 16.').format(
-		bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
-	)
-	msg += _('Please account for ammended documents too.')
-	frappe.throw(msg, title=title)
-
-def read_json(name):
-	file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
-	with open(file_path, 'r') as f:
-		return cstr(f.read())
-
-def get_transaction_details(invoice):
-	supply_type = ''
-	if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
-	elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
-	elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
-	elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
-
-	if not supply_type:
-		rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
-		frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
-			title=_('Invalid Supply Type'))
-
-	return frappe._dict(dict(
-		tax_scheme='GST',
-		supply_type=supply_type,
-		reverse_charge=invoice.reverse_charge
-	))
-
-def get_doc_details(invoice):
-	if getdate(invoice.posting_date) < getdate('2021-01-01'):
-		frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed'))
-
-	invoice_type = 'CRN' if invoice.is_return else 'INV'
-
-	invoice_name = invoice.name
-	invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
-
-	return frappe._dict(dict(
-		invoice_type=invoice_type,
-		invoice_name=invoice_name,
-		invoice_date=invoice_date
-	))
-
-def validate_address_fields(address, is_shipping_address):
-	if ((not address.gstin and not is_shipping_address)
-		or not address.city
-		or not address.pincode
-		or not address.address_title
-		or not address.address_line1
-		or not address.gst_state_number):
-
-		frappe.throw(
-			msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
-			title=_('Missing Address Fields')
-		)
-
-def get_party_details(address_name, is_shipping_address=False):
-	addr = frappe.get_doc('Address', address_name)
-
-	validate_address_fields(addr, is_shipping_address)
-
-	if addr.gst_state_number == 97:
-		# according to einvoice standard
-		addr.pincode = 999999
-
-	party_address_details = frappe._dict(dict(
-		legal_name=sanitize_for_json(addr.address_title),
-		location=sanitize_for_json(addr.city),
-		pincode=addr.pincode, gstin=addr.gstin,
-		state_code=addr.gst_state_number,
-		address_line1=sanitize_for_json(addr.address_line1),
-		address_line2=sanitize_for_json(addr.address_line2)
-	))
-
-	return party_address_details
-
-def get_overseas_address_details(address_name):
-	address_title, address_line1, address_line2, city = frappe.db.get_value(
-		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city']
-	)
-
-	if not address_title or not address_line1 or not city:
-		frappe.throw(
-			msg=_('Address lines and city is mandatory for address {}. Please set them and try again.').format(
-				get_link_to_form('Address', address_name)
-			),
-			title=_('Missing Address Fields')
-		)
-
-	return frappe._dict(dict(
-		gstin='URP',
-		legal_name=sanitize_for_json(address_title),
-		location=city,
-		address_line1=sanitize_for_json(address_line1),
-		address_line2=sanitize_for_json(address_line2),
-		pincode=999999, state_code=96, place_of_supply=96
-	))
-
-def get_item_list(invoice):
-	item_list = []
-
-	for d in invoice.items:
-		einvoice_item_schema = read_json('einv_item_template')
-		item = frappe._dict({})
-		item.update(d.as_dict())
-
-		item.sr_no = d.idx
-		item.description = sanitize_for_json(d.item_name)
-
-		item.qty = abs(item.qty)
-		if flt(item.qty) != 0.0:
-			item.unit_rate = abs(item.taxable_value / item.qty)
-		else:
-			item.unit_rate = abs(item.taxable_value)
-		item.gross_amount = abs(item.taxable_value)
-		item.taxable_value = abs(item.taxable_value)
-		item.discount_amount = 0
-
-		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
-		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
-		item.is_service_item = 'Y' if item.gst_hsn_code and item.gst_hsn_code[:2] == "99" else 'N'
-		item.serial_no = ""
-
-		item = update_item_taxes(invoice, item)
-
-		item.total_value = abs(
-			item.taxable_value + item.igst_amount + item.sgst_amount +
-			item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
-		)
-		einv_item = einvoice_item_schema.format(item=item)
-		item_list.append(einv_item)
-
-	return ', '.join(item_list)
-
-def update_item_taxes(invoice, item):
-	gst_accounts = get_gst_accounts(invoice.company)
-	gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
-
-	for attr in [
-		'tax_rate', 'cess_rate', 'cess_nadv_amount',
-		'cgst_amount',  'sgst_amount', 'igst_amount',
-		'cess_amount', 'cess_nadv_amount', 'other_charges'
-		]:
-		item[attr] = 0
-
-	for t in invoice.taxes:
-		is_applicable = t.tax_amount and t.account_head in gst_accounts_list
-		if is_applicable:
-			# this contains item wise tax rate & tax amount (incl. discount)
-			item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
-
-			item_tax_rate = item_tax_detail[0]
-			# item tax amount excluding discount amount
-			item_tax_amount = (item_tax_rate / 100) * item.taxable_value
-
-			if t.account_head in gst_accounts.cess_account:
-				item_tax_amount_after_discount = item_tax_detail[1]
-				if t.charge_type == 'On Item Quantity':
-					item.cess_nadv_amount += abs(item_tax_amount_after_discount)
-				else:
-					item.cess_rate += item_tax_rate
-					item.cess_amount += abs(item_tax_amount_after_discount)
-
-			for tax_type in ['igst', 'cgst', 'sgst']:
-				if t.account_head in gst_accounts[f'{tax_type}_account']:
-					item.tax_rate += item_tax_rate
-					item[f'{tax_type}_amount'] += abs(item_tax_amount)
-		else:
-			# TODO: other charges per item
-			pass
-
-	return item
-
-def get_invoice_value_details(invoice):
-	invoice_value_details = frappe._dict(dict())
-	invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
-	invoice_value_details.invoice_discount_amt = 0
-
-	invoice_value_details.round_off = invoice.base_rounding_adjustment
-	invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
-	invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
-
-	invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
-
-	return invoice_value_details
-
-def update_invoice_taxes(invoice, invoice_value_details):
-	gst_accounts = get_gst_accounts(invoice.company)
-	gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
-
-	invoice_value_details.total_cgst_amt = 0
-	invoice_value_details.total_sgst_amt = 0
-	invoice_value_details.total_igst_amt = 0
-	invoice_value_details.total_cess_amt = 0
-	invoice_value_details.total_other_charges = 0
-	considered_rows = []
-
-	for t in invoice.taxes:
-		tax_amount = t.base_tax_amount_after_discount_amount
-		if t.account_head in gst_accounts_list:
-			if t.account_head in gst_accounts.cess_account:
-				# using after discount amt since item also uses after discount amt for cess calc
-				invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
-
-			for tax_type in ['igst', 'cgst', 'sgst']:
-				if t.account_head in gst_accounts[f'{tax_type}_account']:
-
-					invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
-				update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
-		else:
-			invoice_value_details.total_other_charges += abs(tax_amount)
-
-	return invoice_value_details
-
-def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
-	prev_row_id = cint(tax_row.row_id) - 1
-	if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
-		if tax_row.charge_type == 'On Previous Row Amount':
-			amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
-			invoice_value_details.total_other_charges -= abs(amount)
-			considered_rows.append(prev_row_id)
-		if tax_row.charge_type == 'On Previous Row Total':
-			amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
-			invoice_value_details.total_other_charges -= abs(amount)
-			considered_rows.append(prev_row_id)
-
-def get_payment_details(invoice):
-	payee_name = invoice.company
-	mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
-	paid_amount = invoice.base_paid_amount
-	outstanding_amount = invoice.outstanding_amount
-
-	return frappe._dict(dict(
-		payee_name=payee_name, mode_of_payment=mode_of_payment,
-		paid_amount=paid_amount, outstanding_amount=outstanding_amount
-	))
-
-def get_return_doc_reference(invoice):
-	invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
-	return frappe._dict(dict(
-		invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
-	))
-
-def get_eway_bill_details(invoice):
-	if invoice.is_return:
-		frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
-			title=_('Invalid Fields'))
-
-
-	mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
-	vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
-
-	return frappe._dict(dict(
-		gstin=invoice.gst_transporter_id,
-		name=invoice.transporter_name,
-		mode_of_transport=mode_of_transport[invoice.mode_of_transport],
-		distance=invoice.distance or 0,
-		document_name=invoice.lr_no,
-		document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
-		vehicle_no=invoice.vehicle_no,
-		vehicle_type=vehicle_type[invoice.gst_vehicle_type]
-	))
-
-def validate_mandatory_fields(invoice):
-	if not invoice.company_address:
-		frappe.throw(
-			_('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
-			title=_('Missing Fields')
-		)
-	if not invoice.customer_address:
-		frappe.throw(
-			_('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
-			title=_('Missing Fields')
-		)
-	if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
-		frappe.throw(
-			_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
-			title=_('Missing Fields')
-		)
-	if invoice.gst_category != 'Overseas' and not frappe.db.get_value('Address', invoice.customer_address, 'gstin'):
-		frappe.throw(
-			_('GSTIN is mandatory to fetch customer GSTIN details. Please enter GSTIN in selected customer address.'),
-			title=_('Missing Fields')
-		)
-
-def validate_totals(einvoice):
-	item_list = einvoice['ItemList']
-	value_details = einvoice['ValDtls']
-
-	total_item_ass_value = 0
-	total_item_cgst_value = 0
-	total_item_sgst_value = 0
-	total_item_igst_value = 0
-	total_item_value = 0
-	for item in item_list:
-		total_item_ass_value += flt(item['AssAmt'])
-		total_item_cgst_value += flt(item['CgstAmt'])
-		total_item_sgst_value += flt(item['SgstAmt'])
-		total_item_igst_value += flt(item['IgstAmt'])
-		total_item_value += flt(item['TotItemVal'])
-
-		if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
-			frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
-
-	if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
-		frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
-
-	if abs(
-		flt(value_details['TotInvVal']) + flt(value_details['Discount']) -
-		flt(value_details['OthChrg']) - flt(value_details['RndOffAmt']) -
-		total_item_value) > 1:
-		frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
-
-	calculated_invoice_value = \
-			flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
-			+ flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
-			+ flt(value_details['OthChrg']) + flt(value_details['RndOffAmt']) - flt(value_details['Discount'])
-
-	if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
-		frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
-
-def make_einvoice(invoice):
-	validate_mandatory_fields(invoice)
-
-	schema = read_json('einv_template')
-
-	transaction_details = get_transaction_details(invoice)
-	item_list = get_item_list(invoice)
-	doc_details = get_doc_details(invoice)
-	invoice_value_details = get_invoice_value_details(invoice)
-	seller_details = get_party_details(invoice.company_address)
-
-	if invoice.gst_category == 'Overseas':
-		buyer_details = get_overseas_address_details(invoice.customer_address)
-	else:
-		buyer_details = get_party_details(invoice.customer_address)
-		place_of_supply = get_place_of_supply(invoice, invoice.doctype)
-		if place_of_supply:
-			place_of_supply = place_of_supply.split('-')[0]
-		else:
-			place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
-		buyer_details.update(dict(place_of_supply=place_of_supply))
-
-	seller_details.update(dict(legal_name=invoice.company))
-	buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
-
-	shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
-	if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
-		if invoice.gst_category == 'Overseas':
-			shipping_details = get_overseas_address_details(invoice.shipping_address_name)
-		else:
-			shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True)
-
-	if invoice.is_pos and invoice.base_paid_amount:
-		payment_details = get_payment_details(invoice)
-
-	if invoice.is_return and invoice.return_against:
-		prev_doc_details = get_return_doc_reference(invoice)
-
-	if invoice.transporter and not invoice.is_return:
-		eway_bill_details = get_eway_bill_details(invoice)
-
-	# not yet implemented
-	dispatch_details = period_details = export_details = frappe._dict({})
-
-	einvoice = schema.format(
-		transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details,
-		seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
-		item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details,
-		period_details=period_details, prev_doc_details=prev_doc_details,
-		export_details=export_details, eway_bill_details=eway_bill_details
-	)
-
-	try:
-		einvoice = safe_json_load(einvoice)
-		einvoice = santize_einvoice_fields(einvoice)
-	except Exception:
-		show_link_to_error_log(invoice, einvoice)
-
-	validate_totals(einvoice)
-
-	return einvoice
-
-def show_link_to_error_log(invoice, einvoice):
-	err_log = log_error(einvoice)
-	link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
-	frappe.throw(
-		_('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
-			invoice.name, link_to_error_log),
-		title=_('E Invoice Creation Failed')
-	)
-
-def log_error(data=None):
-	if isinstance(data, six.string_types):
-		data = json.loads(data)
-
-	seperator = "--" * 50
-	err_tb = traceback.format_exc()
-	err_msg = str(sys.exc_info()[1])
-	data = json.dumps(data, indent=4)
-
-	message = "\n".join([
-		"Error", err_msg, seperator,
-		"Data:", data, seperator,
-		"Exception:", err_tb
-	])
-	frappe.log_error(title=_('E Invoice Request Failed'), message=message)
-
-def santize_einvoice_fields(einvoice):
-	int_fields = ["Pin","Distance","CrDay"]
-	float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
-	copy = einvoice.copy()
-	for key, value in copy.items():
-		if isinstance(value, list):
-			for idx, d in enumerate(value):
-				santized_dict = santize_einvoice_fields(d)
-				if santized_dict:
-					einvoice[key][idx] = santized_dict
-				else:
-					einvoice[key].pop(idx)
-
-			if not einvoice[key]:
-				einvoice.pop(key, None)
-
-		elif isinstance(value, dict):
-			santized_dict = santize_einvoice_fields(value)
-			if santized_dict:
-				einvoice[key] = santized_dict
-			else:
-				einvoice.pop(key, None)
-
-		elif not value or value == "None":
-			einvoice.pop(key, None)
-
-		elif key in float_fields:
-			einvoice[key] = flt(value, 2)
-
-		elif key in int_fields:
-			einvoice[key] = cint(value)
-
-	return einvoice
-
-def safe_json_load(json_string):
-	try:
-		return json.loads(json_string)
-	except json.JSONDecodeError as e:
-		# print a snippet of 40 characters around the location where error occured
-		pos = e.pos
-		start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
-		snippet = json_string[start:end]
-		frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
-
-class RequestFailed(Exception):
-	pass
-class CancellationNotAllowed(Exception):
-	pass
-
-class GSPConnector():
-	def __init__(self, doctype=None, docname=None):
-		self.doctype = doctype
-		self.docname = docname
-
-		self.set_invoice()
-		self.set_credentials()
-
-		# authenticate url is same for sandbox & live
-		self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
-		self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test'
-
-		self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
-		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
-		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
-		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
-		self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
-		self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
-
-	def set_invoice(self):
-		self.invoice = None
-		if self.doctype and self.docname:
-			self.invoice = frappe.get_cached_doc(self.doctype, self.docname)
-
-	def set_credentials(self):
-		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
-
-		if not self.e_invoice_settings.enable:
-			frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
-
-		if self.invoice:
-			gstin = self.get_seller_gstin()
-			credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
-			if credentials_for_gstin:
-				self.credentials = credentials_for_gstin[0]
-			else:
-				frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
-		else:
-			self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
-
-	def get_seller_gstin(self):
-		gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
-		if not gstin:
-			frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
-		return gstin
-
-	def get_auth_token(self):
-		if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
-			self.fetch_auth_token()
-
-		return self.e_invoice_settings.auth_token
-
-	def make_request(self, request_type, url, headers=None, data=None):
-		if request_type == 'post':
-			res = make_post_request(url, headers=headers, data=data)
-		else:
-			res = make_get_request(url, headers=headers, data=data)
-
-		self.log_request(url, headers, data, res)
-		return res
-
-	def log_request(self, url, headers, data, res):
-		headers.update({ 'password': self.credentials.password })
-		request_log = frappe.get_doc({
-			"doctype": "E Invoice Request Log",
-			"user": frappe.session.user,
-			"reference_invoice": self.invoice.name if self.invoice else None,
-			"url": url,
-			"headers": json.dumps(headers, indent=4) if headers else None,
-			"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
-			"response": json.dumps(res, indent=4) if res else None
-		})
-		request_log.save(ignore_permissions=True)
-		frappe.db.commit()
-
-	def fetch_auth_token(self):
-		headers = {
-			'gspappid': frappe.conf.einvoice_client_id,
-			'gspappsecret': frappe.conf.einvoice_client_secret
-		}
-		res = {}
-		try:
-			res = self.make_request('post', self.authenticate_url, headers)
-			self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
-			self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
-			self.e_invoice_settings.save(ignore_permissions=True)
-			self.e_invoice_settings.reload()
-
-		except Exception:
-			log_error(res)
-			self.raise_error(True)
-
-	def get_headers(self):
-		return {
-			'content-type': 'application/json',
-			'user_name': self.credentials.username,
-			'password': self.credentials.get_password(),
-			'gstin': self.credentials.gstin,
-			'authorization': self.get_auth_token(),
-			'requestid': str(base64.b64encode(os.urandom(18))),
-		}
-
-	def fetch_gstin_details(self, gstin):
-		headers = self.get_headers()
-
-		try:
-			params = '?gstin={gstin}'.format(gstin=gstin)
-			res = self.make_request('get', self.gstin_details_url + params, headers)
-			if res.get('success'):
-				return res.get('result')
-			else:
-				log_error(res)
-				raise RequestFailed
-
-		except RequestFailed:
-			self.raise_error()
-
-		except Exception:
-			log_error()
-			self.raise_error(True)
-	@staticmethod
-	def get_gstin_details(gstin):
-		'''fetch and cache GSTIN details'''
-		if not hasattr(frappe.local, 'gstin_cache'):
-			frappe.local.gstin_cache = {}
-
-		key = gstin
-		gsp_connector = GSPConnector()
-		details = gsp_connector.fetch_gstin_details(gstin)
-
-		frappe.local.gstin_cache[key] = details
-		frappe.cache().hset('gstin_cache', key, details)
-		return details
-
-	def generate_irn(self):
-		data = {}
-		try:
-			headers = self.get_headers()
-			einvoice = make_einvoice(self.invoice)
-			data = json.dumps(einvoice, indent=4)
-			res = self.make_request('post', self.generate_irn_url, headers, data)
-
-			if res.get('success'):
-				self.set_einvoice_data(res.get('result'))
-
-			elif '2150' in res.get('message'):
-				# IRN already generated but not updated in invoice
-				# Extract the IRN from the response description and fetch irn details
-				irn = res.get('result')[0].get('Desc').get('Irn')
-				irn_details = self.get_irn_details(irn)
-				if irn_details:
-					self.set_einvoice_data(irn_details)
-				else:
-					raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
-						Contact ERPNext support to resolve the issue.')
-
-			else:
-				raise RequestFailed
-
-		except RequestFailed:
-			errors = self.sanitize_error_message(res.get('message'))
-			self.set_failed_status(errors=errors)
-			self.raise_error(errors=errors)
-
-		except Exception as e:
-			self.set_failed_status(errors=str(e))
-			log_error(data)
-			self.raise_error(True)
-
-	@staticmethod
-	def bulk_generate_irn(invoices):
-		gsp_connector = GSPConnector()
-		gsp_connector.doctype = 'Sales Invoice'
-
-		failed = []
-
-		for invoice in invoices:
-			try:
-				gsp_connector.docname = invoice
-				gsp_connector.set_invoice()
-				gsp_connector.set_credentials()
-				gsp_connector.generate_irn()
-
-			except Exception as e:
-				failed.append({
-					'docname': invoice,
-					'message': str(e)
-				})
-
-		return failed
-
-	def get_irn_details(self, irn):
-		headers = self.get_headers()
-
-		try:
-			params = '?irn={irn}'.format(irn=irn)
-			res = self.make_request('get', self.irn_details_url + params, headers)
-			if res.get('success'):
-				return res.get('result')
-			else:
-				raise RequestFailed
-
-		except RequestFailed:
-			errors = self.sanitize_error_message(res.get('message'))
-			self.raise_error(errors=errors)
-
-		except Exception:
-			log_error()
-			self.raise_error(True)
-
-	def cancel_irn(self, irn, reason, remark):
-		data, res = {}, {}
-		try:
-			# validate cancellation
-			if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24:
-				frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
-			if not irn:
-				frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
-
-			headers = self.get_headers()
-			data = json.dumps({
-				'Irn': irn,
-				'Cnlrsn': reason,
-				'Cnlrem': remark
-			}, indent=4)
-
-			res = self.make_request('post', self.cancel_irn_url, headers, data)
-			if res.get('success') or '9999' in res.get('message'):
-				self.invoice.irn_cancelled = 1
-				self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else ""
-				self.invoice.einvoice_status = 'Cancelled'
-				self.invoice.flags.updater_reference = {
-					'doctype': self.invoice.doctype,
-					'docname': self.invoice.name,
-					'label': _('IRN Cancelled - {}').format(remark)
-				}
-				self.update_invoice()
-
-			else:
-				raise RequestFailed
-
-		except RequestFailed:
-			errors = self.sanitize_error_message(res.get('message'))
-			self.set_failed_status(errors=errors)
-			self.raise_error(errors=errors)
-
-		except CancellationNotAllowed as e:
-			self.set_failed_status(errors=str(e))
-			self.raise_error(errors=str(e))
-
-		except Exception as e:
-			self.set_failed_status(errors=str(e))
-			log_error(data)
-			self.raise_error(True)
-
-	@staticmethod
-	def bulk_cancel_irn(invoices, reason, remark):
-		gsp_connector = GSPConnector()
-		gsp_connector.doctype = 'Sales Invoice'
-
-		failed = []
-
-		for invoice in invoices:
-			try:
-				gsp_connector.docname = invoice
-				gsp_connector.set_invoice()
-				gsp_connector.set_credentials()
-				irn = gsp_connector.invoice.irn
-				gsp_connector.cancel_irn(irn, reason, remark)
-
-			except Exception as e:
-				failed.append({
-					'docname': invoice,
-					'message': str(e)
-				})
-
-		return failed
-
-	def generate_eway_bill(self, **kwargs):
-		args = frappe._dict(kwargs)
-
-		headers = self.get_headers()
-		eway_bill_details = get_eway_bill_details(args)
-		data = json.dumps({
-			'Irn': args.irn,
-			'Distance': cint(eway_bill_details.distance),
-			'TransMode': eway_bill_details.mode_of_transport,
-			'TransId': eway_bill_details.gstin,
-			'TransName': eway_bill_details.transporter,
-			'TrnDocDt': eway_bill_details.document_date,
-			'TrnDocNo': eway_bill_details.document_name,
-			'VehNo': eway_bill_details.vehicle_no,
-			'VehType': eway_bill_details.vehicle_type
-		}, indent=4)
-
-		try:
-			res = self.make_request('post', self.generate_ewaybill_url, headers, data)
-			if res.get('success'):
-				self.invoice.ewaybill = res.get('result').get('EwbNo')
-				self.invoice.eway_bill_validity = res.get('result').get('EwbValidTill')
-				self.invoice.eway_bill_cancelled = 0
-				self.invoice.update(args)
-				self.invoice.flags.updater_reference = {
-					'doctype': self.invoice.doctype,
-					'docname': self.invoice.name,
-					'label': _('E-Way Bill Generated')
-				}
-				self.update_invoice()
-
-			else:
-				raise RequestFailed
-
-		except RequestFailed:
-			errors = self.sanitize_error_message(res.get('message'))
-			self.raise_error(errors=errors)
-
-		except Exception:
-			log_error(data)
-			self.raise_error(True)
-
-	def cancel_eway_bill(self, eway_bill, reason, remark):
-		headers = self.get_headers()
-		data = json.dumps({
-			'ewbNo': eway_bill,
-			'cancelRsnCode': reason,
-			'cancelRmrk': remark
-		}, indent=4)
-		headers["username"] = headers["user_name"]
-		del headers["user_name"]
-		try:
-			res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
-			if res.get('success'):
-				self.invoice.ewaybill = ''
-				self.invoice.eway_bill_cancelled = 1
-				self.invoice.flags.updater_reference = {
-					'doctype': self.invoice.doctype,
-					'docname': self.invoice.name,
-					'label': _('E-Way Bill Cancelled - {}').format(remark)
-				}
-				self.update_invoice()
-
-			else:
-				raise RequestFailed
-
-		except RequestFailed:
-			errors = self.sanitize_error_message(res.get('message'))
-			self.raise_error(errors=errors)
-
-		except Exception:
-			log_error(data)
-			self.raise_error(True)
-
-	def sanitize_error_message(self, message):
-		'''
-			On validation errors, response message looks something like this:
-			message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
-						3095 : Supplier GSTIN is inactive'
-			we search for string between ':' to extract the error messages
-			errors = [
-				': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
-				': Test'
-			]
-			then we trim down the message by looping over errors
-		'''
-		if not message:
-			return []
-
-		errors = re.findall(': [^:]+', message)
-		for idx, e in enumerate(errors):
-			# remove colons
-			errors[idx] = errors[idx].replace(':', '').strip()
-			# if not last
-			if idx != len(errors) - 1:
-				# remove last 7 chars eg: ', 3095 '
-				errors[idx] = errors[idx][:-6]
-
-		return errors
-
-	def raise_error(self, raise_exception=False, errors=[]):
-		title = _('E Invoice Request Failed')
-		if errors:
-			frappe.throw(errors, title=title, as_list=1)
-		else:
-			link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
-			frappe.msgprint(
-				_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
-				title=title,
-				raise_exception=raise_exception,
-				indicator='red'
-			)
-
-	def set_einvoice_data(self, res):
-		enc_signed_invoice = res.get('SignedInvoice')
-		dec_signed_invoice = jwt.decode(enc_signed_invoice, options={"verify_signature": False})['data']
-
-		self.invoice.irn = res.get('Irn')
-		self.invoice.ewaybill = res.get('EwbNo')
-		self.invoice.eway_bill_validity = res.get('EwbValidTill')
-		self.invoice.ack_no = res.get('AckNo')
-		self.invoice.ack_date = res.get('AckDt')
-		self.invoice.signed_einvoice = dec_signed_invoice
-		self.invoice.ack_no = res.get('AckNo')
-		self.invoice.ack_date = res.get('AckDt')
-		self.invoice.signed_qr_code = res.get('SignedQRCode')
-		self.invoice.einvoice_status = 'Generated'
-
-		self.attach_qrcode_image()
-
-		self.invoice.flags.updater_reference = {
-			'doctype': self.invoice.doctype,
-			'docname': self.invoice.name,
-			'label': _('IRN Generated')
-		}
-		self.update_invoice()
-
-	def attach_qrcode_image(self):
-		qrcode = self.invoice.signed_qr_code
-		doctype = self.invoice.doctype
-		docname = self.invoice.name
-		filename = 'QRCode_{}.png'.format(docname).replace(os.path.sep, "__")
-
-		qr_image = io.BytesIO()
-		url = qrcreate(qrcode, error='L')
-		url.png(qr_image, scale=2, quiet_zone=1)
-		_file = frappe.get_doc({
-			"doctype": "File",
-			"file_name": filename,
-			"attached_to_doctype": doctype,
-			"attached_to_name": docname,
-			"attached_to_field": "qrcode_image",
-			"is_private": 0,
-			"content": qr_image.getvalue()})
-		_file.save()
-		frappe.db.commit()
-		self.invoice.qrcode_image = _file.file_url
-
-	def update_invoice(self):
-		self.invoice.flags.ignore_validate_update_after_submit = True
-		self.invoice.flags.ignore_validate = True
-		self.invoice.save()
-
-	def set_failed_status(self, errors=None):
-		frappe.db.rollback()
-		self.invoice.einvoice_status = 'Failed'
-		self.invoice.failure_description = self.get_failure_message(errors) if errors else ""
-		self.update_invoice()
-		frappe.db.commit()
-
-	def get_failure_message(self, errors):
-		if isinstance(errors, list):
-			errors = ', '.join(errors)
-		return errors
-
-def sanitize_for_json(string):
-	"""Escape JSON specific characters from a string."""
-
-	# json.dumps adds double-quotes to the string. Indexing to remove them.
-	return json.dumps(string)[1:-1]
-
-@frappe.whitelist()
-def get_einvoice(doctype, docname):
-	invoice = frappe.get_doc(doctype, docname)
-	return make_einvoice(invoice)
-
-@frappe.whitelist()
-def generate_irn(doctype, docname):
-	gsp_connector = GSPConnector(doctype, docname)
-	gsp_connector.generate_irn()
-
-@frappe.whitelist()
-def cancel_irn(doctype, docname, irn, reason, remark):
-	gsp_connector = GSPConnector(doctype, docname)
-	gsp_connector.cancel_irn(irn, reason, remark)
-
-@frappe.whitelist()
-def generate_eway_bill(doctype, docname, **kwargs):
-	gsp_connector = GSPConnector(doctype, docname)
-	gsp_connector.generate_eway_bill(**kwargs)
-
-@frappe.whitelist()
-def cancel_eway_bill(doctype, docname):
-	# TODO: uncomment when eway_bill api from Adequare is enabled
-	# gsp_connector = GSPConnector(doctype, docname)
-	# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
-
-	frappe.db.set_value(doctype, docname, 'ewaybill', '')
-	frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
-
-@frappe.whitelist()
-def generate_einvoices(docnames):
-	docnames = json.loads(docnames) or []
-
-	if len(docnames) < 10:
-		failures = GSPConnector.bulk_generate_irn(docnames)
-		frappe.local.message_log = []
-
-		if failures:
-			show_bulk_action_failure_message(failures)
-
-		success = len(docnames) - len(failures)
-		frappe.msgprint(
-			_('{} e-invoices generated successfully').format(success),
-			title=_('Bulk E-Invoice Generation Complete')
-		)
-
-	else:
-		enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames)
-
-def schedule_bulk_generate_irn(docnames):
-	failures = GSPConnector.bulk_generate_irn(docnames)
-	frappe.local.message_log = []
-
-	frappe.publish_realtime("bulk_einvoice_generation_complete", {
-		"user": frappe.session.user,
-		"failures": failures,
-		"invoices": docnames
-	})
-
-def show_bulk_action_failure_message(failures):
-	for doc in failures:
-		docname = '<a href="sales-invoice/{0}">{0}</a>'.format(doc.get('docname'))
-		message = doc.get('message').replace("'", '"')
-		if message[0] == '[':
-			errors = json.loads(message)
-			error_list = ''.join(['<li>{}</li>'.format(err) for err in errors])
-			message = '''{} has following errors:<br>
-				<ul style="padding-left: 20px; padding-top: 5px">{}</ul>'''.format(docname, error_list)
-		else:
-			message = '{} - {}'.format(docname, message)
-
-		frappe.msgprint(
-			message,
-			title=_('Bulk E-Invoice Generation Complete'),
-			indicator='red'
-		)
-
-@frappe.whitelist()
-def cancel_irns(docnames, reason, remark):
-	docnames = json.loads(docnames) or []
-
-	if len(docnames) < 10:
-		failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
-		frappe.local.message_log = []
-
-		if failures:
-			show_bulk_action_failure_message(failures)
-
-		success = len(docnames) - len(failures)
-		frappe.msgprint(
-			_('{} e-invoices cancelled successfully').format(success),
-			title=_('Bulk E-Invoice Cancellation Complete')
-		)
-	else:
-		enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark)
-
-def schedule_bulk_cancel_irn(docnames, reason, remark):
-	failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
-	frappe.local.message_log = []
-
-	frappe.publish_realtime("bulk_einvoice_cancellation_complete", {
-		"user": frappe.session.user,
-		"failures": failures,
-		"invoices": docnames
-	})
-
-def enqueue_bulk_action(job, **kwargs):
-	check_scheduler_status()
-
-	enqueue(
-		job,
-		**kwargs,
-		queue="long",
-		timeout=10000,
-		event="processing_bulk_einvoice_action",
-		now=frappe.conf.developer_mode or frappe.flags.in_test,
-	)
-
-	if job == schedule_bulk_generate_irn:
-		msg = _('E-Invoices will be generated in a background process.')
-	else:
-		msg = _('E-Invoices will be cancelled in a background process.')
-
-	frappe.msgprint(msg, alert=1)
-
-def check_scheduler_status():
-	if is_scheduler_inactive() and not frappe.flags.in_test:
-		frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
-
-def job_already_enqueued(job_name):
-	enqueued_jobs = [d.get("job_name") for d in get_info()]
-	if job_name in enqueued_jobs:
-		return True
diff --git a/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py
new file mode 100644
index 0000000..dea17a6
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/test_vat_audit_report.py
@@ -0,0 +1,193 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from unittest import TestCase
+from frappe.utils import today
+
+from erpnext.accounts.doctype.account.test_account import create_account
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+
+from erpnext.regional.report.vat_audit_report.vat_audit_report import execute
+
+class TestVATAuditReport(TestCase):
+	def setUp(self):
+		frappe.set_user("Administrator")
+		make_company("_Test Company SA VAT", "_TCSV")
+
+		create_account(account_name="VAT - 0%", account_type="Tax",
+			parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
+		create_account(account_name="VAT - 15%", account_type="Tax",
+			parent_account="Duties and Taxes - _TCSV", company="_Test Company SA VAT")
+		set_sa_vat_accounts()
+
+		make_item("_Test SA VAT Item")
+		make_item("_Test SA VAT Zero Rated Item", properties = {"is_zero_rated": 1})
+
+		make_customer()
+		make_supplier()
+
+		make_sales_invoices()
+		create_purchase_invoices()
+
+	def tearDown(self):
+		frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company SA VAT'")
+		frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company SA VAT'")
+
+	def test_vat_audit_report(self):
+		filters = {
+			"company": "_Test Company SA VAT",
+			"from_date": today(),
+			"to_date": today()
+		}
+		columns, data = execute(filters)
+		total_tax_amount = 0
+		total_row_tax = 0
+		for row in data:
+			keys = row.keys()
+			# skips total row tax_amount in if.. and skips section header in elif..
+			if 'voucher_no' in keys:
+				total_tax_amount = total_tax_amount + row['tax_amount']
+			elif 'tax_amount' in keys:
+				total_row_tax = total_row_tax + row['tax_amount']
+
+		self.assertEqual(total_tax_amount, total_row_tax)
+
+def make_company(company_name, abbr):
+	if not frappe.db.exists("Company", company_name):
+		company = frappe.get_doc({
+			"doctype": "Company",
+			"company_name": company_name,
+			"abbr": abbr,
+			"default_currency": "ZAR",
+			"country": "South Africa",
+			"create_chart_of_accounts_based_on": "Standard Template"
+		})
+		company.insert()
+	else:
+		company = frappe.get_doc("Company", company_name)
+
+	company.create_default_warehouses()
+
+	if not frappe.db.get_value("Cost Center", {"is_group": 0, "company": company.name}):
+		company.create_default_cost_center()
+
+	company.save()
+
+	return company
+
+def set_sa_vat_accounts():
+	if not frappe.db.exists("South Africa VAT Settings", "_Test Company SA VAT"):
+		vat_accounts = frappe.get_all(
+			"Account",
+			fields=["name"],
+			filters = {
+				"company": "_Test Company SA VAT",
+				"is_group": 0,
+				"account_type": "Tax"
+			}
+		)
+
+		sa_vat_accounts = []
+		for account in vat_accounts:
+			sa_vat_accounts.append({
+				"doctype": "South Africa VAT Account",
+				"account": account.name
+			})
+
+		frappe.get_doc({
+			"company": "_Test Company SA VAT",
+			"vat_accounts": sa_vat_accounts,
+			"doctype": "South Africa VAT Settings",
+		}).insert()
+
+def make_customer():
+	if not frappe.db.exists("Customer", "_Test SA Customer"):
+		frappe.get_doc({
+			"doctype": "Customer",
+			"customer_name": "_Test SA Customer",
+			"customer_type": "Company",
+		}).insert()
+
+def make_supplier():
+	if not frappe.db.exists("Supplier", "_Test SA Supplier"):
+		frappe.get_doc({
+			"doctype": "Supplier",
+			"supplier_name": "_Test SA Supplier",
+			"supplier_type": "Company",
+			"supplier_group":"All Supplier Groups"
+		}).insert()
+
+def make_item(item_code, properties=None):
+	if not frappe.db.exists("Item", item_code):
+		item = frappe.get_doc({
+			"doctype": "Item",
+			"item_code": item_code,
+			"item_name": item_code,
+			"description": item_code,
+			"item_group": "Products"
+		})
+
+		if properties:
+			item.update(properties)
+
+		item.insert()
+
+def make_sales_invoices():
+	def make_sales_invoices_wrapper(item, rate, tax_account, tax_rate, tax=True):
+		si = create_sales_invoice(
+			company="_Test Company SA VAT",
+			customer = "_Test SA Customer",
+			currency = "ZAR",
+			item=item,
+			rate=rate,
+			warehouse = "Finished Goods - _TCSV",
+			debit_to = "Debtors - _TCSV",
+			income_account = "Sales - _TCSV",
+			expense_account = "Cost of Goods Sold - _TCSV",
+			cost_center = "Main - _TCSV",
+			do_not_save=1
+		)
+		if tax:
+			si.append("taxes", {
+					"charge_type": "On Net Total",
+					"account_head": tax_account,
+					"cost_center": "Main - _TCSV",
+					"description": "VAT 15% @ 15.0",
+					"rate": tax_rate
+				})
+
+		si.submit()
+
+	test_item = "_Test SA VAT Item"
+	test_zero_rated_item = "_Test SA VAT Zero Rated Item"
+
+	make_sales_invoices_wrapper(test_item, 100.0, "VAT - 15% - _TCSV", 15.0)
+	make_sales_invoices_wrapper(test_zero_rated_item, 100.0, "VAT - 0% - _TCSV", 0.0)
+
+def create_purchase_invoices():
+	pi = make_purchase_invoice(
+		company = "_Test Company SA VAT",
+		supplier = "_Test SA Supplier",
+		supplier_warehouse = "Finished Goods - _TCSV",
+		warehouse = "Finished Goods - _TCSV",
+		currency = "ZAR",
+		cost_center = "Main - _TCSV",
+		expense_account = "Cost of Goods Sold - _TCSV",
+		item = "_Test SA VAT Item",
+		qty = 1,
+		rate = 100,
+		uom = "Nos",
+		do_not_save = 1
+	)
+	pi.append("taxes", {
+		"charge_type": "On Net Total",
+		"account_head": "VAT - 15% - _TCSV",
+		"cost_center": "Main - _TCSV",
+		"description": "VAT 15% @ 15.0",
+		"rate": 15.0
+	})
+
+	pi.submit()
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
index 17aca17..88f6b92 100644
--- a/erpnext/regional/report/vat_audit_report/vat_audit_report.py
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -1,11 +1,11 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
 import frappe
 import json
 from frappe import _
-from frappe.utils import formatdate
+from frappe.utils import formatdate, get_link_to_form
 
 def execute(filters=None):
 	return VATAuditReport(filters).run()
@@ -42,7 +42,8 @@
 		self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
 			filters = {"parent": self.filters.company}, pluck="account")
 		if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
-			frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings"))
+			link_to_settings = get_link_to_form("South Africa VAT Settings", "", label="South Africa VAT Settings")
+			frappe.throw(_("Please set VAT Accounts in {0}").format(link_to_settings))
 
 	def get_invoice_data(self, doctype):
 		conditions = self.get_conditions()
@@ -69,7 +70,7 @@
 
 		items = frappe.db.sql("""
 			SELECT
-				item_code, parent, taxable_value, base_net_amount, is_zero_rated
+				item_code, parent, base_net_amount, is_zero_rated
 			FROM
 				`tab%s Item`
 			WHERE
@@ -79,7 +80,7 @@
 			if d.item_code not in self.invoice_items.get(d.parent, {}):
 				self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
 					'net_amount': 0.0})
-				self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
+				self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('base_net_amount', 0)
 				self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
 
 	def get_items_based_on_tax_rate(self, doctype):
diff --git a/erpnext/regional/united_states/product_tax_category_data.json b/erpnext/regional/united_states/product_tax_category_data.json
new file mode 100644
index 0000000..4527bb2
--- /dev/null
+++ b/erpnext/regional/united_states/product_tax_category_data.json
@@ -0,0 +1,4084 @@
+{
+    "categories": [
+        {
+            "description": "An item commonly used by a student in a course of study.  This category is limited to the following items...binders, blackboard chalk, cellophane tape, compasses, composition books, crayons, erasers, folders, glue/paste/glue sticks, highlighters, index cards, index card boxes, legal pads, lunch boxes, markers, notebooks, paper (copy, graph, tracing, manila, colored, construction, notebook), pencils, pencil boxes, pencil sharpeners, pens, posterboard, protractors, rulers, scissors, writing tablets.",
+            "name": "School Supplies",
+            "product_tax_code": "44121600A0001"
+        },
+        {
+            "description": "This is a labor charge for: the planning and design of interior spaces; preparation of layout drawings, schedules, and specifications pertaining to the planning and design of interior spaces; furniture arranging; design and planning of furniture, fixtures, and cabinetry; staging; lighting and sound design; and the selection, purchase, and arrangement of surface coverings, draperies, furniture, and other decorations.",
+            "name": "Interior Decorating Services",
+            "product_tax_code": "73890600A0000"
+        },
+        {
+            "description": "Ammunition for firearms with a barrel greater than an internal diameter of .50 caliber or a shotgun larger than 10 gauge., including bullets, shotgun shells, and gunpowder.",
+            "name": "Ammunition - designed for firearms other than small arms.",
+            "product_tax_code": "46101600A0002"
+        },
+        {
+            "description": "Firearms, limited to pistols, revolvers, rifles with a barrel greater than an internal diameter of .50 caliber or a shotgun larger than 10 gauge.",
+            "name": "Firearms - other than small arms",
+            "product_tax_code": "46101500A0002"
+        },
+        {
+            "description": "A charge for an objective visual examination of a house’s systems and physical structure. The charge includes a report of the inspector's findings including pictures, analysis, and recommendations.",
+            "name": "Home Inspection Services",
+            "product_tax_code": "80131802A0001"
+        },
+        {
+            "description": "A charge for custodial services to residential structures, including the cleaning of floors, carpets, walls, windows, appliances, furniture, fixtures, exterior cleaning, etc. No Tangible Personal Property is transferred.",
+            "name": "Cleaning/Janitorial Services - Residential",
+            "product_tax_code": "76111501A0001"
+        },
+        {
+            "description": "A subscription service for membership to an online dating platform.",
+            "name": "Online Dating Services",
+            "product_tax_code": "91000000A1111"
+        },
+        {
+            "description": "A charge for the service to maintain the proper operation of home or building gutters through cleaning out debris that could otherwise affect the proper water flow through the gutter system.",
+            "name": "Gutter Cleaning Services",
+            "product_tax_code": "72152602A0001"
+        },
+        {
+            "description": "Clothing - Swim Fins",
+            "name": "Clothing - Swim Fins",
+            "product_tax_code": "4914606A0001"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods can be streamed and/or downloaded to a device with permanent access granted.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - bundle - downloaded with permanent rights and streamed - non subscription",
+            "product_tax_code": "55111516A0110"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods can be streamed and/or downloaded to a device with access that expires after a stated period of time.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - bundle - downloaded with limited rights and streamed - non subscription",
+            "product_tax_code": "55111516A0210"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods are downloaded to a device with access that expires after a stated period of time.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - downloaded - non subscription - with limited rights",
+            "product_tax_code": "55111516A0020"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods are downloaded to a device with permanent access granted.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - downloaded - non subscription - with permanent rights",
+            "product_tax_code": "55111516A0010"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods are streamed to a device with access that expires after a stated period of time.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - streamed - non subscription - with limited rights",
+            "product_tax_code": "55111516A0030"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods are streamed to a device with access that is conditioned upon continued subscription payment.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - streamed - subscription - with conditional rights",
+            "product_tax_code": "55111516A0040"
+        },
+        {
+            "description": "A series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.  These goods are streamed and/or downloaded to a device with access that is conditioned upon continued subscription payment.  These goods include motion pictures, music videos, animations, news and entertainment programs, and live events, but do not include video greeting cards or video or electronic games.",
+            "name": "Digital Audio Visual Works - bundle - downloaded and streamed - subscription - with conditional rights",
+            "product_tax_code": "55111516A0310"
+        },
+        {
+            "description": "Works that result from the fixation of a series of musical, spoken, or other sounds that are transferred electronically.  These goods are streamed to a device with access that is conditioned upon continued subscription payment.  These goods include prerecorded or live music, prerecorded or live readings of books or other written materials, prerecorded or live speeches, ringtones, or other sound recordings, but not including audio greeting cards.",
+            "name": "Digital Audio Works - streamed - subscription - with conditional rights",
+            "product_tax_code": "55111512A0040"
+        },
+        {
+            "description": "Works that result from the fixation of a series of musical, spoken, or other sounds that are transferred electronically.  These goods are streamed to a device with access that expires after a stated period of time.  These goods include prerecorded or live music, prerecorded or live readings of books or other written materials, prerecorded or live speeches, ringtones, or other sound recordings, but not including audio greeting cards.",
+            "name": "Digital Audio Works - streamed - non subscription - with limited rights",
+            "product_tax_code": "55111512A0030"
+        },
+        {
+            "description": "Works that result from the fixation of a series of musical, spoken, or other sounds that are transferred electronically.  These goods are downloaded to a device with permanent access granted.  These goods include prerecorded or live music, prerecorded or live readings of books or other written materials, prerecorded or live speeches, ringtones, or other sound recordings, but not including audio greeting cards.",
+            "name": "Digital Audio Works - downloaded - non subscription - with permanent rights",
+            "product_tax_code": "55111512A0010"
+        },
+        {
+            "description": "Works that result from the fixation of a series of musical, spoken, or other sounds that are transferred electronically.  These goods are downloaded to a device with access that expires after a stated period of time.  These goods include prerecorded or live music, prerecorded or live readings of books or other written materials, prerecorded or live speeches, ringtones, or other sound recordings, but not including audio greeting cards.",
+            "name": "Digital Audio Works - downloaded - non subscription - with limited rights",
+            "product_tax_code": "55111512A0020"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals with the entire publication or individual articles viewable (but not downloadable) on a device with access that is conditioned upon continued subscription payment.",
+            "name": "Digital Newspapers - viewable only - subscription - with conditional rights",
+            "product_tax_code": "55111507A0060"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals with the entire publication or individual articles viewable (but not downloadable) on a device with permanent access granted.  The publication is accessed without a subscription.",
+            "name": "Digital Newspapers - viewable only - non subscription - with permanent rights",
+            "product_tax_code": "55111507A0040"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals with the entire publication or individual articles viewable (but not downloadable) on a device with access that expires after a stated period of time.  The publication is accessed without a subscription.",
+            "name": "Digital Newspapers - viewable only - non subscription - with limited rights",
+            "product_tax_code": "55111507A0030"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals.  The publication is accessed via a subscription which also entitles the purchaser to physical copies of the media.",
+            "name": "Digital Newspapers - subscription tangible and digital",
+            "product_tax_code": "55111507A0070"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals with the entire publication or individual articles downloaded to a device with access that is conditioned upon continued subscription payment.",
+            "name": "Digital Newspapers - downloadable - subscription - with conditional rights",
+            "product_tax_code": "55111507A0050"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals with the entire publication or individual articles downloaded to a device with permanent access granted.  The publication is accessed without a subscription.",
+            "name": "Digital Newspapers - downloadable - non subscription - with permanent rights",
+            "product_tax_code": "55111507A0010"
+        },
+        {
+            "description": "A digital version of a traditional newspaper published at regular intervals with the entire publication or individual articles downloaded to a device with access that expires after a stated period of time.  The publication is accessed without a subscription.",
+            "name": "Digital Newspapers - downloadable - non subscription - with limited rights",
+            "product_tax_code": "55111507A0020"
+        },
+        {
+            "description": "A digital version of a traditional periodical published at regular intervals with the entire publication or individual articles viewable (but not downloadable) on a device with access that is conditioned upon continued subscription payment.",
+            "name": "Digital Magazines/Periodicals - viewable only - subscription - with conditional rights",
+            "product_tax_code": "55111506A0060"
+        },
+        {
+            "description": "A digital version of a traditional periodical published at regular intervals with the entire publication or individual articles viewable (but not downloadable) on a device with permanent access granted.  The publication is accessed without a subscription.",
+            "name": "Digital Magazines/Periodicals - viewable only - non subscription - with permanent rights",
+            "product_tax_code": "55111506A0040"
+        },
+        {
+            "description": "A digital version of a traditional periodical published at regular intervals with the entire publication or individual articles viewable (but not downloadable) on a device with access that expires after a stated period of time.  The publication is accessed without a subscription.",
+            "name": "Digital Magazines/Periodicals - viewable only - non subscription - with limited rights",
+            "product_tax_code": "55111506A0030"
+        },
+        {
+            "description": "A digital version of a traditional magazine published at regular intervals.  The publication is accessed via a subscription which also entitles the purchaser to physical copies of the media.",
+            "name": "Digital Magazines/Periodicals - subscription tangible and digital",
+            "product_tax_code": "55111506A0070"
+        },
+        {
+            "description": "A digital version of a traditional periodical published at regular intervals with the entire publication or individual articles downloaded to a device with access that is conditioned upon continued subscription payment.",
+            "name": "Digital Magazines/Periodicals - downloadable - subscription - with conditional rights",
+            "product_tax_code": "55111506A0050"
+        },
+        {
+            "description": "A digital version of a traditional periodical published at regular intervals with the entire publication or individual articles downloaded to a device with permanent access granted.  The publication is accessed without a subscription.",
+            "name": "Digital Magazines/Periodicals - downloadable - non subscription - with permanent rights",
+            "product_tax_code": "55111506A0010"
+        },
+        {
+            "description": "A digital version of a traditional periodical published at regular intervals with the entire publication or individual articles downloaded to a device with access that expires after a stated period of time.  The publication is accessed without a subscription.",
+            "name": "Digital Magazines/Periodicals - downloadable - non subscription - with limited rights",
+            "product_tax_code": "55111506A0020"
+        },
+        {
+            "description": "Works that are generally recognized in the ordinary and usual sense as books and are transferred electronically.  These goods are viewable (but not downloadable) on a device with access that is conditioned upon continued subscription payment.  These goods include novels, autobiographies, encyclopedias, dictionaries, repair manuals, phone directories, business directories, zip code directories, cookbooks, etc.",
+            "name": "Digital Books - viewable only - subscription - with conditional rights",
+            "product_tax_code": "55111505A0060"
+        },
+        {
+            "description": "Works that are generally recognized in the ordinary and usual sense as books and are transferred electronically.  These goods are downloaded to a device with access that is conditioned upon continued subscription payment.  These goods include novels, autobiographies, encyclopedias, dictionaries, repair manuals, phone directories, business directories, zip code directories, cookbooks, etc.",
+            "name": "Digital Books - downloaded - subscription - with conditional rights",
+            "product_tax_code": "55111505A0050"
+        },
+        {
+            "description": "Works that are generally recognized in the ordinary and usual sense as books and are transferred electronically.  These goods are downloaded to a device with permanent access granted.  These goods include novels, autobiographies, encyclopedias, dictionaries, repair manuals, phone directories, business directories, zip code directories, cookbooks, etc.",
+            "name": "Digital Books - downloaded - non subscription - with permanent rights",
+            "product_tax_code": "55111505A0010"
+        },
+        {
+            "description": "Works that are generally recognized in the ordinary and usual sense as books and are transferred electronically.  These goods are downloaded to a device with access that expires after a stated period of time.  These goods include novels, autobiographies, encyclopedias, dictionaries, repair manuals, phone directories, business directories, zip code directories, cookbooks, etc.",
+            "name": "Digital Books - downloaded - non subscription - with limited rights",
+            "product_tax_code": "55111505A0020"
+        },
+        {
+            "description": "The final art used for actual reproduction by photomechanical or other processes or for display purposes, but does not include website or home page design, and that is transferred electronically.  These goods are downloaded to a device with access that is conditioned upon continued subscription payment.  These goods include drawings, paintings, designs, photographs, lettering, paste-ups, mechanicals, assemblies, charts, graphs, illustrative materials, etc.",
+            "name": "Digital Finished Artwork - downloaded - subscription - with conditional rights",
+            "product_tax_code": "82141502A0050"
+        },
+        {
+            "description": "The final art used for actual reproduction by photomechanical or other processes or for display purposes, but does not include website or home page design, and that is transferred electronically.  These goods are downloaded to a device with permanent access granted.  These goods include drawings, paintings, designs, photographs, lettering, paste-ups, mechanicals, assemblies, charts, graphs, illustrative materials, etc.",
+            "name": "Digital Finished Artwork - downloaded - non subscription - with permanent rights",
+            "product_tax_code": "82141502A0010"
+        },
+        {
+            "description": "The final art used for actual reproduction by photomechanical or other processes or for display purposes, but does not include website or home page design, and that is transferred electronically.  These goods are downloaded to a device with access that expires after a stated period of time.  These goods include drawings, paintings, designs, photographs, lettering, paste-ups, mechanicals, assemblies, charts, graphs, illustrative materials, etc.",
+            "name": "Digital Finished Artwork - downloaded - non subscription - with limited rights",
+            "product_tax_code": "82141502A0020"
+        },
+        {
+            "description": "Individual digital news articles, newsletters, and other stand-alone documents.  These goods are viewable (but not downloadable) on a device with access that is conditioned upon continued subscription payment.",
+            "name": "Digital other news or documents - viewable only - subscription - with conditional rights",
+            "product_tax_code": "82111900A0002"
+        },
+        {
+            "description": "Individual digital news articles, newsletters, and other stand-alone documents.  These goods are viewable (but not downloadable) on a device with permanent access granted.",
+            "name": "Digital other news or documents - viewable only - non subscription - with permanent rights",
+            "product_tax_code": "82111900A0005"
+        },
+        {
+            "description": "Individual digital news articles, newsletters, and other stand-alone documents.  These goods are viewable (but not downloadable) on a device with access that expires after a stated period of time.",
+            "name": "Digital other news or documents - viewable only - non subscription - with limited rights",
+            "product_tax_code": "82111900A0006"
+        },
+        {
+            "description": "Individual digital news articles, newsletters, and other stand-alone documents.  These goods are downloaded to a device with access that is conditioned upon continued subscription payment.",
+            "name": "Digital other news or documents - downloadable - subscription - with conditional rights",
+            "product_tax_code": "82111900A0001"
+        },
+        {
+            "description": "Individual digital news articles, newsletters, and other stand-alone documents.  These goods are downloaded to a device with permanent access granted.  These publications are accessed without a subscription.",
+            "name": "Digital other news or documents - downloadable - non subscription - with permanent rights",
+            "product_tax_code": "82111900A0003"
+        },
+        {
+            "description": "Individual digital news articles, newsletters, and other stand-alone documents.  These goods are downloaded to a device with access that expires after a stated period of time.",
+            "name": "Digital other news or documents - downloadable - non subscription - with limited rights",
+            "product_tax_code": "82111900A0004"
+        },
+        {
+            "description": "Works that are required as part of a formal academic education program and are transferred electronically.  These goods are downloaded to a device with permanent access granted.",
+            "name": "Digital School Textbooks - downloaded - non subscription - with permanent rights",
+            "product_tax_code": "55111513A0010"
+        },
+        {
+            "description": "Works that are required as part of a formal academic education program and are transferred electronically.  These goods are downloaded to a device with access that expires after a stated period of time.",
+            "name": "Digital School Textbooks - downloaded - non subscription - with limited rights",
+            "product_tax_code": "55111513A0020"
+        },
+        {
+            "description": "An electronic greeting \"card\" typically sent via email that contains only static images or text, rather than an audio visual or audio only experience.",
+            "name": "Digital Greeting Cards - Static text and/or images only",
+            "product_tax_code": "14111605A0003"
+        },
+        {
+            "description": "An electronic greeting \"card\" typically sent via email that contains a series of related images which, when shown in succession, impart an impression of motion, together with accompanying sounds, if any.",
+            "name": "Digital Greeting Cards - Audio Visual",
+            "product_tax_code": "14111605A0001"
+        },
+        {
+            "description": "An electronic greeting \"card\" typically sent via email that contains an audio only message.",
+            "name": "Digital Greeting Cards - Audio Only",
+            "product_tax_code": "14111605A0002"
+        },
+        {
+            "description": "Digital images that are downloaded to a device with permanent access granted.",
+            "name": "Digital Photographs/Images - downloaded - non subscription - with permanent rights for permanent use",
+            "product_tax_code": "60121011A0001"
+        },
+        {
+            "description": "Video or electronic games in the common sense are transferred electronically.  These goods are streamed to a device with access that is conditioned upon continued subscription payment.",
+            "name": "Video Games - streamed - subscription - with conditional rights",
+            "product_tax_code": "60141104A0040"
+        },
+        {
+            "description": "Video or electronic games in the common sense are transferred electronically.  These goods are streamed to a device with access that expires after a stated period of time.",
+            "name": "Video Games - streamed - non subscription - with limited rights",
+            "product_tax_code": "60141104A0030"
+        },
+        {
+            "description": "Video or electronic games in the common sense are transferred electronically.  These goods are downloaded to a device with access that is conditioned upon continued subscription payment.",
+            "name": "Video Games - downloaded - subscription - with conditional rights",
+            "product_tax_code": "60141104A0050"
+        },
+        {
+            "description": "Video or electronic games in the common sense are transferred electronically.  These goods are downloaded to a device with permanent access granted.",
+            "name": "Video Games - downloaded - non subscription - with permanent rights",
+            "product_tax_code": "60141104A0010"
+        },
+        {
+            "description": "Video or electronic games in the common sense are transferred electronically.  These goods are downloaded to a device with access that expires after a stated period of time.",
+            "name": "Video Games - downloaded - non subscription - with limited rights",
+            "product_tax_code": "60141104A0020"
+        },
+        {
+            "description": "The conceptualize, design, program or maintain a website. The code is unique to a particular client's website.",
+            "name": "Web Site Design",
+            "product_tax_code": "81112103A0000"
+        },
+        {
+            "description": "The process of renting or buying space to house a website on the World Wide Web. Website content such as HTML, CSS, and images has to be housed on a server to be viewable online.",
+            "name": "Web Hosting Services",
+            "product_tax_code": "81112105A0000"
+        },
+        {
+            "description": "A charge separately stated from the sale of the product itself that entitles the purchaser to future repair and labor services to return the defective item of tangible personal property to its original state.  The warranty contract is optional to the purchaser.  Motor vehicle warranties are excluded.",
+            "name": "Warranty - Optional",
+            "product_tax_code": "81111818A0000"
+        },
+        {
+            "description": "A charge separately stated from the sale of the product itself that entitles the purchaser to future repair and labor services to return the defective item of tangible personal property to its original state.  The warranty contract is mandatory and is required to be purchased on conjunction with the purchased tangible personal property.  Motor vehicle warranties are excluded.",
+            "name": "Warranty - Mandatory",
+            "product_tax_code": "81111818A0001"
+        },
+        {
+            "description": "Personal or small group teaching, designed to help people who need extra help with their studies",
+            "name": "Tutoring",
+            "product_tax_code": "86132001A0000"
+        },
+        {
+            "description": "Self Study web based training, not instructor led.  This does not include downloads of video replays.",
+            "name": "Training Services - Self Study Web Based",
+            "product_tax_code": "86132000A0002"
+        },
+        {
+            "description": "Live training web based.  This does not include video replays of the instruction or course.",
+            "name": "Training Services - Live Virtual",
+            "product_tax_code": "86132201A0000"
+        },
+        {
+            "description": "Charges for installing, configuring, debugging, modifying, testing, or troubleshooting computer  hardware,  networks,  programs  or  software.  Labor only charge.",
+            "name": "Technical Support Services",
+            "product_tax_code": "81111811A0001"
+        },
+        {
+            "description": "A charge to preserve an animal's body via mounting or stuffing, for the purpose of display or study.  The customer provide the animal.  This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Taxidermy Services",
+            "product_tax_code": "82151508A0000"
+        },
+        {
+            "description": "A charge to have files or documents shredded either onsite or offsite.",
+            "name": "Shredding Service",
+            "product_tax_code": "44101603A9007"
+        },
+        {
+            "description": "A charge to repair or restore footwear was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.  Note: This product tax code will partially apply tax in CA, MI, IL.",
+            "name": "Shoe Repair",
+            "product_tax_code": "53111600A9007"
+        },
+        {
+            "description": "A charge for the printing, imprinting, lithographing, mimeographing, photocopying, and similar reproductions of various articles including mailers, catalogs, letterhead, envelopes, business cards, presentation folders, forms, signage, etc.  The end result is the transfer of tangible personal property to the customer.",
+            "name": "Printing",
+            "product_tax_code": "82121500A0000"
+        },
+        {
+            "description": "Service processing payroll checks and tracking payroll data; including printing employees’ payroll checks, pay statements, management reports, tracking payroll taxes, preparing tax returns and producing W-2’s for distribution.",
+            "name": "Payroll Services",
+            "product_tax_code": "87210202A0000"
+        },
+        {
+            "description": "A charge to repair or restore to operating condition a motor vehicle that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Motor Vehicle Repair",
+            "product_tax_code": "25100000A9007"
+        },
+        {
+            "description": "A charge to make customer provided meat suitable for human consumption, typically referred to a butcher or slaughter services.",
+            "name": "Meat Processing",
+            "product_tax_code": "42447000A0000"
+        },
+        {
+            "description": "A charge to repair or restore to operating condition a machine that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Machine Repair",
+            "product_tax_code": "23019007A0000"
+        },
+        {
+            "description": "A charge to provide laundry services to linens and the like. This charge is not for clothing items. The business customer is the owner of the items being cleaned.",
+            "name": "Linen Services - Laundry only - items other than clothing",
+            "product_tax_code": "91111502A1601"
+        },
+        {
+            "description": "A charge to provide laundry services to clothing. The business customer is the owner of the items being cleaned.",
+            "name": "Linen Services - Laundry only",
+            "product_tax_code": "91111502A1600"
+        },
+        {
+            "description": "A charge to repair or restore jewelry that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Jewelry Repair",
+            "product_tax_code": "54119007A0000"
+        },
+        {
+            "description": "A charge for the wrapping of articles in a box or bag with paper and other decorative additions.  The wrapping not linked the purchased of the article(s) and is performed by a party other vendor of the article(s).",
+            "name": "Gift Wrapping - separate from purchase of article",
+            "product_tax_code": "14111601A9007"
+        },
+        {
+            "description": "A charge for the wrapping of articles in a box or bag with paper and other decorative additions. The charge is separately stated from the article.",
+            "name": "Gift Wrapping - in conjunction with purchase of article",
+            "product_tax_code": "14111601A0001"
+        },
+        {
+            "description": "A charge to perform an alteration on a item of clothing by a service provider other than vendor of the article.  The alteration is not linked to the clothing purchase.  Alterations could include hemming of a dress, shortening of pants, adjusting the waistline of a garment, etc.",
+            "name": "Garment Alterations- separate from purchase of garment",
+            "product_tax_code": "81149000A0000"
+        },
+        {
+            "description": "A charge to perform an alteration on a item of clothing by the vendor of the article.  The alteration is separately stated from the clothing, but contracted for at the time of the clothing purchase.  Alterations could include hemming of a dress, shortening of pants, adjusting the waistline of a garment, etc.",
+            "name": "Garment Alterations- in conjunction with purchase of garment",
+            "product_tax_code": "81149000A0001"
+        },
+        {
+            "description": "A separately stated labor charge to cover a piece of furniture previously owned by the customer with new fabric coverings. Any materials transferred as part of the service are separately stated.",
+            "name": "Furniture Reupholstering",
+            "product_tax_code": "72153614A0000"
+        },
+        {
+            "description": "A charge to create a finished good from materials supplied by the customer.  This is a labor only charge to transform a customer's existing property.",
+            "name": "Fabrication",
+            "product_tax_code": "23839000A0000"
+        },
+        {
+            "description": "E-file services for tax returns",
+            "name": "Electronic Filing Service",
+            "product_tax_code": "72910000A0000"
+        },
+        {
+            "description": "Private schools, not college or university",
+            "name": "Educational Services",
+            "product_tax_code": "86132209A0000"
+        },
+        {
+            "description": "A charge to a non-commercial customer for the cleaning or renovating items other than clothing by immersion and agitation, spraying, vaporization, or immersion only, in a volatile, commercially moisture-free solvent or by the use of a volatile or inflammable product. This does not include the use of a self-service coin (or credit card) operated cleaning machine.",
+            "name": "Dry Cleaning - items other than clothing",
+            "product_tax_code": "91111503A1601"
+        },
+        {
+            "description": "A charge to repair or restore to operating condition computer hardware that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Computer Repair",
+            "product_tax_code": "81112300A0000"
+        },
+        {
+            "description": "A charge to clean, wash or wax a motor vehicle, other than a self-service coin (or credit card) operated washing station.  This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Car Washing",
+            "product_tax_code": "81119200A0000"
+        },
+        {
+            "description": "A charge to assemble goods for a purchaser who will later sell the assembled goods to end consumers.",
+            "name": "Assembly - prior to final purchase of article",
+            "product_tax_code": "93121706A0001"
+        },
+        {
+            "description": "A charge separately stated from the sale of the product itself to bring the article to its finished state and in the condition specified by the buyer.",
+            "name": "Assembly - in conjunction with final purchase of article",
+            "product_tax_code": "93121706A0000"
+        },
+        {
+            "description": "A charge to repair or restore to operating condition an appliance (dishwasher, washing machine, refrigerator, etc.) that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Appliance Repair",
+            "product_tax_code": "52143609A0000"
+        },
+        {
+            "description": "A charge to repair or restore to operating condition an aircraft that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential. Commercial aircraft is excluded.",
+            "name": "Aircraft Repair",
+            "product_tax_code": "78181800A0000"
+        },
+        {
+            "description": "A charge for the printing, imprinting, or lithographing on any article supplied by the customer.  The customer owns the article throughout the process.  This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Printing - customer supplied articles",
+            "product_tax_code": "19009"
+        },
+        {
+            "description": "A charge to a non-commercial customer for the cleaning or renovating clothing by immersion and agitation, spraying, vaporization, or immersion only, in a volatile, commercially moisture-free solvent or by the use of a volatile or inflammable product. This does not include the use of a self-service coin (or credit card) operated cleaning machine.",
+            "name": "Dry Cleaning Services",
+            "product_tax_code": "19006"
+        },
+        {
+            "description": "A charge to repair or restore tangible personal property that was broken, worn, damaged, defective, or malfunctioning. This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Repair Services",
+            "product_tax_code": "19007"
+        },
+        {
+            "description": "Food for household pets that is consumed for nutritional value. This code is not intended for food related to working farm animals or animals raised for meat or milk production. This code is intended for retail sales made directly to end consumers.",
+            "name": "OTC Pet Food",
+            "product_tax_code": "10122100A0000"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with candy, with the candy comprising between 25% and 49% of the overall value of the bundle (food comprises 51 to 75%).  Note that any candy containing flour should be considered as food (and not candy) when determining bundle percentages.",
+            "name": "Food/Candy Bundle - with Candy 25% to 49%",
+            "product_tax_code": "50193400A0008"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with candy, with the candy comprising between 11% and 24% of the overall value of the bundle (food comprises 76% to 89%).  Note that any candy containing flour should be considered as food (and not candy) when determining bundle percentages.",
+            "name": "Food/Candy Bundle - with Candy 11% to 24%",
+            "product_tax_code": "50193400A0009"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with candy, with the candy comprising 10% or less of the overall value of the bundle (food comprises 90% or more).  Note that any candy containing flour should be considered as food (and not candy) when determining bundle percentages.",
+            "name": "Food/Candy Bundle - with Candy 10% or less",
+            "product_tax_code": "50193400A0010"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with candy, with the candy comprising 50% or more of the overall value of the bundle (food comprises 50% or less).  Note that any candy containing flour should be considered as food (and not candy) when determining bundle percentages.",
+            "name": "Food/Candy Bundle - with Candy 50% or more",
+            "product_tax_code": "50193400A0007"
+        },
+        {
+            "description": "Male or female condoms and vaginal sponges used to prevent pregnancy and/or exposure to STDs, containing a spermicidal lubricant as indicated by a \"drug facts\" panel or a statement of active ingredients, sold under prescription order of a licensed professional.",
+            "name": "Condoms with Spermicide with Prescription",
+            "product_tax_code": "53131622A0004"
+        },
+        {
+            "description": "Over-the-Counter emergency contraceptive pills act to prevent pregnancy after intercourse. The contraceptive contains a hormone that prevents ovulation, fertilization, or implantation of an embryo.",
+            "name": "Birth Control - Over-the-Counter Oral Contraceptives",
+            "product_tax_code": "51350000A0001"
+        },
+        {
+            "description": "An oral medication containing hormones effective in altering the menstrual cycle to eliminate ovulation and prevent pregnancy, available only under prescription order of a licensed professional.  Other than preventing pregnancy, hormonal birth control can also be used to treat various conditions, such as Polycystic Ovary Syndrome, Endometriosis, Primary Ovarian Insufficiency, etc.",
+            "name": "Birth Control - Prescription Oral Contraceptives",
+            "product_tax_code": "51350000A0000"
+        },
+        {
+            "description": "Over-the-Counter emergency contraceptive pills act to prevent pregnancy after intercourse, sold under prescription order of a licensed professional.  The contraceptive contains a hormone that prevents ovulation, fertilization, or implantation of an embryo.",
+            "name": "Birth Control - Over-the-Counter Oral Contraceptives with Prescription",
+            "product_tax_code": "51350000A0002"
+        },
+        {
+            "description": "Barrier based prescription only birth control methods, including the diaphragm and cervical cap that prevent the joining of the sperm and egg, available only under prescription order of a licensed professional.",
+            "name": "Birth Control - Prescription non-Oral Contraceptives - Barriers",
+            "product_tax_code": "42143103A0000"
+        },
+        {
+            "description": "Hormonal based birth control methods other than the oral pill, including intrauterine devices, injections, skin implants, transdermal patches, and vaginal rings that release a continuous dose of hormones to eliminate ovulation and prevent pregnancy, available only under prescription order of a licensed professional.",
+            "name": "Birth Control - Prescription non-Oral Contraceptives - Hormonal",
+            "product_tax_code": "51350000A0003"
+        },
+        {
+            "description": "Male or female condoms and vaginal sponges used to prevent pregnancy and/or exposure to STDs, sold under prescription order of a licensed professional.",
+            "name": "Condoms with Prescription",
+            "product_tax_code": "53131622A0003"
+        },
+        {
+            "description": "Feminine hygiene product designed to absorb the menstrual flow. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Tampons, menstrual cups, pads, liners",
+            "product_tax_code": "53131615A0000"
+        },
+        {
+            "description": "Infant washable/reusable cloth diapers.",
+            "name": "Clothing - Cloth Diapers",
+            "product_tax_code": "53102305A0001"
+        },
+        {
+            "description": "Clothing - Diaper liners",
+            "name": "Clothing - Diaper liners",
+            "product_tax_code": "53102308A0000"
+        },
+        {
+            "description": "Clothing - Adult diapers",
+            "name": "Clothing - Adult diapers",
+            "product_tax_code": "53102306A0000"
+        },
+        {
+            "description": "Clothing - Infant diapers",
+            "name": "Clothing - Infant diapers",
+            "product_tax_code": "53102305A0000"
+        },
+        {
+            "description": "Food and Beverage - Candy containing flour as an ingredient",
+            "name": "Food and Beverage - Candy containing flour as an ingredient",
+            "product_tax_code": "50161800A0001"
+        },
+        {
+            "description": "Food and Beverage - Food and Food Ingredients for Home Consumption",
+            "name": "Food and Beverage - Food and Food Ingredients for Home Consumption",
+            "product_tax_code": "50000000A0000"
+        },
+        {
+            "description": "Clothing - Zippers",
+            "name": "Clothing - Zippers",
+            "product_tax_code": "53141503A0000"
+        },
+        {
+            "description": "Clothing - Gorgets",
+            "name": "Clothing - Gorgets",
+            "product_tax_code": "53102519A0000"
+        },
+        {
+            "description": "Clothing - Shoulder boards or epaulettes",
+            "name": "Clothing - Shoulder boards or epaulettes",
+            "product_tax_code": "53102520A0000"
+        },
+        {
+            "description": "Yarn - For use other than fabricating/repairing clothing",
+            "name": "Yarn - For use other than fabricating/repairing clothing",
+            "product_tax_code": "11151700A0001"
+        },
+        {
+            "description": "Clothing - Snaps",
+            "name": "Clothing - Snaps",
+            "product_tax_code": "53141506A0000"
+        },
+        {
+            "description": "Clothing - Clasps",
+            "name": "Clothing - Clasps",
+            "product_tax_code": "53141507A0000"
+        },
+        {
+            "description": "Clothing - Buttons",
+            "name": "Clothing - Buttons",
+            "product_tax_code": "53141505A0000"
+        },
+        {
+            "description": "Clothing - Clothing - Fabric dye",
+            "name": "Clothing - Fabric dye",
+            "product_tax_code": "60105810A0000"
+        },
+        {
+            "description": "Clothing - Fabric for use in clothing",
+            "name": "Clothing - Fabric for use in clothing",
+            "product_tax_code": "11160000A0000"
+        },
+        {
+            "description": "Clothing - Clothing - Yarn",
+            "name": "Clothing - Yarn",
+            "product_tax_code": "11151700A0000"
+        },
+        {
+            "description": "A lump sum charge where both the downloaded digital products and the service components each are greater than 10% of the bundle.",
+            "name": "Digital Products (> 10%) / General Services (> 10%) Bundle",
+            "product_tax_code": "55111500A5000"
+        },
+        {
+            "description": "Services provided by a licensed or registered professional in the medical field.  Examples: Doctor, dentist, nurse, optometrist, etc.",
+            "name": "Medical Professional Services - Physician, Dentist, and similar",
+            "product_tax_code": "62139900A0000"
+        },
+        {
+            "description": "The puncturing or penetration of the skin of a person and the insertion of jewelry or other adornment into the opening.",
+            "name": "Body Piercing",
+            "product_tax_code": "72990190A0000"
+        },
+        {
+            "description": "Cosmetic beauty treatment for the fingernails and toenails. Consists of filing, cutting and shaping and the application of polish.",
+            "name": "Manicure Services",
+            "product_tax_code": "72310104A0000"
+        },
+        {
+            "description": "Performing tests and research for a particular client in connection with the development of particular products, property, goods or services that the client sells to consumers in the regular course of business.",
+            "name": "Marketing Services",
+            "product_tax_code": "87420300A0000"
+        },
+        {
+            "description": "Performing surveying and mapping services of the surface of the earth, including the sea floor. These services may include surveying and mapping of areas above or below the surface of the earth, such as the creation of view easements or segregating rights in parcels of land by creating underground utility easements.",
+            "name": "Property Surveying Services",
+            "product_tax_code": "87130000A0000"
+        },
+        {
+            "description": "Providing a systematic inquiry, examination, or analysis of people, events or documents, to determine the facts of a given situation. The evaluation is submitted in the form of a report or provided as a testimony in legal proceedings. Techniques such as surveillance, background checks, computer searches, fingerprinting, lie detector services, and interviews may be used to gather the information.",
+            "name": "Private Investigator Services",
+            "product_tax_code": "73810204A0000"
+        },
+        {
+            "description": "The provision of expertise or strategic advice that is presented for consideration and decision-making.",
+            "name": "Consulting Services",
+            "product_tax_code": "87480000A0000"
+        },
+        {
+            "description": "Services relating to advocating for the passage or defeat of legislation to members or staff of the government.",
+            "name": "Lobbying Services",
+            "product_tax_code": "87439901A0000"
+        },
+        {
+            "description": "Preparation of materials, written or otherwise, that are designed to influence the general public or other groups by promoting the interests of a service recipient.",
+            "name": "Public Relations",
+            "product_tax_code": "87430000A0000"
+        },
+        {
+            "description": "Services related to the art and science of designing and building structures for human habitation or use and includes planning, providing preliminary studies, designs, specifications, working drawings and providing for general administration of construction contracts.",
+            "name": "Architectural Services",
+            "product_tax_code": "87120000A0000"
+        },
+        {
+            "description": "Services provided by a profession trained to apply physical laws and principles of engineering in the design, development, and utilization of machines, materials, instruments, structures, processes, and systems. The services involve any of the following activities: provision of advice, preparation of feasibility studies, preparation of preliminary and final plans and designs, provision of technical services during the construction or installation phase, inspection and evaluation of engineering projects, and related services.",
+            "name": "Engineering Services",
+            "product_tax_code": "87110000A0000"
+        },
+        {
+            "description": "Services that provide non-medical care and supervision for infant to school-age children or senior citizens.",
+            "name": "Childcare Services / Adultcare",
+            "product_tax_code": "83510000A0000"
+        },
+        {
+            "description": "Services provided by a facility for overnight care of an animal not related to veterinary care.",
+            "name": "Pet Services - Boarding",
+            "product_tax_code": "81291000A0001"
+        },
+        {
+            "description": "Services relating to or concerned with the law. Such services include, but are not limited to, representation by an attorney (or other person, when permitted) in an administrative or legal proceeding, legal drafting, paralegal services, legal research services, arbitration, mediation, and court reporting services.",
+            "name": "Legal Services",
+            "product_tax_code": "81110000A0000"
+        },
+        {
+            "description": "Medical procedure performed on an individual that is directed at improving the individual's appearance and that does not meaningfully promote the proper function of the body or prevent or treat illness or disease.",
+            "name": "Cosmetic Medical Procedure",
+            "product_tax_code": "80110517A0000"
+        },
+        {
+            "description": "Alarm monitoring involves people using computers, software, and telecommunications to monitor homes and businesses for break-ins, fires, and other unexpected events.",
+            "name": "Security - Alarm Services",
+            "product_tax_code": "73829901A0000"
+        },
+        {
+            "description": "Services related to protecting persons or their property, preventing the theft of goods, merchandise, or money.  Responding to alarm signal device, burglar alarm, television camera, still camera, or a mechanical or electronic device installed or used to prevent or detect burglary, theft, shoplifting, pilferage, losses, or other security measures.  Providing management and control of crowds for safety and protection.",
+            "name": "Security - Guard Services",
+            "product_tax_code": "73810105A0000"
+        },
+        {
+            "description": "Transporting under armed private security guard from one place to another any currency, jewels, stocks, bonds, paintings, or other valuables of any kind in a specially equipped motor vehicle that offers a high degree of security.",
+            "name": "Armored Car Services",
+            "product_tax_code": "73810101A0000"
+        },
+        {
+            "description": "Services related to providing personnel, on a temporary basis, to perform work or labor under the supervision or control of another.",
+            "name": "Temporary Help Services",
+            "product_tax_code": "73630103A0000"
+        },
+        {
+            "description": "Services employment agencies provide are finding a job for a job-seeker and finding an employee for an employer.",
+            "name": "Employment Services",
+            "product_tax_code": "73610000A0000"
+        },
+        {
+            "description": "Services to industrial, commercial or income-producing real property, such as as management, electrical, plumbing, painting and carpentry, provided to income-producing property.",
+            "name": "Building Management Services",
+            "product_tax_code": "73490000A0000"
+        },
+        {
+            "description": "Services which include, but are not limited to, editing, letter writing, proofreading, resume writing, typing or word processing.  Not including court reporting and stenographic services.",
+            "name": "Secretarial Services",
+            "product_tax_code": "73389903A0000"
+        },
+        {
+            "description": "Services that include typing, taking shorthand, and taking and transcribing dictation for others for a consideration.",
+            "name": "Stenographic Services",
+            "product_tax_code": "73380200A0000"
+        },
+        {
+            "description": "A process that uses needles and colored ink to permanently put a mark or design on a person’s skin. Also applying permanent make-up, such as eyelining and other permanent colors to enhance the skin of the face, lips, eyelids, and eyebrows.",
+            "name": "Tattooing Services",
+            "product_tax_code": "72990106A0000"
+        },
+        {
+            "description": "A variety of personal services typically with the purpose of improving health, beauty and relaxation through treatments such as hair, massages and facials.",
+            "name": "Spa Services",
+            "product_tax_code": "72990201A0000"
+        },
+        {
+            "description": "Services where the use of structured touch, include holding, applying pressure, positioning, and mobilizing soft tissue of the body by manual technique.  Note: This does not include medical massage prescribed by a physician.",
+            "name": "Massage Services",
+            "product_tax_code": "72990200A0000"
+        },
+        {
+            "description": "The measurement, processing and communication of financial information about economic entities including, but is not limited to, financial accounting, management accounting, auditing, cost containment and auditing services, taxation and accounting information systems; excluding general bookkeeping service.",
+            "name": "Accounting Services",
+            "product_tax_code": "87210200A0000"
+        },
+        {
+            "description": "The training of an animal to obey certain commands.",
+            "name": "Pet Services - Obedience Training",
+            "product_tax_code": "81291000A0002"
+        },
+        {
+            "description": "Credit monitoring services are companies consumers pay to keep an eye on your credit files. The services notifies one when they see activity in credit files, so one can determine if that activity is a result of action one took or possibly fraudulent",
+            "name": "Credit Monitoring Services",
+            "product_tax_code": "56145000A0000"
+        },
+        {
+            "description": "Grooming services for an animal such as haircuts, bathing, nail trimming, and flea dips.",
+            "name": "Pet Services - Grooming",
+            "product_tax_code": "81291000A0000"
+        },
+        {
+            "description": "Debt collection is when a collection agency or company tries to collect past-due debts from borrowers.",
+            "name": "Debt Collection Services",
+            "product_tax_code": "73229902A0000"
+        },
+        {
+            "description": "A service that arranges introductions, for a fee, for strangers seeking romantic partners or friends.  This excludes online dating services.",
+            "name": "Dating Services",
+            "product_tax_code": "72990301A0000"
+        },
+        {
+            "description": "A service that allows merchants to accept credit cards as well as send credit card payment details to the credit card network. It then forwards the payment authorization back to the acquiring bank.",
+            "name": "Credit Card Processing Services",
+            "product_tax_code": "52232000A0000"
+        },
+        {
+            "description": "Services for artificial tanning and skin beautification.",
+            "name": "Tanning Services",
+            "product_tax_code": "72990105A0000"
+        },
+        {
+            "description": "Credit reporting agencies receive various types of information which can be included in their offerings for customers.",
+            "name": "Credit Reporting Services",
+            "product_tax_code": "73230000A0000"
+        },
+        {
+            "description": "Miscellaneous personal services are generally services that affect the appearance or comfort of people.  This does not include haircutting/styling services",
+            "name": "Personal Services",
+            "product_tax_code": "72990000A0000"
+        },
+        {
+            "description": "The removal of hair from the face or body using chemicals or heat energy.",
+            "name": "Electrolysis",
+            "product_tax_code": "72310102A0000"
+        },
+        {
+            "description": "Services provided to insurance companies providing insurance to consumers.  Examples are loss or damage appraisals, inspections, actuarial services, claims adjustment or processing.  Investigations as excluded from this definition.",
+            "name": "Insurance Services",
+            "product_tax_code": "64110000A0000"
+        },
+        {
+            "description": "An at-home infectious disease test kit that can be sold without a prescription.",
+            "name": "Infectious Disease Test - Over-the-Counter",
+            "product_tax_code": "41116205A0005"
+        },
+        {
+            "description": "An at-home infectious disease test kit that can only be sold with a prescription.",
+            "name": "Infectious Disease Test - Prescription only",
+            "product_tax_code": "41116205A0004"
+        },
+        {
+            "description": "Online database information retrieval service (personal or individual)",
+            "name": "Online database information retrieval service (personal or individual)",
+            "product_tax_code": "81111902A0001"
+        },
+        {
+            "description": "Database information retrieval",
+            "name": "Database information retrieval (personal or individual)",
+            "product_tax_code": "81111901A0001"
+        },
+        {
+            "description": "Services provided by beauty shops and barber shops, including but not limited to haircutting, hair coloring, shampooing, blow drying, permanents, hair extensions, hair straightening, and hair restorations.",
+            "name": "Hairdressing Services",
+            "product_tax_code": "19008"
+        },
+        {
+            "description": "Professional services which are not subject to a service-specific tax levy.",
+            "name": "Professional Services",
+            "product_tax_code": "19005"
+        },
+        {
+            "description": "Online database information retrieval service",
+            "name": "Online database information retrieval service",
+            "product_tax_code": "81111902A0000"
+        },
+        {
+            "description": "Database information retrieval",
+            "name": "Database information retrieval",
+            "product_tax_code": "81111901A0000"
+        },
+        {
+            "description": "Cash donation",
+            "name": "Cash donation",
+            "product_tax_code": "14111803A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicaid, medical grade oyxgen.",
+            "name": "Medical Oxygen with Prescription billed to Medicaid",
+            "product_tax_code": "42271700A0008"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, a machine used that filters a patient's blood to remove excess water and waste products when the kidneys are damaged,",
+            "name": "Kidney Dialysis Equipment for home use without Prescription",
+            "product_tax_code": "42161800A0006"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicaid, equipment that: can withstand repeated use; is primarily and customarily used to serve a medical purpose; generally is not useful to a person in the absence of illness or injury; and is not worn in or on the body. Home use means the equipment is sold to an individual for use at home, regardless of where the individual resides. Examples include hospital beds, commode chairs, bed pans, shower and bath aids, IV poles, etc.",
+            "name": "Durable Medical Equipment for home use with Prescription reimbursed by Medicaid",
+            "product_tax_code": "42140000A0005"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicare, a machine used that filters a patient's blood to remove excess water and waste products when the kidneys are damaged,  dysfunctional, or missing. The kidney dialysis machine is an artificial part which augments the natural functioning of the kidneys.",
+            "name": "Kidney Dialysis Equipment for home use with Prescription billed to Medicare",
+            "product_tax_code": "42161800A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicare, equipment that: can withstand repeated use; is primarily and customarily used to serve a medical purpose; generally is not useful to a person in the absence of illness or injury; and is not worn in or on the body. Home use means the equipment is sold to an individual for use at home, regardless of where the individual resides. Examples include hospital beds, commode chairs, bed pans, shower and bath aids, IV poles, etc.",
+            "name": "Durable Medical Equipment for home use with Prescription reimbursed by Medicare",
+            "product_tax_code": "42140000A0004"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicare, equipment used to administer oxygen directly into the lungs of the patient for the relief of conditions in which the human body experiences an abnormal deficiency or inadequate supply of oxygen. Oxygen equipment means oxygen cylinders, cylinder transport devices, including sheaths and carts, cylinder studs and support devices, regulators, flowmeters, tank wrenches, oxygen concentrators, liquid oxygen base dispensers, liquid oxygen portable dispensers, oxygen tubing, nasal cannulas, face masks, oxygen humidifiers, and oxygen fittings and accessories.",
+            "name": "Oxygen Delivery Equipment for home use with Prescription reimbursed by Medicare",
+            "product_tax_code": "42271700A0004"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, equipment that: can withstand repeated use; is primarily and customarily used to serve a medical purpose; generally is not useful to a person in the absence of illness or injury; and is not worn in or on the body.  Home use means the equipment is sold to an individual for use at home, regardless of where the individual resides. Examples include hospital beds, commode chairs, bed pans, shower and bath aids, IV poles, etc.",
+            "name": "Durable Medical Equipment for home use with Prescription",
+            "product_tax_code": "42140000A0001"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicare, equipment used to administer oxygen directly into the lungs of the patient for the relief of conditions in which the human body experiences an abnormal deficiency or inadequate supply of oxygen. Oxygen equipment means oxygen cylinders, cylinder transport devices, including sheaths and carts, cylinder studs and support devices, regulators, flowmeters, tank wrenches, oxygen concentrators, liquid oxygen base dispensers, liquid oxygen portable dispensers, oxygen tubing, nasal cannulas, face masks, oxygen humidifiers, and oxygen fittings and accessories.",
+            "name": "Oxygen Delivery Equipment for home use with Prescription billed to Medicare",
+            "product_tax_code": "42271700A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicaid, medical grade oyxgen.",
+            "name": "Medical Oxygen with Prescription reimbursed by Medicaid",
+            "product_tax_code": "42271700A0010"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, medical grade oyxgen.",
+            "name": "Medical Oxygen with Prescription",
+            "product_tax_code": "42271700A0012"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, medical grade oyxgen.",
+            "name": "Medical Oxygen without Prescription",
+            "product_tax_code": "42271700A0011"
+        },
+        {
+            "description": "Equipment which is primarily and customarily used to provide or increase the ability to move from one place to another, sold without a prescription, and which is appropriate for use either in a home or a motor vehicle; Is not generally used by persons with normal mobility; and does not include any motor vehicle or equipment on a motor vehicle normally provided by a motor vehicle manufacturer.  Examples include wheelchairs, crutches, canes, walkers, chair lifts, etc.",
+            "name": "Mobility Enhancing Equipment without Prescription",
+            "product_tax_code": "42211500A0006"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicaid, equipment used to administer oxygen directly into the lungs of the patient for the relief of conditions in which the human body experiences an abnormal deficiency or inadequate supply of oxygen. Oxygen equipment means oxygen cylinders, cylinder transport devices, including sheaths and carts, cylinder studs and support devices, regulators, flowmeters, tank wrenches, oxygen concentrators, liquid oxygen base dispensers, liquid oxygen portable dispensers, oxygen tubing, nasal cannulas, face masks, oxygen humidifiers, and oxygen fittings and accessories.",
+            "name": "Oxygen Delivery Equipment for home use with Prescription billed to Medicaid",
+            "product_tax_code": "42271700A0003"
+        },
+        {
+            "description": "Synthetic or animal-based insulin used as an injectible drug for diabetes patients, sold under prescription order of a licensed professional.",
+            "name": "Insulin with Prescription",
+            "product_tax_code": "51183603A0000"
+        },
+        {
+            "description": "Devices used by diabetic individuals to monitor sugar levels in the blood, sold under prescription order of a licensed professional.",
+            "name": "Blood Glucose Monitoring Devices with Prescription",
+            "product_tax_code": "41116201A0000"
+        },
+        {
+            "description": "Devices used by diabetic individuals to monitor sugar levels in the blood, sold without prescription order of a licensed professional.",
+            "name": "Blood Glucose Monitoring Devices without Prescription",
+            "product_tax_code": "41116201A0001"
+        },
+        {
+            "description": "At home urine-based tests used to detect the presense of various drug substances in an individual.",
+            "name": "Drug Testing Kits",
+            "product_tax_code": "41116136A0001"
+        },
+        {
+            "description": "Single use hollow needle commonly used with a syringe to inject insulin into the body by diabetic individuals, sold under prescription order of a licensed professional.",
+            "name": "Hypodermic Needles/Syringes with prescription - Insulin",
+            "product_tax_code": "42142523A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicare, medical grade oyxgen.",
+            "name": "Medical Oxygen with Prescription reimbursed by Medicare",
+            "product_tax_code": "42271700A0009"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicare, medical grade oyxgen.",
+            "name": "Medical Oxygen with Prescription billed to Medicare",
+            "product_tax_code": "42271700A0007"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, a replacement, corrective, or supportive device, worn on or in the body to: Artificially replace a missing portion of the body; Prevent or correct physical deformity or malfunction; or Support a weak or deformed portion of the body. Worn in or on the body means that the item is implanted or attached so that it becomes part of the body, or is carried by the body and does not hinder the mobility of the individual. Examples include artificial limbs, pacemakers, orthopedics, ostomy/colostomy devices, etc.",
+            "name": "Prosthetic Devices without Prescription",
+            "product_tax_code": "42242000A0006"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicaid, a replacement, corrective, or supportive device, worn on or in the body to: Artificially replace a missing portion of the body; Prevent or correct physical deformity or malfunction; or Support a weak or deformed portion of the body. Worn in or on the body means that the item is implanted or attached so that it becomes part of the body, or is carried by the body and does not hinder the mobility of the individual. Examples include artificial limbs, pacemakers, orthotics, orthopedics, ostomy/colostomy devices, catheters, etc.",
+            "name": "Prosthetic Devices with Prescription Billed to Medicaid",
+            "product_tax_code": "42242000A0003"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicare, a replacement, corrective, or supportive device, worn on or in the body to: Artificially replace a missing portion of the body; Prevent or correct physical deformity or malfunction; or Support a weak or deformed portion of the body. Worn in or on the body means that the item is implanted or attached so that it becomes part of the body, or is carried by the body and does not hinder the mobility of the individual. Examples include artificial limbs, pacemakers, orthotics, orthopedics, ostomy/colostomy devices, catheters, etc.",
+            "name": "Prosthetic Devices with Prescription Billed to Medicare",
+            "product_tax_code": "42242000A0002"
+        },
+        {
+            "description": "At home saliva, cheeek swab or blood drop based tests used to detect various genetic markers in an individual.",
+            "name": "DNA Testing Kits",
+            "product_tax_code": "41116205A0003"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicaid, nutritional tube feeding equipment including button-style feeding tubes, standard G-tubes, NG-tubes, extension sets, adapters, feeding pumps, feeding pump delivery sets.",
+            "name": "Enteral Feeding Equipment for home use with Prescription reimbursed by Medicaid",
+            "product_tax_code": "42231500A0005"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicaid, equipment used to administer oxygen directly into the lungs of the patient for the relief of conditions in which the human body experiences an abnormal deficiency or inadequate supply of oxygen. Oxygen equipment means oxygen cylinders, cylinder transport devices, including sheaths and carts, cylinder studs and support devices, regulators, flowmeters, tank wrenches, oxygen concentrators, liquid oxygen base dispensers, liquid oxygen portable dispensers, oxygen tubing, nasal cannulas, face masks, oxygen humidifiers, and oxygen fittings and accessories.",
+            "name": "Oxygen Delivery Equipment for home use with Prescription reimbursed by Medicaid",
+            "product_tax_code": "42271700A0005"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, equipment used to administer oxygen directly into the lungs of the patient for the relief of conditions in which the human body experiences an abnormal deficiency or inadequate supply of oxygen. Oxygen equipment means oxygen cylinders, cylinder transport devices, including sheaths and carts, cylinder studs and support devices, regulators, flowmeters, tank wrenches, oxygen concentrators, liquid oxygen base dispensers, liquid oxygen portable dispensers, oxygen tubing, nasal cannulas, face masks, oxygen humidifiers, and oxygen fittings and accessories.",
+            "name": "Oxygen Delivery Equipment for home use with Prescription",
+            "product_tax_code": "42271700A0001"
+        },
+        {
+            "description": "Synthetic or animal-based insulin used as an injectible drug for diabetes patients, sold without prescription order of a licensed professional.",
+            "name": "Insulin without Prescription",
+            "product_tax_code": "51183603A0001"
+        },
+        {
+            "description": "Single use hollow needle commonly used with a syringe to inject insulin into the body by diabetic individuals, sold without prescription order of a licensed professional.",
+            "name": "Hypodermic Needles/Syringes without prescription - Insulin",
+            "product_tax_code": "42142523A0001"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, equipment used to administer oxygen directly into the lungs of the patient for the relief of conditions in which the human body experiences an abnormal deficiency or inadequate supply of oxygen. Oxygen equipment means oxygen cylinders, cylinder transport devices, including sheaths and carts, cylinder studs and support devices, regulators, flowmeters, tank wrenches, oxygen concentrators, liquid oxygen base dispensers, liquid oxygen portable dispensers, oxygen tubing, nasal cannulas, face masks, oxygen humidifiers, and oxygen fittings and accessories.",
+            "name": "Oxygen Delivery Equipment for home use without Prescription",
+            "product_tax_code": "42271700A0006"
+        },
+        {
+            "description": "At home blood-prick based tests used to monitor cholesterol levels in an individual.",
+            "name": "Cholesterol Testing Kits",
+            "product_tax_code": "41116202A0001"
+        },
+        {
+            "description": "Single use supplies utilized by diabetics in the regular blood sugar monitoring regimen. Includes skin puncture lancets, test strips for blood glucose monitors, visual read test strips, and urine test strips, sold under prescription order of a licensed professional.",
+            "name": "Diabetic Testing Supplies - single use - with Prescription",
+            "product_tax_code": "41116120A0002"
+        },
+        {
+            "description": "A breast pump is a mechanical device that lactating women use to extract milk from their breasts. They may be manual devices powered by hand or foot movements or automatic devices powered by electricity.",
+            "name": "Breast Pumps",
+            "product_tax_code": "42231901A0000"
+        },
+        {
+            "description": "At home urine-based tests used to detect pregancy hormone levels.",
+            "name": "Pregenacy Testing Kits",
+            "product_tax_code": "41116205A0001"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, and reimbursed by Medicaid, equipment which is primarily and customarily used to provide or increase the ability to move from one place to another and which is appropriate for use either in a home or a motor vehicle; Is not generally used by persons with normal mobility; and Does not include any motor vehicle or equipment on a motor vehicle normally provided by a motor vehicle manufacturer.  Examples include wheelchairs, crutches, canes, walkers, chair lifts, etc.",
+            "name": "Mobility Enhancing Equipment with Prescription Reimbursed by Medicaid",
+            "product_tax_code": "42211500A0005"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, a replacement, corrective, or supportive device, worn in the mouth, including dentures, orthodontics, crowns, bridges, etc.",
+            "name": "Dental Prosthetics without Prescription",
+            "product_tax_code": "42151500A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, a replacement, corrective, or supportive device, worn in the mouth, including dentures, orthodontics, crowns, bridges, etc.",
+            "name": "Dental Prosthetics with Prescription",
+            "product_tax_code": "42151500A0001"
+        },
+        {
+            "description": "At home urine-based tests used to detect impending ovulation to assist in pregnancy planning.",
+            "name": "Ovulation Testing Kits",
+            "product_tax_code": "41116205A0002"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, nutritional tube feeding equipment including button-style feeding tubes, standard G-tubes, NG-tubes, extension sets, adapters, feeding pumps, feeding pump delivery sets.",
+            "name": "Enteral Feeding Equipment for home use without Prescription",
+            "product_tax_code": "42231500A0006"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicare, nutritional tube feeding equipment including button-style feeding tubes, standard G-tubes, NG-tubes, extension sets, adapters, feeding pumps, feeding pump delivery sets.",
+            "name": "Enteral Feeding Equipment for home use with Prescription reimbursed by Medicare",
+            "product_tax_code": "42231500A0004"
+        },
+        {
+            "description": "When sold without prescription order of a licensed professional, equipment that: can withstand repeated use; is primarily and customarily used to serve a medical purpose; generally is not useful to a person in the absence of illness or injury; and is not worn in or on the body. Home use means the equipment is sold to an individual for use at home, regardless of where the individual resides. Examples include hospital beds, commode chairs, bed pans, IV poles, etc.",
+            "name": "Durable Medical Equipment for home use without Prescription",
+            "product_tax_code": "42140000A0006"
+        },
+        {
+            "description": "Male or female condoms used to prevent pregnancy or exposure to STDs, containing a spermicidal lubricant as indicated by a \"drug facts\" panel or a statement of active ingredients.",
+            "name": "Condoms with Spermicide",
+            "product_tax_code": "53131622A0001"
+        },
+        {
+            "description": "Single use supplies utilized by diabetics in the regular blood sugar monitoring regimen. Includes skin puncture lancets, test strips for blood glucose monitors, visual read test strips, and urine test strips.",
+            "name": "Diabetic Testing Supplies - single use - without Prescription",
+            "product_tax_code": "41116120A0001"
+        },
+        {
+            "description": "An electronic device that clips onto a patient's finger to measure heart rate and oxygen saturation in his or her red blood cells.",
+            "name": "Pulse Oximeter",
+            "product_tax_code": "42181801A0000"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, equipment which is primarily and customarily used to provide or increase the ability to move from one place to another and which is appropriate for use either in a home or a motor vehicle; Is not generally used by persons with normal mobility; and Does not include any motor vehicle or equipment on a motor vehicle normally provided by a motor vehicle manufacturer.  Examples include wheelchairs, crutches, canes, walkers, chair lifts, etc.",
+            "name": "Mobility Enhancing Equipment with Prescription",
+            "product_tax_code": "42211500A0001"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, a replacement, corrective, or supportive device, worn on or in the body to: Artificially replace a missing portion of the body; Prevent or correct physical deformity or malfunction; or Support a weak or deformed portion of the body. Worn in or on the body means that the item is implanted or attached so that it becomes part of the body, or is carried by the body and does not hinder the mobility of the individual. Examples include artificial limbs, pacemakers, orthotics, orthopedics, ostomy/colostomy devices, catheters, etc.",
+            "name": "Prosthetic Devices with Prescription",
+            "product_tax_code": "42242000A0001"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicare, nutritional tube feeding equipment including button-style feeding tubes, standard G-tubes, NG-tubes, extension sets, adapters, feeding pumps, feeding pump delivery sets.",
+            "name": "Enteral Feeding Equipment for home use with Prescription billed to Medicare",
+            "product_tax_code": "42231500A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicaid, equipment that: can withstand repeated use; is primarily and customarily used to serve a medical purpose; generally is not useful to a person in the absence of illness or injury; and is not worn in or on the body. Home use means the equipment is sold to an individual for use at home, regardless of where the individual resides. Examples include hospital beds, commode chairs, bed pans, shower and bath aids, IV poles, etc.",
+            "name": "Durable Medical Equipment for home use with Prescription billed to Medicaid",
+            "product_tax_code": "42140000A0003"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicare, a replacement, corrective, or supportive device, worn on or in the body to: Artificially replace a missing portion of the body; Prevent or correct physical deformity or malfunction; or Support a weak or deformed portion of the body. Worn in or on the body means that the item is implanted or attached so that it becomes part of the body, or is carried by the body and does not hinder the mobility of the individual. Examples include artificial limbs, pacemakers, orthotics, orthopedics, ostomy/colostomy devices, catheters, etc.",
+            "name": "Prosthetic Devices with Prescription Reimbursed by Medicare",
+            "product_tax_code": "42242000A0004"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, and billed to Medicare, equipment which is primarily and customarily used to provide or increase the ability to move from one place to another and which is appropriate for use either in a home or a motor vehicle; Is not generally used by persons with normal mobility; and Does not include any motor vehicle or equipment on a motor vehicle normally provided by a motor vehicle manufacturer.  Examples include wheelchairs, crutches, canes, walkers, chair lifts, etc.",
+            "name": "Mobility Enhancing Equipment with Prescription Billed to Medicare",
+            "product_tax_code": "42211500A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, and billed to Medicaid, equipment which is primarily and customarily used to provide or increase the ability to move from one place to another and which is appropriate for use either in a home or a motor vehicle; Is not generally used by persons with normal mobility; and Does not include any motor vehicle or equipment on a motor vehicle normally provided by a motor vehicle manufacturer.  Examples include wheelchairs, crutches, canes, walkers, chair lifts, etc.",
+            "name": "Mobility Enhancing Equipment with Prescription Billed to Medicaid",
+            "product_tax_code": "42211500A0003"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicare, a machine used that filters a patient's blood to remove excess water and waste products when the kidneys are damaged, dysfunctional, or missing. The kidney dialysis machine is an artificial part which augments the natural functioning of the kidneys.",
+            "name": "Kidney Dialysis Equipment for home use with Prescription reimbursed by Medicare",
+            "product_tax_code": "42161800A0004"
+        },
+        {
+            "description": "At home digital or manual (aneroid) sphygmomanometers, also known as a blood pressure meter, blood pressure monitor, or blood pressure gauge, are devices used to measure blood pressure, composed of an inflatable cuff to collapse and then release the artery under the cuff in a controlled manner.",
+            "name": "Blood Pressure Testing Devices",
+            "product_tax_code": "42181600A0001"
+        },
+        {
+            "description": "A topical preparation containing a spermicidal lubricant to prevent pregnancy as indicated by a \"drug facts\" panel or a statement of active ingredients.",
+            "name": "Contraceptive Ointments",
+            "product_tax_code": "53131622A0002"
+        },
+        {
+            "description": "Male or female condoms used to prevent pregnacy or exposure to STDs.",
+            "name": "Condoms",
+            "product_tax_code": "53131622A0000"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicare, equipment that: can withstand repeated use; is primarily and customarily used to serve a medical purpose; generally is not useful to a person in the absence of illness or injury; and is not worn in or on the body. Home use means the equipment is sold to an individual for use at home, regardless of where the individual resides. Examples include hospital beds, commode chairs, bed pans, shower and bath aids, IV poles, etc.",
+            "name": "Durable Medical Equipment for home use with Prescription billed to Medicare",
+            "product_tax_code": "42140000A0002"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicaid, a replacement, corrective, or supportive device, worn on or in the body to: Artificially replace a missing portion of the body; Prevent or correct physical deformity or malfunction; or Support a weak or deformed portion of the body. Worn in or on the body means that the item is implanted or attached so that it becomes part of the body, or is carried by the body and does not hinder the mobility of the individual. Examples include artificial limbs, pacemakers, orthotics, orthopedics, ostomy/colostomy devices, catheters, etc.",
+            "name": "Prosthetic Devices with Prescription Reimbursed by Medicaid",
+            "product_tax_code": "42242000A0005"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, and reimbursed by Medicare, equipment which is primarily and customarily used to provide or increase the ability to move from one place to another and which is appropriate for use either in a home or a motor vehicle; Is not generally used by persons with normal mobility; and Does not include any motor vehicle or equipment on a motor vehicle normally provided by a motor vehicle manufacturer.  Examples include wheelchairs, crutches, canes, walkers, chair lifts, etc.",
+            "name": "Mobility Enhancing Equipment with Prescription Reimbursed by Medicare",
+            "product_tax_code": "42211500A0004"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicaid, nutritional tube feeding equipment including button-style feeding tubes, standard G-tubes, NG-tubes, extension sets, adapters, feeding pumps, feeding pump delivery sets.",
+            "name": "Enteral Feeding Equipment for home use with prescription billed to Medicaid",
+            "product_tax_code": "42231500A0003"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, nutritional tube feeding equipment including button-style feeding tubes, standard G-tubes, NG-tubes, extension sets, adapters, feeding pumps, feeding pump delivery sets.",
+            "name": "Enteral Feeding Equipment for home use with Prescription",
+            "product_tax_code": "42231500A0001"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and billed directly to Medicaid, a machine used that filters a patient's blood to remove excess water and waste products when the kidneys are damaged,  dysfunctional, or missing. The kidney dialysis machine is an artificial part which augments the natural functioning of the kidneys.",
+            "name": "Kidney Dialysis Equipment for home use with Prescription billed to Medicaid",
+            "product_tax_code": "42161800A0003"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional and reimbursed by Medicaid, a machine used that filters a patient's blood to remove excess water and waste products when the kidneys are damaged, dysfunctional, or missing. The kidney dialysis machine is an artificial part which augments the natural functioning of the kidneys.",
+            "name": "Kidney Dialysis Equipment for home use with Prescription and reimbursed by Medicaid",
+            "product_tax_code": "42161800A0005"
+        },
+        {
+            "description": "When sold under prescription order of a licensed professional, a machine used that filters a patient's blood to remove excess water and waste products when the kidneys are damaged,  dysfunctional, or missing. The kidney dialysis machine is an artificial part which augments the natural functioning of the kidneys.",
+            "name": "Kidney Dialysis Equipment for home use with Prescription",
+            "product_tax_code": "42161800A0001"
+        },
+        {
+            "description": "Handbags, Purses",
+            "name": "Handbags, Purses",
+            "product_tax_code": "53121600A0000"
+        },
+        {
+            "description": "Video Gaming Console - Fixed",
+            "name": "Video Gaming Console - Fixed",
+            "product_tax_code": "52161557A0000"
+        },
+        {
+            "description": "Video Cameras",
+            "name": "Video Cameras",
+            "product_tax_code": "45121515A0000"
+        },
+        {
+            "description": "Portable audio equipment that records digital music for playback",
+            "name": "Digital Music Players",
+            "product_tax_code": "52161543A0000"
+        },
+        {
+            "description": "A type of consumer electronic device used to play vinyl recordings.",
+            "name": "Audio Turntables",
+            "product_tax_code": "52161548A0000"
+        },
+        {
+            "description": "Video Gaming Console - Portable",
+            "name": "Video Gaming Console - Portable",
+            "product_tax_code": "52161558A0000"
+        },
+        {
+            "description": "A framed display designed to display preloaded digital images (jpeg or any digital image format). Has slots for flash memory cards and/or an interface for digital photo camera connection.",
+            "name": "Digital Picture Frames",
+            "product_tax_code": "52161549A0000"
+        },
+        {
+            "description": "Digital Cameras",
+            "name": "Digital Cameras",
+            "product_tax_code": "45121504A0000"
+        },
+        {
+            "description": "Mobile Phones",
+            "name": "Mobile Phones",
+            "product_tax_code": "43191501A0000"
+        },
+        {
+            "description": "A digital wristwatch that provides many other features besides timekeeping.  Like a smartphone, a smartwatch has a touchscreen display, which allows you to perform actions by tapping or swiping on the screen. Smartwatches include allow access to apps, similar to apps for smartphones and tablets.",
+            "name": "Watches - Smart",
+            "product_tax_code": "54111500A0001"
+        },
+        {
+            "description": "A bicycle helmet that is NOT marketed and labeled as being intended for youth.",
+            "name": "Bicycle Helmets - Adult",
+            "product_tax_code": "46181704A0003"
+        },
+        {
+            "description": "A bicycle helmet marketed and labeled as being intended for youth.",
+            "name": "Bicycle Helmets - Youth",
+            "product_tax_code": "46181704A0002"
+        },
+        {
+            "description": "Luggage",
+            "name": "Luggage",
+            "product_tax_code": "53121500A0000"
+        },
+        {
+            "description": "Clothing - Sleep or eye mask",
+            "name": "Clothing - Sleep or eye mask",
+            "product_tax_code": "53102607A0000"
+        },
+        {
+            "description": "Clothing - Pocket protectors",
+            "name": "Clothing - Pocket protectors",
+            "product_tax_code": "53102514A0000"
+        },
+        {
+            "description": "Clothing - Button covers",
+            "name": "Clothing - Button covers",
+            "product_tax_code": "53102515A0000"
+        },
+        {
+            "description": "Shoe Inserts/Insoles",
+            "name": "Clothing - Shoe Inserts/Insoles",
+            "product_tax_code": "46182208A0000"
+        },
+        {
+            "description": "Aprons - Household/Kitchen",
+            "name": "Clothing - Aprons - Household/Kitchen",
+            "product_tax_code": "46181501A0002"
+        },
+        {
+            "description": "Hunting Vests",
+            "name": "Clothing - Hunting Vests",
+            "product_tax_code": "53103100A0003"
+        },
+        {
+            "description": "Clothing apparel/uniforms that are specific to the training and competition of various martial arts.",
+            "name": "Clothing - Martial Arts Attire",
+            "product_tax_code": "53102717A0001"
+        },
+        {
+            "description": "Clothing - Umbrellas",
+            "name": "Clothing - Umbrellas",
+            "product_tax_code": "53102505A0000"
+        },
+        {
+            "description": "Briefcases",
+            "name": "Briefcases",
+            "product_tax_code": "53121701A0000"
+        },
+        {
+            "description": "Wallets",
+            "name": "Wallets",
+            "product_tax_code": "53121600A0001"
+        },
+        {
+            "description": "Wristwatch timepieces",
+            "name": "Watches",
+            "product_tax_code": "54111500A0000"
+        },
+        {
+            "description": "Jewelry",
+            "name": "Jewelry",
+            "product_tax_code": "54100000A0000"
+        },
+        {
+            "description": "Non-prescription sunglasses",
+            "name": "Sunglasses - Non-Rx",
+            "product_tax_code": "42142905A0001"
+        },
+        {
+            "description": "Wigs, Hairpieces, Hair extensions",
+            "name": "Clothing - Wigs, Hairpieces, Hair extensions",
+            "product_tax_code": "53102500A0002"
+        },
+        {
+            "description": "Hair notions, hair clips,  barrettes, hair bows, hair nets, etc.",
+            "name": "Clothing - Hair Accessories",
+            "product_tax_code": "53102500A0001"
+        },
+        {
+            "description": "Clothing - Headbands",
+            "name": "Clothing - Headbands",
+            "product_tax_code": "53102513A0000"
+        },
+        {
+            "description": "These supplies contain medication such as an antibiotic ointment.  They are a labeled with a \"drug facts\" panel or a statement of active ingredients.  A wound care supply is defined as an item that is applied directly to or inside a wound to absorb wound drainage, protect healing tissue, maintain a moist or dry wound environment (as appropriate), or prevent bacterial contamination.  Examples include bandages, dressings, gauze, medical tape.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Wound Care Supplies - Bandages, Dressings, Gauze - Medicated",
+            "product_tax_code": "42311514A0000"
+        },
+        {
+            "description": "A wound care supply is defined as an item that is applied directly to or inside a wound to absorb wound drainage, protect healing tissue, maintain a moist or dry wound environment (as appropriate), or prevent bacterial contamination.  Examples include bandages, dressings, gauze, medical tape.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Wound Care Supplies - Bandages, Dressings, Gauze",
+            "product_tax_code": "42311500A0001"
+        },
+        {
+            "description": "Toothpaste containing \"drug facts\" panel or a statement of active ingredients.  These products do contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Toothpaste",
+            "product_tax_code": "53131502A0000"
+        },
+        {
+            "description": "Disposable moistened cleansing wipes - non medicated.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Baby Wipes/Cleansing Wipes",
+            "product_tax_code": "53131624A0000"
+        },
+        {
+            "description": "Toilet Paper.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Toilet Paper",
+            "product_tax_code": "14111704A0000"
+        },
+        {
+            "description": "A lotion, spray, gel, foam, stick or other topical product that absorbs or reflects some of the sun's ultraviolet (UV) radiation and thus helps protect against sunburn.  Sunscreen contains a \"drug facts\" label or statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Sunscreen",
+            "product_tax_code": "53131609A0000"
+        },
+        {
+            "description": "Soaps, body washes, shower gels for personal hygiene containing antibacterial. These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Soaps - Antibacterial",
+            "product_tax_code": "53131608A0001"
+        },
+        {
+            "description": "Over-the-counter nicotine replacement products, including patches, gum, lozenges, sprays and inhalers.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Smoking Cessation Products",
+            "product_tax_code": "51143218A0000"
+        },
+        {
+            "description": "Lotions, moisturizers, creams, powders, sprays, etc that promote optimal skin health.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Skin Care Products- Medicated",
+            "product_tax_code": "51241200A0001"
+        },
+        {
+            "description": "A hair care product for cleansing the hair/scalp, with anti-dandruff active ingredients.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Shampoo - medicated (anti-dandruff)",
+            "product_tax_code": "53131628A0001"
+        },
+        {
+            "description": "A multi-purpose skin protectorant and topical ointment.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Petroleum Jelly",
+            "product_tax_code": "53131641A0000"
+        },
+        {
+            "description": "An over-the-counter drug via RX is a substance that contains a label identifying it as a drug and including a \"drug facts\" panel or a statement of active ingredients, that can be obtained without a prescription,  but is sold under prescription order of a licensed professional.  A drug can be intended for internal (ingestible, implant, injectable) or external (topical) application to the human body.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Over-the-Counter Drugs via Prescription",
+            "product_tax_code": "51030"
+        },
+        {
+            "description": "Flexible adhesive strips that attach over the bridge of the nose to lift the sides of the nose, opening the nasal passages to provide relief for congestion and snoring.  The products are drug free and contain no active drug ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Nasal Breathing Strips",
+            "product_tax_code": "42312402A0001"
+        },
+        {
+            "description": "Therapeutic mouthwash, having active ingredients (such as antiseptic, or flouride) intended to help control or reduce conditions like bad breath, gingivitis, plaque, and tooth decay.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Mouthwash - Therapeutic",
+            "product_tax_code": "53131501A0000"
+        },
+        {
+            "description": "Multiple use medical thermometers for oral, temporal/forehead, or rectal body temperature diagnostics.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Medical Thermometers - Reusable",
+            "product_tax_code": "42182200A0002"
+        },
+        {
+            "description": "One-time use medical thermometers for oral, temporal/forehead, or rectal body temperature diagnostics.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Medical Thermometers - Disposable",
+            "product_tax_code": "42182200A0001"
+        },
+        {
+            "description": "Masks designed for one-time use to protect the wearer from contamination of breathable particles.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Medical Masks",
+            "product_tax_code": "42131713A0001"
+        },
+        {
+            "description": "A medicated skin protectorant for the lips.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Lip Balm - Medicated",
+            "product_tax_code": "53131630A0001"
+        },
+        {
+            "description": "A skin protectorant for the lips.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Lip Balm",
+            "product_tax_code": "53131630A0000"
+        },
+        {
+            "description": "Artificial devices to correct or alleviate hearing deficiencies, sold under prescription order of a licensed professional. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hearing Aids with Prescription",
+            "product_tax_code": "42211705A0000"
+        },
+        {
+            "description": "Artificial deives to correct or alleviate hearing deficiencies, sold without a prescription order of a licensed professional. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hearing Aids without Prescription",
+            "product_tax_code": "42211705A0001"
+        },
+        {
+            "description": "Batteries specifically labeled and designed to operate hearing aid devices, sold under prescription order of a licensed professional. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hearing Aid Batteries with Prescription",
+            "product_tax_code": "26111710A0001"
+        },
+        {
+            "description": "Batteries specifically labeled and designed to operate hearing aid devices, sold without a prescription order of a licensed professional.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hearing Aid Batteries without Prescription",
+            "product_tax_code": "26111710A0002"
+        },
+        {
+            "description": "A liquid, gel, foam, or wipe generally used to decrease infectious agents on the hands. Alcohol-based versions typically contain some combination of isopropyl alcohol, ethanol (ethyl alcohol), or n-propanol.  Alcohol-free products are generally based on disinfectants, or on antimicrobial agents.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hand Sanitizers",
+            "product_tax_code": "53131626A0000"
+        },
+        {
+            "description": "Topical foams, creams, gels, etc that prevent hair loss and promote hair regrowth.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hair Loss Products",
+            "product_tax_code": "51182001A0001"
+        },
+        {
+            "description": "A collection of mixed supplies and equipment that is used to give medical treatment, often housed in durable plastic boxes, fabric pouches or in wall mounted cabinets.  Exempt or low rated qualifying medicinal items (eg. OTC drugs) make up 51% or more of the value of the kit.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "First Aid Kits - 51% or more medicinal items",
+            "product_tax_code": "42172001A0002"
+        },
+        {
+            "description": "A collection of mixed supplies and equipment that is used to give medical treatment, often housed in durable plastic boxes, fabric pouches or in wall mounted cabinets.  Exempt or low rated qualifying medicinal items (eg. OTC drugs) make up 50% or less of the value of the kit.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "First Aid Kits - 50% or less medicinal items",
+            "product_tax_code": "42172001A0001"
+        },
+        {
+            "description": "Over-the-counter antifungal creams, ointments or suppositories to treat yeast infections, containing a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Feminine Yeast Treatments",
+            "product_tax_code": "51302300A0001"
+        },
+        {
+            "description": "Vaginal cleaning products include douches and wipes with medication such as an antiseptic, containing a \"drug facts\" panel or a statement of active ingredients. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Feminine Cleansing Solutions - Medicated",
+            "product_tax_code": "53131615A0002"
+        },
+        {
+            "description": "Vaginal cleaning products include douches and wipes. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Feminine Cleansing Solutions",
+            "product_tax_code": "53131615A0001"
+        },
+        {
+            "description": "Single use disposable gloves (latex, nitrile, vinyl, etc) that while appropriate for multiple uses,  have an application in a first aid or medical setting.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Disposable Gloves",
+            "product_tax_code": "42132203A0000"
+        },
+        {
+            "description": "Personal under-arm deodorants/antiperspirants.  These products do contain a \"drug facts\" panel or a statement of active ingredients, typically aluminum.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Deodorant/Antiperspirant",
+            "product_tax_code": "53131606A0000"
+        },
+        {
+            "description": "Denture adhesives are pastes, powders or adhesive pads that may be placed in/on dentures to help them stay in place.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Denture creams/adhesives",
+            "product_tax_code": "53131510A0000"
+        },
+        {
+            "description": "Toothbrushes.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Toothbrushes",
+            "product_tax_code": "53131503A0000"
+        },
+        {
+            "description": "Dental Floss/picks.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Dental Floss/picks",
+            "product_tax_code": "53131504A0000"
+        },
+        {
+            "description": "Single use cotton balls or swabs for multi-purpose use other than applying medicines and cleaning wounds, due to not being sterile.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Cotton Balls/Swabs - Unsterile",
+            "product_tax_code": "42141500A0002"
+        },
+        {
+            "description": "Single use cotton balls or swabs for application of antiseptics and medications and to cleanse scratches, cuts or minor wounds.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Cotton Balls/Swabs - Sterile",
+            "product_tax_code": "42141500A0001"
+        },
+        {
+            "description": "Corrective lenses, eyeglasses, sold under prescription order of a licensed professional.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Corrective Lenses, Eyeglasses with Prescription",
+            "product_tax_code": "42142900A0001"
+        },
+        {
+            "description": "Corrective lenses, including eyeglasses and contact lenses, sold without a prescription order of a licensed professional.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Corrective Lenses without Prescription",
+            "product_tax_code": "42142900A0002"
+        },
+        {
+            "description": "Liquid solution for lubricating/rewetting, but not disinfecting, contact lenses.  This solution is applied directly to the lens, rather then inserted into the eye. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Contact Lens Lubricating Solutions - For lens",
+            "product_tax_code": "42142914A0001"
+        },
+        {
+            "description": "Liquid solution for lubricating/rewetting, but not disinfecting, contact lenses.  This solution is applied directly to the eye. This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Contact Lens Lubricating Solutions - For eyes",
+            "product_tax_code": "42142914A0002"
+        },
+        {
+            "description": "Contact lenses, sold under prescription order of a licensed professional.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Contact Lenses with Prescription",
+            "product_tax_code": "42142913A0000"
+        },
+        {
+            "description": "Liquid solution for cleaning and disinfecting contact lenses.   This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Contact Lens Disinfecting Solutions",
+            "product_tax_code": "42142914A0000"
+        },
+        {
+            "description": "A reusable pain management supply that includes artificial ice packs, gel packs, heat wraps, etc used for pain relief.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Cold or Hot Therapy Packs - Reusable",
+            "product_tax_code": "42142100A0002"
+        },
+        {
+            "description": "A heating pad is a pad used for warming of parts of the body in order to manage pain.  Types of heating pads include electrical, chemical and hot water bottles.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Heating Pads",
+            "product_tax_code": "42142100A0001"
+        },
+        {
+            "description": "A single use pain management supply that includes artificial ice packs, gel packs, heat wraps, etc used for pain relief.   These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Cold or Hot Therapy Packs - Disposable - Medicated",
+            "product_tax_code": "42142100A0004"
+        },
+        {
+            "description": "A single use pain management supply that includes artificial ice packs, gel packs, heat wraps, etc used for pain relief.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Cold or Hot Therapy Packs - Disposable",
+            "product_tax_code": "42142100A0003"
+        },
+        {
+            "description": "Baby powder is an astringent powder used for preventing diaper rash, as a spray, and for other cosmetic uses. It may be composed of talcum (in which case it is also called talcum powder) or corn starch.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Baby Powder",
+            "product_tax_code": "53131649A0001"
+        },
+        {
+            "description": "Baby oil is an inert (typically mineral) oil for the purpose of keeping skin soft and supple.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Baby Oil",
+            "product_tax_code": "51241900A0001"
+        },
+        {
+            "description": "A cosmetic foam or gel used for shaving preparation. The purpose of shaving cream is to soften the hair by providing lubrication.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Shaving Creams",
+            "product_tax_code": "53131611A0000"
+        },
+        {
+            "description": "Personal under-arm deodorants/antiperspirants containing natural ingredients and/or ingredients that are not considered drugs.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Deodorant - Natural or no active ingredients",
+            "product_tax_code": "53131606A0001"
+        },
+        {
+            "description": "A hair care product for cleansing the hair/scalp.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Shampoo",
+            "product_tax_code": "53131628A0000"
+        },
+        {
+            "description": "Various surfactant preparations to improve cleaning, enhance the enjoyment of bathing, and serve as a vehicle for cosmetic agents.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Bubble Bath, Bath Salts/Oils/Crystals",
+            "product_tax_code": "53131612A0001"
+        },
+        {
+            "description": "Cosmetic mouthwash may temporarily control bad breath and leave behind a pleasant taste, but has no chemical or biological application beyond their temporary benefit.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Mouthwash - Cosmetic",
+            "product_tax_code": "53131501A0001"
+        },
+        {
+            "description": "Teeth whitening gels, rinse, strips, trays, etc containing bleaching agents.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Teeth Whitening Kits",
+            "product_tax_code": "42151506A0000"
+        },
+        {
+            "description": "A hair care product typically applied and rinsed after shampooing that is used to improve the feel, appearance and manageability of hair.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Conditioner - Hair",
+            "product_tax_code": "53131628A0002"
+        },
+        {
+            "description": "Depilatories are cosmetic preparations used to remove hair from the skin.  Chemical depilatories are available in gel, cream, lotion, aerosol, roll-on, and powder forms.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hair Removal Products",
+            "product_tax_code": "53131623A0000"
+        },
+        {
+            "description": "Breath spray is a product sprayed into the mouth and breath strips dissolve in the mouth for the purpose of eliminating halitosis.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Breath Spray/dissolvable strips",
+            "product_tax_code": "53131509A0000"
+        },
+        {
+            "description": "Lotions, moisturizers, creams, powders, sprays, etc that promote optimal skin health.  These products do not contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Skin Care Products",
+            "product_tax_code": "51241200A0002"
+        },
+        {
+            "description": "Liquid drops to be placed inside the ear canal to reduce the symptoms of an ear ache, or to act as an ear drying aid, or to loosen, cleanse, and aid in the removal of ear wax.  These products contain a \"drug facts\" panel or a statement of active ingredients.  Examples include Ear Ache, Swimmers' Ears, and Ear Wax removal drops.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Ear Drops - Medicated",
+            "product_tax_code": "51241000A0001"
+        },
+        {
+            "description": "Topical medicated solutions for treating skin acne.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Acne Treatments",
+            "product_tax_code": "51241400A0001"
+        },
+        {
+            "description": "A skin cream forming a protective barrier to help heal and soothe diaper rash discomfort.  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Diaper Cream",
+            "product_tax_code": "51241859A0001"
+        },
+        {
+            "description": "A liquid solution typically used as a topical antiseptic.  The products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Isopropyl (Rubbing) Alcohol",
+            "product_tax_code": "51471901A0000"
+        },
+        {
+            "description": "Hydrogen peroxide is a mild antiseptic used on the skin to prevent infection of minor cuts, scrapes, and burns.  It may also be used as a mouth rinse to help remove mucus or to relieve minor mouth irritation (e.g., due to canker/cold sores, gingivitis).  These products contain a \"drug facts\" panel or a statement of active ingredients.  This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Hydrogen Peroxide",
+            "product_tax_code": "51473503A0000"
+        },
+        {
+            "description": "Articles intended to be rubbed, poured, sprinkled, or sprayed on, introduced into, or otherwise applied to the human body or any part thereof for beautifying, promoting attractiveness, or altering the appearance.  This category supports only the following items: Acrylic fingernail glue, Acrylic fingernails, Artificial eyelashes, Blush, Bronzer, Body glitter, Concealer, Eyelash glue, Finger/toenail decorations, Finger/toenail polish, Nail polish remover, Hair coloring, Hair mousse/gel, Hair oil, Hair spray, Hair relaxer, Hair wave treatment, Hair wax, Lip gloss, Lip liner, Lipstick, Liquid foundation, Makeup, Mascara, Nail polish remover, Powder foundation, Cologne, Perfume.    This code is intended for sales directly to end consumers that are NOT healthcare providers.",
+            "name": "Cosmetics - Beautifying",
+            "product_tax_code": "53131619A0001"
+        },
+        {
+            "description": "Power cords",
+            "name": "Power cords",
+            "product_tax_code": "26121636A0000"
+        },
+        {
+            "description": "Archery accessories including quivers, releases, arrow shafts, armguards, hunting belts, bow parts, cleaning products, mounted safety equipment, scopes, sights, hunting slings, string wax, targets, target throwers, etc.",
+            "name": "Archery Accessories",
+            "product_tax_code": "49181602A0002"
+        },
+        {
+            "description": "Landscape soil, mulch, compost - residential",
+            "name": "Landscape Soil, Mulch, Compost - Residential",
+            "product_tax_code": "11121700A0001"
+        },
+        {
+            "description": "Firearms, limited to pistols, revolvers, rifles with a barrel no greater than an internal diameter of .50 caliber or a shotguns of 10 gauge or smaller.",
+            "name": "Firearms",
+            "product_tax_code": "46101500A0001"
+        },
+        {
+            "description": "Firearm accessories including repair parts, cleaning products, holsters, mounted safety equipment, choke tubes, scopes, shooting tripod/bipod/monopod, shooting bags/pouches, sights, etc.",
+            "name": "Firearm Accessories",
+            "product_tax_code": "46101506A0001"
+        },
+        {
+            "description": "Portable fuel container",
+            "name": "Portable Fuel Container",
+            "product_tax_code": "24111808A0001"
+        },
+        {
+            "description": "Hard and soft cases designed specifically for firearms equipment",
+            "name": "Gun Cases",
+            "product_tax_code": "46101801A0000"
+        },
+        {
+            "description": "Primary archery equipment including bows, crossbow, and bow strings.",
+            "name": "Archery Equipment",
+            "product_tax_code": "49181602A0001"
+        },
+        {
+            "description": "Hard and soft cases designed specifically for archery equipment.",
+            "name": "Archery Cases",
+            "product_tax_code": "46101801A0001"
+        },
+        {
+            "description": "Protective earmuffs to muffle the sound of gunfire.",
+            "name": "Hearing Protection Earmuffs",
+            "product_tax_code": "46181902A0001"
+        },
+        {
+            "description": "Ammunition for firearms with a barrel no greater than an internal diameter of .50 caliber or a shotgun of 10 gauge or smaller., including bullets, shotgun shells, and gunpowder.",
+            "name": "Ammunition",
+            "product_tax_code": "46101600A0001"
+        },
+        {
+            "description": "Bedclothes items including sheets, pillow cases, bedspreads, comforters, blankets, throws, duvet covers, pillow shams, bed skirts, mattress pad, mattress toppers, and pillows.",
+            "name": "Bedding",
+            "product_tax_code": "52121500A0000"
+        },
+        {
+            "description": "Towels used for individual drying of persons, including bath towels, beach towels, wash cloths, hand towels, fact towels, sport towels, etc.",
+            "name": "Bath towels",
+            "product_tax_code": "52121700A0000"
+        },
+        {
+            "description": "WaterSense labeled urinals.",
+            "name": "Urinals - WaterSense",
+            "product_tax_code": "30181506A0000"
+        },
+        {
+            "description": "WaterSense labeled toilets.",
+            "name": "Toilets - WaterSense",
+            "product_tax_code": "30181505A0000"
+        },
+        {
+            "description": "WaterSense labeled irrigation controllers, which act like a thermostat for your sprinkler system telling it when to turn on and off, use local weather and landscape conditions to tailor watering schedules to actual conditions on the site.",
+            "name": "Irrigation Controls - WaterSense",
+            "product_tax_code": "21102503A0001"
+        },
+        {
+            "description": "Ceiling Fans carrying an Energy Star rating.",
+            "name": "Ceiling fans - Energy Star",
+            "product_tax_code": "40101609A0000"
+        },
+        {
+            "description": "Standard incandescent light bulbs carrying an Energy Star rating.",
+            "name": "Incandescent Light Bulbs - Energy Star",
+            "product_tax_code": "39101612A0001"
+        },
+        {
+            "description": "Compact Fluorescent light (CFL) bulbs carrying an Energy Star rating.",
+            "name": "Compact Fluorescent Light Bulbs - Energy Star",
+            "product_tax_code": "39101619A0001"
+        },
+        {
+            "description": "Domestic appliance carrying an Energy Star Rating which reduces and maintains the level of humidity in the air.",
+            "name": "Dehumidifier - Energy Star",
+            "product_tax_code": "40101902A0000"
+        },
+        {
+            "description": "Domestic air conditioning (central or room) systems carrying Energy Star rating.",
+            "name": "Air conditioners - Energy Star",
+            "product_tax_code": "40101701A0000"
+        },
+        {
+            "description": "Artificial ice, blue ice, ice packs, reusable ice",
+            "name": "Artificial Ice",
+            "product_tax_code": "24121512A0000"
+        },
+        {
+            "description": "A port replicator is an attachment for a notebook computer that allows a number of devices such as a printer, large monitor, and keyboard to be simultaneously connected.",
+            "name": "Port Replicators",
+            "product_tax_code": "43211603A0000"
+        },
+        {
+            "description": "Computer Mouse/Pointing Devices",
+            "name": "Computer Mouse/Pointing Devices",
+            "product_tax_code": "43211708A0000"
+        },
+        {
+            "description": "Storage drives, hard drives, Zip drives, etc.",
+            "name": "Computer Drives",
+            "product_tax_code": "43201800A0001"
+        },
+        {
+            "description": "An in home programmable thermostat, such as a WiFi enabled smart thermostat, carrying an Energy Star rating.",
+            "name": "Programmable Wall Thermostat - Energy Star",
+            "product_tax_code": "41112209A0001"
+        },
+        {
+            "description": "Domestic gas or oil boilers for space or water heating carrying an Energy Star rating.",
+            "name": "Boilers - Energy Star",
+            "product_tax_code": "40102004A0001"
+        },
+        {
+            "description": "Domestic water heater carrying Energy Star rating.",
+            "name": "Water heater - Energy Star",
+            "product_tax_code": "40101825A0000"
+        },
+        {
+            "description": "Domestic freezers carrying Energy Star rating.",
+            "name": "Freezers- Energy Star",
+            "product_tax_code": "52141506A0000"
+        },
+        {
+            "description": "Domestic air source heat pumps carrying Energy Star rating.",
+            "name": "Heat Pumps - Energy Star",
+            "product_tax_code": "40101806A0000"
+        },
+        {
+            "description": "Domestic gas or oil furnaces carrying an Energy Star rating.",
+            "name": "Furnaces - Energy Star",
+            "product_tax_code": "40101805A0000"
+        },
+        {
+            "description": "Plywood, window film, storm shutters, hurricane shutters or other materials specifically designed to protect windows.",
+            "name": "Storm shutters/window protection devices",
+            "product_tax_code": "30151801A0001"
+        },
+        {
+            "description": "Smoke Detectors",
+            "name": "Smoke Detectors",
+            "product_tax_code": "46191501A0000"
+        },
+        {
+            "description": "Mobile phone charging device/cord",
+            "name": "Mobile Phone Charging Device/cord",
+            "product_tax_code": "43191501A0002"
+        },
+        {
+            "description": "A webcam is a video camera that feeds or streams an image or video in real time to or through a computer to a computer network, such as the Internet. Webcams are typically small cameras that sit on a desk, attach to a user's monitor, or are built into the hardware",
+            "name": "Web Camera",
+            "product_tax_code": "45121520A0000"
+        },
+        {
+            "description": "A sound card is an expansion component used in computers to receive and send audio.",
+            "name": "Sound Cards",
+            "product_tax_code": "43201502A0000"
+        },
+        {
+            "description": "Computer Speakers",
+            "name": "Computer Speakers",
+            "product_tax_code": "43211607A0000"
+        },
+        {
+            "description": "Computer Microphones",
+            "name": "Computer Microphones",
+            "product_tax_code": "43211719A0000"
+        },
+        {
+            "description": "A docking station is a hardware frame and set of electrical connection interfaces that enable a notebook computer to effectively serve as a desktop computer.",
+            "name": "Docking Stations",
+            "product_tax_code": "43211602A0000"
+        },
+        {
+            "description": "Computer Batteries",
+            "name": "Computer Batteries",
+            "product_tax_code": "26111711A0001"
+        },
+        {
+            "description": "Computer Monitor/Displays",
+            "name": "Computer Monitor/Displays",
+            "product_tax_code": "43211900A0000"
+        },
+        {
+            "description": "Computer Keyboards",
+            "name": "Computer Keyboards",
+            "product_tax_code": "43211706A0000"
+        },
+        {
+            "description": "Printer Ink",
+            "name": "Printer Ink",
+            "product_tax_code": "44103105A0000"
+        },
+        {
+            "description": "Printer Paper",
+            "name": "Printer Paper",
+            "product_tax_code": "14111507A0000"
+        },
+        {
+            "description": "Non-electric can opener",
+            "name": "Can opener - manual",
+            "product_tax_code": "52151605A0001"
+        },
+        {
+            "description": "Sheet music - Student",
+            "name": "Sheet music - Student",
+            "product_tax_code": "55101514A0000"
+        },
+        {
+            "description": "Musical instruments - Student",
+            "name": "Musical instruments - Student",
+            "product_tax_code": "60130000A0001"
+        },
+        {
+            "description": "Reference printed material commonly used by a student in a course of study as a reference and to learn the subject being taught.",
+            "name": "Dictionaries/Thesauruses",
+            "product_tax_code": "55101526A0001"
+        },
+        {
+            "description": "An item commonly used by a student in a course of study for artwork.  This category is limited to the following items...clay and glazes, paints, paintbrushes for artwork, sketch and drawing pads, watercolors.",
+            "name": "School Art Supplies",
+            "product_tax_code": "60121200A0001"
+        },
+        {
+            "description": "A calendar based notebook to aid in outlining one's daily appointments, classes, activities, etc.",
+            "name": "Daily Planners",
+            "product_tax_code": "44112004A0001"
+        },
+        {
+            "description": "Portable self-powered or battery powered radio, two-way radio, weatherband radio.",
+            "name": "Portable Radios",
+            "product_tax_code": "43191510A0000"
+        },
+        {
+            "description": "Single or multi-pack AA, AAA, c, D, 6-volt or 9-volt batteries, excluding automobile or boat batteries.",
+            "name": "Alkaline Batteries",
+            "product_tax_code": "26111702A0000"
+        },
+        {
+            "description": "Routers",
+            "name": "Routers",
+            "product_tax_code": "43222609A0000"
+        },
+        {
+            "description": "Removable storage media such as compact disks, flash drives, thumb drives, flash memory cards.",
+            "name": "Computer Storage Media",
+            "product_tax_code": "43202000A0000"
+        },
+        {
+            "description": "Computer Printer",
+            "name": "Computer Printer",
+            "product_tax_code": "43212100A0001"
+        },
+        {
+            "description": "Portable self-powered or battery powered light sources, including flashlights, lanterns, emergency glow sticks or light sticks.",
+            "name": "Portable Light Sources",
+            "product_tax_code": "39111610A0000"
+        },
+        {
+            "description": "Canned software on tangible media that is used for non-recreational purposes, such as Antivirus, Database, Educational, Financial, Word processing, etc.",
+            "name": "Software - Prewritten, tangible media - Non-recreational",
+            "product_tax_code": "43230000A1101"
+        },
+        {
+            "description": "Personal computers, including laptops, tablets, desktops.",
+            "name": "Personal Computers",
+            "product_tax_code": "43211500A0001"
+        },
+        {
+            "description": "A device that joins pages of paper or similar material by fastening a thin metal staple through the sheets and folding the ends underneath.",
+            "name": "Staplers/Staples",
+            "product_tax_code": "44121615A0000"
+        },
+        {
+            "description": "Pins/tacks to secure papers, pictures, calendars, etc. to bulletin boards, walls, etc.",
+            "name": "Push pins/tacks",
+            "product_tax_code": "44122106A0000"
+        },
+        {
+            "description": "Bags/packs designed to carry students' books during the school day.  This category does not include backpags for traveling, hiking, camping, etc.",
+            "name": "Bookbags/Backpacks - Student",
+            "product_tax_code": "53121603A0001"
+        },
+        {
+            "description": "Ground anchor systems and tie down kits for securing property against severe weather.",
+            "name": "Ground Anchor Systems and Tie-down Kits",
+            "product_tax_code": "31162108A0000"
+        },
+        {
+            "description": "An expansion card that allows the computer to send graphical information to a video display device such as a monitor, TV, or projector. Video cards are often used by gamers in place of integrated graphics due to their extra processing power and video ram.",
+            "name": "Video/Graphics Card",
+            "product_tax_code": "43201401A0000"
+        },
+        {
+            "description": "Scanners",
+            "name": "Scanners",
+            "product_tax_code": "43211711A0000"
+        },
+        {
+            "description": "Modems",
+            "name": "Modems",
+            "product_tax_code": "43222628A0000"
+        },
+        {
+            "description": "A map that could be used by a student in a course of study as a reference and to learn the subject being taught.",
+            "name": "Maps - Student",
+            "product_tax_code": "60103410A0001"
+        },
+        {
+            "description": "Tarps, plastic sheeting, plastic drop cloths, waterproof sheeting.",
+            "name": "Tarpaulins and Weatherproof Sheeting",
+            "product_tax_code": "24141506A0000"
+        },
+        {
+            "description": "Portable generator used to provide light or communications or power appliances during a power outage.",
+            "name": "Portable Generator",
+            "product_tax_code": "26111604A0001"
+        },
+        {
+            "description": "Headphones/Earbuds",
+            "name": "Headphones/Earbuds",
+            "product_tax_code": "52161514A0000"
+        },
+        {
+            "description": "A portable electronic device for reading digital books and periodicals.",
+            "name": "E-Book Readers",
+            "product_tax_code": "43211519A0000"
+        },
+        {
+            "description": "Mobile phone batteries",
+            "name": "Mobile Phone Batteries",
+            "product_tax_code": "43191501A0001"
+        },
+        {
+            "description": "A globe that could be used by a student in a course of study as a reference and to learn the subject being taught.",
+            "name": "Globes - Student",
+            "product_tax_code": "60104414A0001"
+        },
+        {
+            "description": "Domestic standard size refrigerators carrying Energy Star rating.",
+            "name": "Refrigerators - Energy Star",
+            "product_tax_code": "52141501A0000"
+        },
+        {
+            "description": "Non-electric food or beverage cooler.",
+            "name": "Food Storage Cooler",
+            "product_tax_code": "52152002A0001"
+        },
+        {
+            "description": "A motherboard is the physical component in a computer that contains the computer's basic circuitry and other components",
+            "name": "Motherboards",
+            "product_tax_code": "43201513A0000"
+        },
+        {
+            "description": "Calculators",
+            "name": "Calculators",
+            "product_tax_code": "44101807A0000"
+        },
+        {
+            "description": "Axes/Hatchets",
+            "name": "Axes/Hatchets",
+            "product_tax_code": "27112005A0000"
+        },
+        {
+            "description": "Water conserving products are for conserving or retaining groundwater; recharging water tables; or decreasing ambient air temperature, and so limiting water evaporation. Examples include soil sufactants, a soaker or drip-irrigation hose, a moisture control for a sprinkler or irrigation system, a rain barrel or an alternative rain and moisture collection system, a permeable ground cover surface that allows water to reach underground basins, aquifers or water collection points.",
+            "name": "Water Conserving Products",
+            "product_tax_code": "21102500A0001"
+        },
+        {
+            "description": "Domestic clothes drying appliances carrying Energy Star rating.",
+            "name": "Clothes drying machine - Energy Star",
+            "product_tax_code": "52141602A0000"
+        },
+        {
+            "description": "Software - Prewritten, electronic delivery - Business Use",
+            "name": "Software - Prewritten, electronic delivery - Business Use",
+            "product_tax_code": "43230000A9200"
+        },
+        {
+            "description": "Software - Custom, electronic delivery - Business Use",
+            "name": "Software - Custom, electronic delivery - Business Use",
+            "product_tax_code": "43230000A9201"
+        },
+        {
+            "description": "Food and Beverage - Sugar and Sugar Substitutes",
+            "name": "Food and Beverage - Sugar and Sugar Substitutes",
+            "product_tax_code": "50161900A0000"
+        },
+        {
+            "description": "Food and Beverage - Eggs and egg products",
+            "name": "Food and Beverage - Eggs and egg products",
+            "product_tax_code": "50131600A0000"
+        },
+        {
+            "description": "Food and Beverage - Coffee, coffee substitutes and tea",
+            "name": "Food and Beverage - Coffee, coffee substitutes and tea",
+            "product_tax_code": "50201700A0000"
+        },
+        {
+            "description": "Food and Beverage - Candy",
+            "name": "Food and Beverage - Candy",
+            "product_tax_code": "50161800A0000"
+        },
+        {
+            "description": "Food and Beverage - Butter, Margarine, Shortening and Cooking Oils",
+            "name": "Food and Beverage - Butter, Margarine, Shortening and Cooking Oils",
+            "product_tax_code": "50151500A0000"
+        },
+        {
+            "description": "Clothing - Facial shields",
+            "name": "Clothing - Facial shields",
+            "product_tax_code": "46181702A0001"
+        },
+        {
+            "description": "Clothing - Hard hats",
+            "name": "Clothing - Hard hats",
+            "product_tax_code": "46181701A0001"
+        },
+        {
+            "description": "Clothing - Cleanroom footwear",
+            "name": "Clothing - Cleanroom footwear",
+            "product_tax_code": "46181603A0001"
+        },
+        {
+            "description": "Clothing - Fire retardant footwear",
+            "name": "Clothing - Fire retardant footwear",
+            "product_tax_code": "46181601A0001"
+        },
+        {
+            "description": "Clothing - Protective pants",
+            "name": "Clothing - Protective pants",
+            "product_tax_code": "46181527A0001"
+        },
+        {
+            "description": "Clothing - Garters",
+            "name": "Clothing - Garters",
+            "product_tax_code": "53102509A0000"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered electronically (includes support services only - no updates/upgrades)",
+            "name": "Software maintenance and support - Optional, custom, electronic delivery (support services only)",
+            "product_tax_code": "81112200A2222"
+        },
+        {
+            "description": "Software maintenance and support - Mandatory maintenance and support charges for prewritten software including items delivered by load and leave",
+            "name": "Software maintenance and support - Mandatory, prewritten, load and leave delivery",
+            "product_tax_code": "81112200A1310"
+        },
+        {
+            "description": "Software maintenance and support - Mandatory maintenance and support charges for prewritten software including items delivered electronically",
+            "name": "Software maintenance and support - Mandatory, prewritten, electronic delivery",
+            "product_tax_code": "81112200A1210"
+        },
+        {
+            "description": "Electronic software documentation or user manuals - For prewritten software & delivered electronically",
+            "name": "Electronic software documentation or user manuals - Prewritten, electronic delivery",
+            "product_tax_code": "55111601A1200"
+        },
+        {
+            "description": "Clothing - Fur Ear muffs or scarves",
+            "name": "Clothing - Fur Ear muffs or scarves",
+            "product_tax_code": "53102502A0001"
+        },
+        {
+            "description": "Clothing - Safety glasses",
+            "name": "Clothing - Safety glasses",
+            "product_tax_code": "46181802A0001"
+        },
+        {
+            "description": "Software - Prewritten & delivered on tangible media",
+            "name": "Software - Prewritten, tangible media",
+            "product_tax_code": "43230000A1100"
+        },
+        {
+            "description": "Mainframe administration services\r\n",
+            "name": "Mainframe administration services\r\n",
+            "product_tax_code": "81111802A0000"
+        },
+        {
+            "description": "Co-location service",
+            "name": "Co-location service",
+            "product_tax_code": "81111814A0000"
+        },
+        {
+            "description": "Information management system for mine action IMSMA\r\n",
+            "name": "Information management system for mine action IMSMA\r\n",
+            "product_tax_code": "81111710A0000"
+        },
+        {
+            "description": "Local area network LAN maintenance or support",
+            "name": "Local area network LAN maintenance or support",
+            "product_tax_code": "81111803A0000"
+        },
+        {
+            "description": "Data center services",
+            "name": "Data center services",
+            "product_tax_code": "81112003A0000"
+        },
+        {
+            "description": "Wide area network WAN maintenance or support",
+            "name": "Wide area network WAN maintenance or support",
+            "product_tax_code": "81111804A0000"
+        },
+        {
+            "description": "Electronic data interchange EDI design\r\n",
+            "name": "Electronic data interchange EDI design\r\n",
+            "product_tax_code": "81111703A0000"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for prewritten software including items delivered on tangible media (includes support services only - no updates/upgrades)",
+            "name": "Software maintenance and support - Optional, prewritten, tangible media (support services only)",
+            "product_tax_code": "81112200A1122"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for prewritten software including items delivered on tangible media (includes software updates/upgrades)",
+            "name": "Software maintenance and support - Optional, prewritten, tangible media (incl. updates/upgrades)",
+            "product_tax_code": "81112200A1121"
+        },
+        {
+            "description": "Clothing - Safety sleeves",
+            "name": "Clothing - Safety sleeves",
+            "product_tax_code": "46181516A0001"
+        },
+        {
+            "description": "Clothing - Fire retardant apparel",
+            "name": "Clothing - Fire retardant apparel",
+            "product_tax_code": "46181508A0001"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for prewritten software including items delivered by load and leave (includes support services only - no updates/upgrades)",
+            "name": "Software maintenance and support - Optional, prewritten, load and leave delivery (support services only)",
+            "product_tax_code": "81112200A1322"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for prewritten software including items delivered by load and leave (includes software updates/upgrades)",
+            "name": "Software maintenance and support - Optional, prewritten, load and leave delivery (incl. updates/upgrades)",
+            "product_tax_code": "81112200A1321"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for prewritten software including items delivered electronically (includes support services only - no updates/upgrades)",
+            "name": "Software maintenance and support - Optional, prewritten, electronic delivery (support services only)",
+            "product_tax_code": "81112200A1222"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered on tangible media (includes support services only - no updates/upgrades)",
+            "name": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered on tangible media (includes support services only - no updates/upgrades)\r\n",
+            "product_tax_code": "81112200A2122"
+        },
+        {
+            "description": "Clothing - Protective ponchos",
+            "name": "Clothing - Protective ponchos",
+            "product_tax_code": "46181506A0001"
+        },
+        {
+            "description": "Clothing - Protective gloves",
+            "name": "Clothing - Protective gloves",
+            "product_tax_code": "46181504A0001"
+        },
+        {
+            "description": "Clothing - Synthetic Fur Vest",
+            "name": "Clothing - Synthetic Fur Vest",
+            "product_tax_code": "53103100A0002"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered on tangible media (includes software updates/upgrades)",
+            "name": "Software maintenance and support - Optional, custom, tangible media (incl. updates/upgrades)",
+            "product_tax_code": "81112200A2121"
+        },
+        {
+            "description": "Computer graphics service\r\n",
+            "name": "Computer graphics service\r\n",
+            "product_tax_code": "81111512A0000"
+        },
+        {
+            "description": "Proprietary or licensed systems maintenance or support",
+            "name": "Proprietary or licensed systems maintenance or support",
+            "product_tax_code": "81111805A0000"
+        },
+        {
+            "description": "Software - Prewritten & delivered electronically",
+            "name": "Software - Prewritten, electronic delivery",
+            "product_tax_code": "43230000A1200"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for prewritten software including items delivered electronically (includes software updates/upgrades)",
+            "name": "Software maintenance and support - Optional, prewritten, electronic delivery (incl. updates/upgrades)",
+            "product_tax_code": "81112200A1221"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered by load and leave (includes support services only - no updates/upgrades)",
+            "name": "Software maintenance and support - Optional, custom, load and leave delivery (support services only)",
+            "product_tax_code": "81112200A2322"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered by load and leave (includes software updates/upgrades)",
+            "name": "Software maintenance and support - Optional, custom, load and leave delivery (incl. updates/upgrades)",
+            "product_tax_code": "81112200A2321"
+        },
+        {
+            "description": "Software maintenance and support - Optional maintenance and support charges for custom software including items delivered electronically (includes software updates/upgrades)",
+            "name": "Software maintenance and support - Optional, custom, electronic delivery (incl. updates/upgrades)",
+            "product_tax_code": "81112200A2221"
+        },
+        {
+            "description": "Software maintenance and support - Mandatory maintenance and support charges for custom software including items delivered on tangible media",
+            "name": "Software maintenance and support - Mandatory, custom, tangible media",
+            "product_tax_code": "81112200A2110"
+        },
+        {
+            "description": "Software maintenance and support - Mandatory maintenance and support charges for custom software including items delivered electronically",
+            "name": "Software maintenance and support - Mandatory, custom, electronic delivery",
+            "product_tax_code": "81112200A2210"
+        },
+        {
+            "description": "Food and Beverage - Fish and seafood",
+            "name": "Food and Beverage - Fish and seafood",
+            "product_tax_code": "50121500A0000"
+        },
+        {
+            "description": "Food and Beverage - Ice cubes",
+            "name": "Food and Beverage - Ice cubes",
+            "product_tax_code": "50202302A0000"
+        },
+        {
+            "description": "Food and Beverage - Cooking Ingredients",
+            "name": "Food and Beverage - Cooking Ingredients",
+            "product_tax_code": "50181700A0000"
+        },
+        {
+            "description": "Food and Beverage - Cocoa and Cocoa products",
+            "name": "Food and Beverage - Cocoa and Cocoa products",
+            "product_tax_code": "50161511A0000"
+        },
+        {
+            "description": "Food and Beverage - Baby foods and formulas",
+            "name": "Food and Beverage - Baby foods and formulas",
+            "product_tax_code": "42231800A0000"
+        },
+        {
+            "description": "Clothing - Hazardous material protective footwear",
+            "name": "Clothing - Hazardous material protective footwear",
+            "product_tax_code": "46181602A0001"
+        },
+        {
+            "description": "Clothing - Welder gloves",
+            "name": "Clothing - Welder gloves",
+            "product_tax_code": "46181540A0001"
+        },
+        {
+            "description": "Clothing - Protective shirts",
+            "name": "Clothing - Protective shirts",
+            "product_tax_code": "46181526A0001"
+        },
+        {
+            "description": "Gift Cards",
+            "name": "Gift Cards",
+            "product_tax_code": "14111803A0001"
+        },
+        {
+            "description": "Clothing - Leg protectors",
+            "name": "Clothing - Leg protectors",
+            "product_tax_code": "46181520A0001"
+        },
+        {
+            "description": "Clothing - Protective coveralls",
+            "name": "Clothing - Protective coveralls",
+            "product_tax_code": "46181503A0001"
+        },
+        {
+            "description": "Internet or intranet client application development services\r\n",
+            "name": "Internet or intranet client application development services\r\n",
+            "product_tax_code": "81111509A0000"
+        },
+        {
+            "description": "Database design\r\n",
+            "name": "Database design\r\n",
+            "product_tax_code": "81111704A0000"
+        },
+        {
+            "description": "Computer programmers\r\n",
+            "name": "Computer programmers\r\n",
+            "product_tax_code": "81111600A0000"
+        },
+        {
+            "description": "Clothing - Synthetic Fur Hat",
+            "name": "Clothing - Synthetic Fur Hat",
+            "product_tax_code": "53102504A0002"
+        },
+        {
+            "description": "System or application programming management service\r\n",
+            "name": "System or application programming management service\r\n",
+            "product_tax_code": "81111511A0000"
+        },
+        {
+            "description": "Food and Beverage - Fruit",
+            "name": "Food and Beverage - Fruit",
+            "product_tax_code": "50300000A0000"
+        },
+        {
+            "description": "Food and Beverage - Vegetables",
+            "name": "Food and Beverage - Vegetables",
+            "product_tax_code": "50400000A0000"
+        },
+        {
+            "description": "Food and Beverage - Dried fruit, unsweetened",
+            "name": "Food and Beverage - Dried fruit, unsweetened",
+            "product_tax_code": "50320000A0000"
+        },
+        {
+            "description": "Food and Beverage - Snack Foods",
+            "name": "Food and Beverage - Snack Foods",
+            "product_tax_code": "50192100A0000"
+        },
+        {
+            "description": "Food and Beverage - Processed Nuts and Seeds",
+            "name": "Food and Beverage - Nuts and seeds that have been processed or treated by salting, spicing, smoking, roasting, or other means",
+            "product_tax_code": "50101716A0001"
+        },
+        {
+            "description": "Food and Beverage - Non-Alcoholic Beer, Wine",
+            "name": "Food and Beverage - Non-Alcoholic Beer, Wine",
+            "product_tax_code": "50202300A0001"
+        },
+        {
+            "description": "Food and Beverage - Ice Cream, sold in container less than one pint",
+            "name": "Food and Beverage - Ice Cream, sold in container less than one pint",
+            "product_tax_code": "50192304A0000"
+        },
+        {
+            "description": "Food and Beverage - Alcoholic beverages - Spirits",
+            "name": "Food and Beverage - Alcoholic beverages - Spirits",
+            "product_tax_code": "50202206A0000"
+        },
+        {
+            "description": "Food and Beverage - Wine",
+            "name": "Food and Beverage - Alcoholic beverages - Wine",
+            "product_tax_code": "50202203A0000"
+        },
+        {
+            "description": "Electronic content bundle - Delivered electronically with less than permanent rights of usage and streamed",
+            "name": "Electronic content bundle - Delivered electronically with less than permanent rights of usage and streamed",
+            "product_tax_code": "55111500A9220"
+        },
+        {
+            "description": "Clothing - Welding masks",
+            "name": "Clothing - Welding masks",
+            "product_tax_code": "46181703A0001"
+        },
+        {
+            "description": "Clothing - Protective wear dispenser",
+            "name": "Clothing - Protective wear dispenser",
+            "product_tax_code": "46181553A0001"
+        },
+        {
+            "description": "Clothing - Anti cut gloves",
+            "name": "Clothing - Anti cut gloves",
+            "product_tax_code": "46181536A0001"
+        },
+        {
+            "description": "Clothing - Reflective apparel or accessories",
+            "name": "Clothing - Reflective apparel or accessories",
+            "product_tax_code": "46181531A0001"
+        },
+        {
+            "description": "Clothing - Heat resistant clothing",
+            "name": "Clothing - Heat resistant clothing",
+            "product_tax_code": "46181518A0001"
+        },
+        {
+            "description": "Clothing - Cleanroom apparel",
+            "name": "Clothing - Cleanroom apparel",
+            "product_tax_code": "46181512A0001"
+        },
+        {
+            "description": "Clothing - Hazardous material protective apparel",
+            "name": "Clothing - Hazardous material protective apparel",
+            "product_tax_code": "46181509A0001"
+        },
+        {
+            "description": "Clothing - Safety vests",
+            "name": "Clothing - Safety vests",
+            "product_tax_code": "46181507A0001"
+        },
+        {
+            "description": "Clothing - Protective knee pads",
+            "name": "Clothing - Protective knee pads",
+            "product_tax_code": "46181505A0001"
+        },
+        {
+            "description": "Clothing - Bullet proof vests",
+            "name": "Clothing - Bullet proof vests",
+            "product_tax_code": "46181502A0001"
+        },
+        {
+            "description": "Clothing - Vest or waistcoats",
+            "name": "Clothing - Vest or waistcoats",
+            "product_tax_code": "53103100A0000"
+        },
+        {
+            "description": "Clothing - Prisoner uniform",
+            "name": "Clothing - Prisoner uniform",
+            "product_tax_code": "53102716A0000"
+        },
+        {
+            "description": "Clothing - Paramedic uniforms",
+            "name": "Clothing - Paramedic uniforms",
+            "product_tax_code": "53102712A0000"
+        },
+        {
+            "description": "Clothing - Ambulance officers uniforms",
+            "name": "Clothing - Ambulance officers uniforms",
+            "product_tax_code": "53102709A0000"
+        },
+        {
+            "description": "Clothing - Doctors coat",
+            "name": "Clothing - Doctors coat",
+            "product_tax_code": "53102707A0000"
+        },
+        {
+            "description": "Clothing - Sweat bands",
+            "name": "Clothing - Sweat bands",
+            "product_tax_code": "53102506A0000"
+        },
+        {
+            "description": "Clothing - Helmet parts or accessories",
+            "name": "Clothing - Helmet parts or accessories",
+            "product_tax_code": "46181706A0001"
+        },
+        {
+            "description": "Clothing - Fur Vest",
+            "name": "Clothing - Fur Vest",
+            "product_tax_code": "53103100A0001"
+        },
+        {
+            "description": "Clothing - Fur Gloves",
+            "name": "Clothing - Fur Gloves",
+            "product_tax_code": "53102503A0001"
+        },
+        {
+            "description": "Clothing - Motorcycle helmets",
+            "name": "Clothing - Motorcycle helmets",
+            "product_tax_code": "46181705A0001"
+        },
+        {
+            "description": "Operating system programming services\r\n",
+            "name": "Operating system programming services\r\n",
+            "product_tax_code": "81111505A0000"
+        },
+        {
+            "description": "Local area network communications design\r\n",
+            "name": "Local area network communications design\r\n",
+            "product_tax_code": "81111702A0000"
+        },
+        {
+            "description": "Clothing - Eye shields",
+            "name": "Clothing - Eye shields",
+            "product_tax_code": "46181803A0001"
+        },
+        {
+            "description": "Clothing - Welders helmet",
+            "name": "Clothing - Welders helmet",
+            "product_tax_code": "46181711A0001"
+        },
+        {
+            "description": "Clothing - Footwear covers",
+            "name": "Clothing - Footwear covers",
+            "product_tax_code": "46181606A0001"
+        },
+        {
+            "description": "Clothing - Cooling vest",
+            "name": "Clothing - Cooling vest",
+            "product_tax_code": "46181554A0001"
+        },
+        {
+            "description": "Clothing - Protective mesh jacket",
+            "name": "Clothing - Protective mesh jacket",
+            "product_tax_code": "46181551A0001"
+        },
+        {
+            "description": "Clothing - Protective scarf",
+            "name": "Clothing - Protective scarf",
+            "product_tax_code": "46181550A0001"
+        },
+        {
+            "description": "Clothing - Neck gaitor",
+            "name": "Clothing - Neck gaitor",
+            "product_tax_code": "46181549A0001"
+        },
+        {
+            "description": "Clothing - Welder bib",
+            "name": "Clothing - Welder bib",
+            "product_tax_code": "46181548A0001"
+        },
+        {
+            "description": "Clothing - Waterproof cap cover",
+            "name": "Clothing - Waterproof cap cover",
+            "product_tax_code": "46181547A0001"
+        },
+        {
+            "description": "Clothing - Waterproof suit",
+            "name": "Clothing - Waterproof suit",
+            "product_tax_code": "46181545A0001"
+        },
+        {
+            "description": "Clothing - Waterproof trousers or pants",
+            "name": "Clothing - Waterproof trousers or pants",
+            "product_tax_code": "46181544A0001"
+        },
+        {
+            "description": "Clothing - Protective mittens",
+            "name": "Clothing - Protective mittens",
+            "product_tax_code": "46181542A0001"
+        },
+        {
+            "description": "Clothing - Chemical resistant gloves",
+            "name": "Clothing - Chemical resistant gloves",
+            "product_tax_code": "46181541A0001"
+        },
+        {
+            "description": "Clothing - Anti vibratory gloves",
+            "name": "Clothing - Anti vibratory gloves",
+            "product_tax_code": "46181539A0001"
+        },
+        {
+            "description": "Clothing - Thermal gloves",
+            "name": "Clothing - Thermal gloves",
+            "product_tax_code": "46181538A0001"
+        },
+        {
+            "description": "Clothing - Insulated gloves",
+            "name": "Clothing - Insulated gloves",
+            "product_tax_code": "46181537A0001"
+        },
+        {
+            "description": "Clothing - Protective socks or hosiery",
+            "name": "Clothing - Protective socks or hosiery",
+            "product_tax_code": "46181535A0001"
+        },
+        {
+            "description": "Clothing - Protective wristbands",
+            "name": "Clothing - Protective wristbands",
+            "product_tax_code": "46181534A0001"
+        },
+        {
+            "description": "Clothing - Protective coats",
+            "name": "Clothing - Protective coats",
+            "product_tax_code": "46181533A0001"
+        },
+        {
+            "description": "Clothing - Insulated clothing for cold environments",
+            "name": "Clothing - Insulated clothing for cold environments",
+            "product_tax_code": "46181529A0001"
+        },
+        {
+            "description": "Clothing - Protective frock",
+            "name": "Clothing - Protective frock",
+            "product_tax_code": "46181528A0001"
+        },
+        {
+            "description": "Clothing - Safety hoods",
+            "name": "Clothing - Safety hoods",
+            "product_tax_code": "46181522A0001"
+        },
+        {
+            "description": "Clothing - Insulated or flotation suits",
+            "name": "Clothing - Insulated or flotation suits",
+            "product_tax_code": "46181517A0001"
+        },
+        {
+            "description": "Clothing - Elbow protectors",
+            "name": "Clothing - Elbow protectors",
+            "product_tax_code": "46181514A0001"
+        },
+        {
+            "description": "Clothing - Protective aprons",
+            "name": "Clothing - Protective aprons",
+            "product_tax_code": "46181501A0001"
+        },
+        {
+            "description": "Clothing - Shoes",
+            "name": "Clothing - Shoes",
+            "product_tax_code": "53111600A0000"
+        },
+        {
+            "description": "Clothing - Athletic wear",
+            "name": "Clothing - Athletic wear",
+            "product_tax_code": "53102900A0000"
+        },
+        {
+            "description": "Clothing - Folkloric clothing",
+            "name": "Clothing - Folkloric clothing",
+            "product_tax_code": "53102200A0000"
+        },
+        {
+            "description": "Clothing - Overalls or coveralls",
+            "name": "Clothing - Overalls or coveralls",
+            "product_tax_code": "53102100A0000"
+        },
+        {
+            "description": "Clothing - Dresses or skirts or saris or kimonos",
+            "name": "Clothing - Dresses or skirts or saris or kimonos",
+            "product_tax_code": "53102000A0000"
+        },
+        {
+            "description": "Clothing - Suits",
+            "name": "Clothing - Suits",
+            "product_tax_code": "53101900A0000"
+        },
+        {
+            "description": "Clothing - Sport uniform",
+            "name": "Clothing - Sport uniform",
+            "product_tax_code": "53102717A0000"
+        },
+        {
+            "description": "Clothing - Judicial robe",
+            "name": "Clothing - Judicial robe",
+            "product_tax_code": "53102714A0000"
+        },
+        {
+            "description": "Clothing - Ushers uniforms",
+            "name": "Clothing - Ushers uniforms",
+            "product_tax_code": "53102713A0000"
+        },
+        {
+            "description": "Clothing - Nurses uniforms",
+            "name": "Clothing - Nurses uniforms",
+            "product_tax_code": "53102708A0000"
+        },
+        {
+            "description": "Clothing - School uniforms",
+            "name": "Clothing - School uniforms",
+            "product_tax_code": "53102705A0000"
+        },
+        {
+            "description": "Clothing - Institutional food preparation or service attire",
+            "name": "Clothing - Institutional food preparation or service attire",
+            "product_tax_code": "53102704A0000"
+        },
+        {
+            "description": "Clothing - Police uniforms",
+            "name": "Clothing - Police uniforms",
+            "product_tax_code": "53102703A0000"
+        },
+        {
+            "description": "Clothing - Customs uniforms",
+            "name": "Clothing - Customs uniforms",
+            "product_tax_code": "53102702A0000"
+        },
+        {
+            "description": "Clothing - Bandannas",
+            "name": "Clothing - Bandannas",
+            "product_tax_code": "53102511A0000"
+        },
+        {
+            "description": "Clothing - Armbands",
+            "name": "Clothing - Armbands",
+            "product_tax_code": "53102508A0000"
+        },
+        {
+            "description": "Clothing - Caps",
+            "name": "Clothing - Caps",
+            "product_tax_code": "53102516A0000"
+        },
+        {
+            "description": "Clothing - Protective finger cots",
+            "name": "Clothing - Protective finger cots",
+            "product_tax_code": "46181530A0001"
+        },
+        {
+            "description": "Application programming services\r\n",
+            "name": "Application programming services\r\n",
+            "product_tax_code": "81111504A0000"
+        },
+        {
+            "description": "Application implementation services\r\n",
+            "name": "Application implementation services\r\n",
+            "product_tax_code": "81111508A0000"
+        },
+        {
+            "description": "Clothing - Synthetic Fur Ear muffs or scarves",
+            "name": "Clothing - Synthetic Fur Ear muffs or scarves",
+            "product_tax_code": "53102502A0002"
+        },
+        {
+            "description": "Clothing - Fur Poncho or Cape",
+            "name": "Clothing - Fur Poncho or Cape",
+            "product_tax_code": "53101806A0001"
+        },
+        {
+            "description": "Food and Beverage - Vitamins and Supplements - labeled with nutritional facts",
+            "name": "Food and Beverage - Vitamins and Supplements - labeled with nutritional facts",
+            "product_tax_code": "50501500A0001"
+        },
+        {
+            "description": "Food and Beverage - Nuts and seeds",
+            "name": "Food and Beverage - Nuts and seeds",
+            "product_tax_code": "50101716A0000"
+        },
+        {
+            "description": "Food and Beverage - Milk Substitutes",
+            "name": "Food and Beverage - Milk Substitutes",
+            "product_tax_code": "50151515A9000"
+        },
+        {
+            "description": "Food and Beverage - Milk and milk products",
+            "name": "Food and Beverage - Milk and milk products",
+            "product_tax_code": "50131700A0000"
+        },
+        {
+            "description": "Food and Beverage - Cheese",
+            "name": "Food and Beverage - Cheese",
+            "product_tax_code": "50131800A0000"
+        },
+        {
+            "description": "Clothing - Sandals",
+            "name": "Clothing - Sandals",
+            "product_tax_code": "53111800A0000"
+        },
+        {
+            "description": "Clothing - Pajamas or nightshirts or robes",
+            "name": "Clothing - Pajamas or nightshirts or robes",
+            "product_tax_code": "53102600A0000"
+        },
+        {
+            "description": "Clothing - Sweaters",
+            "name": "Clothing - Sweaters",
+            "product_tax_code": "53101700A0000"
+        },
+        {
+            "description": "Clothing - Slacks or trousers or shorts",
+            "name": "Clothing - Slacks or trousers or shorts",
+            "product_tax_code": "53101500A0000"
+        },
+        {
+            "description": "Clothing - Firefighter uniform",
+            "name": "Clothing - Firefighter uniform",
+            "product_tax_code": "53102718A0000"
+        },
+        {
+            "description": "Clothing - Salon smocks",
+            "name": "Clothing - Salon smocks",
+            "product_tax_code": "53102711A0000"
+        },
+        {
+            "description": "Clothing - Military uniforms",
+            "name": "Clothing - Military uniforms",
+            "product_tax_code": "53102701A0000"
+        },
+        {
+            "description": "Clothing - Heel pads",
+            "name": "Clothing - Heel pads",
+            "product_tax_code": "53112003A0000"
+        },
+        {
+            "description": "Clothing - Shoelaces",
+            "name": "Clothing - Shoelaces",
+            "product_tax_code": "53112002A0000"
+        },
+        {
+            "description": "Clothing - Infant swaddles or buntings or receiving blankets",
+            "name": "Clothing - Infant swaddles or buntings or receiving blankets",
+            "product_tax_code": "53102608A0000"
+        },
+        {
+            "description": "Clothing - Hats",
+            "name": "Clothing - Hats",
+            "product_tax_code": "53102503A0000"
+        },
+        {
+            "description": "Clothing - Ties or scarves or mufflers",
+            "name": "Clothing - Ties or scarves or mufflers",
+            "product_tax_code": "53102502A0000"
+        },
+        {
+            "description": "Clothing - Belts or suspenders",
+            "name": "Clothing - Belts or suspenders",
+            "product_tax_code": "53102501A0000"
+        },
+        {
+            "description": "Clothing - Tights",
+            "name": "Clothing - Tights",
+            "product_tax_code": "53102404A0000"
+        },
+        {
+            "description": "Clothing - Disposable youth training pants",
+            "name": "Clothing - Disposable youth training pants",
+            "product_tax_code": "53102311A0000"
+        },
+        {
+            "description": "Clothing - Undershirts",
+            "name": "Clothing - Undershirts",
+            "product_tax_code": "53102301A0000"
+        },
+        {
+            "description": "Clothing - Insulated cold weather shoe",
+            "name": "Clothing - Insulated cold weather shoe",
+            "product_tax_code": "46181610A0000"
+        },
+        {
+            "description": "Food and Beverage - Grains, Rice, Cereal",
+            "name": "Food and Beverage - Grains, Rice, Cereal",
+            "product_tax_code": "50221200A0000"
+        },
+        {
+            "description": "Clothing - Shirts",
+            "name": "Clothing - Shirts",
+            "product_tax_code": "53101600A0000"
+        },
+        {
+            "description": "Clothing - Safety boots",
+            "name": "Clothing - Safety boots",
+            "product_tax_code": "46181604A0000"
+        },
+        {
+            "description": "Clothing - Shin guards",
+            "name": "Clothing - Shin guards",
+            "product_tax_code": "49161525A0001"
+        },
+        {
+            "description": "Clothing - Athletic supporter",
+            "name": "Clothing - Athletic supporter",
+            "product_tax_code": "49161517A0001"
+        },
+        {
+            "description": "Clothing - Cleated or spiked shoes",
+            "name": "Clothing - Cleated or spiked shoes",
+            "product_tax_code": "53111900A0002"
+        },
+        {
+            "description": "Wide area network communications design\r\n",
+            "name": "Wide area network communications design\r\n\r\n",
+            "product_tax_code": "81111701A0000"
+        },
+        {
+            "description": "Systems integration design\r\n",
+            "name": "Systems integration design\r\n",
+            "product_tax_code": "81111503A0000"
+        },
+        {
+            "description": "Clothing - Bridal Gown",
+            "name": "Clothing - Bridal Gown",
+            "product_tax_code": "53101801A0004"
+        },
+        {
+            "description": "Clothing - Waterproof cap",
+            "name": "Clothing - Waterproof cap",
+            "product_tax_code": "46181546A0000"
+        },
+        {
+            "description": "Food and Beverage - Yogurt",
+            "name": "Food and Beverage - Yogurt",
+            "product_tax_code": "50131800A0001"
+        },
+        {
+            "description": "Food and Beverage - Nut Butters",
+            "name": "Food and Beverage - Nut Butters",
+            "product_tax_code": "50480000A9000"
+        },
+        {
+            "description": "Food and Beverage - Jams and Jellies",
+            "name": "Food and Beverage - Jams and Jellies",
+            "product_tax_code": "50192401A0000"
+        },
+        {
+            "description": "Food and Beverage - Honey, Maple Syrup",
+            "name": "Food and Beverage - Honey, Maple Syrup",
+            "product_tax_code": "50161509A0000"
+        },
+        {
+            "description": "Food and Beverage - Foods for Immediate Consumption",
+            "name": "Food and Beverage - Foods for Immediate Consumption",
+            "product_tax_code": "90100000A0001"
+        },
+        {
+            "description": "Food and Beverage - Bread and Flour Products",
+            "name": "Food and Beverage - Bread and Flour Products",
+            "product_tax_code": "50180000A0000"
+        },
+        {
+            "description": "Clothing - Overshoes",
+            "name": "Clothing - Overshoes",
+            "product_tax_code": "53112000A0000"
+        },
+        {
+            "description": "Clothing - Athletic footwear",
+            "name": "Clothing - Athletic footwear",
+            "product_tax_code": "53111900A0000"
+        },
+        {
+            "description": "Clothing - Slippers",
+            "name": "Clothing - Slippers",
+            "product_tax_code": "53111700A0000"
+        },
+        {
+            "description": "Clothing - Boots",
+            "name": "Clothing - Boots",
+            "product_tax_code": "53111500A0000"
+        },
+        {
+            "description": "Clothing - T-Shirts",
+            "name": "Clothing - T-Shirts",
+            "product_tax_code": "53103000A0000"
+        },
+        {
+            "description": "Clothing - Swimwear",
+            "name": "Clothing - Swimwear",
+            "product_tax_code": "53102800A0000"
+        },
+        {
+            "description": "Clothing - Coats or jackets",
+            "name": "Clothing - Coats or jackets",
+            "product_tax_code": "53101800A0000"
+        },
+        {
+            "description": "Clothing - Prison officer uniform",
+            "name": "Clothing - Prison officer uniform",
+            "product_tax_code": "53102715A0000"
+        },
+        {
+            "description": "Clothing - Corporate uniforms",
+            "name": "Clothing - Corporate uniforms",
+            "product_tax_code": "53102710A0000"
+        },
+        {
+            "description": "Clothing - Security uniforms",
+            "name": "Clothing - Security uniforms",
+            "product_tax_code": "53102706A0000"
+        },
+        {
+            "description": "Clothing - Chevrons",
+            "name": "Clothing - Chevrons",
+            "product_tax_code": "53102518A0000"
+        },
+        {
+            "description": "Clothing - Disposable work coat",
+            "name": "Clothing - Disposable work coat",
+            "product_tax_code": "53103201A0000"
+        },
+        {
+            "description": "Clothing - Bath robes",
+            "name": "Clothing - Bath robes",
+            "product_tax_code": "53102606A0000"
+        },
+        {
+            "description": "Clothing - Bib",
+            "name": "Clothing - Bib",
+            "product_tax_code": "53102521A0000"
+        },
+        {
+            "description": "Clothing - Gloves or mittens",
+            "name": "Clothing - Gloves or mittens",
+            "product_tax_code": "53102504A0000"
+        },
+        {
+            "description": "Clothing - Mouth guards",
+            "name": "Clothing - Mouth guards",
+            "product_tax_code": "42152402A0001"
+        },
+        {
+            "description": "Clothing - Boxing gloves",
+            "name": "Clothing - Boxing gloves",
+            "product_tax_code": "49171600A0000"
+        },
+        {
+            "description": "Clothing - Golf shoes",
+            "name": "Clothing - Golf shoes",
+            "product_tax_code": "53111900A0004"
+        },
+        {
+            "description": "Clothing - Bowling shoes",
+            "name": "Clothing - Bowling shoes",
+            "product_tax_code": "53111900A0003"
+        },
+        {
+            "description": "Internet or intranet server application development services\r\n",
+            "name": "Internet or intranet server application development services\r\n",
+            "product_tax_code": "81111510A0000"
+        },
+        {
+            "description": "Data conversion service\r\n",
+            "name": "Data conversion service\r\n",
+            "product_tax_code": "81112010A0000"
+        },
+        {
+            "description": "Client or server programming services\r\n",
+            "name": "Client or server programming services\r\n",
+            "product_tax_code": "81111506A0000"
+        },
+        {
+            "description": "Clothing - Ballet or tap shoes",
+            "name": "Clothing - Ballet or tap shoes",
+            "product_tax_code": "53111900A0001"
+        },
+        {
+            "description": "Clothing - Golf gloves",
+            "name": "Clothing - Golf gloves",
+            "product_tax_code": "49211606A0000"
+        },
+        {
+            "description": "Hardware as a service (HaaS)",
+            "name": "Hardware as a service (HaaS)",
+            "product_tax_code": "81161900A0000"
+        },
+        {
+            "description": "Cloud-based platform as a service (PaaS) - Personal Use",
+            "name": "Cloud-based platform as a service (PaaS) - Personal Use",
+            "product_tax_code": "81162100A0000"
+        },
+        {
+            "description": "Clothing - Panty hose",
+            "name": "Clothing - Panty hose",
+            "product_tax_code": "53102403A0000"
+        },
+        {
+            "description": "Clothing - Brassieres",
+            "name": "Clothing - Brassieres",
+            "product_tax_code": "53102304A0000"
+        },
+        {
+            "description": "Clothing - Protective sandals",
+            "name": "Clothing - Protective sandals",
+            "product_tax_code": "46181608A0000"
+        },
+        {
+            "description": "Clothing - Tuxedo or Formalwear",
+            "name": "Clothing - Tuxedo or Formalwear",
+            "product_tax_code": "53101801A0001"
+        },
+        {
+            "description": "Clothing - Lab coats",
+            "name": "Clothing - Lab coats",
+            "product_tax_code": "46181532A0000"
+        },
+        {
+            "description": "Systems planning services\r\n",
+            "name": "Systems planning services\r\n",
+            "product_tax_code": "81111707A0000"
+        },
+        {
+            "description": "Food and Beverage - Vitamins and Supplements",
+            "name": "Food and Beverage - Vitamins and Supplements - labeled with supplement facts",
+            "product_tax_code": "50501500A0000"
+        },
+        {
+            "description": "Food and Beverage - Jello and pudding mixes",
+            "name": "Food and Beverage - Jello and pudding mixes",
+            "product_tax_code": "50192404A0000"
+        },
+        {
+            "description": "Food and Beverage - Cooking spices",
+            "name": "Food and Beverage - Cooking spices",
+            "product_tax_code": "50171500A0000"
+        },
+        {
+            "description": "Food and Beverage - Alcoholic beverages - Beer/Malt Beverages",
+            "name": "Food and Beverage - Alcoholic beverages - Beer/Malt Beverages",
+            "product_tax_code": "50202201A0000"
+        },
+        {
+            "description": "Food and Beverage - Ice Cream, packaged",
+            "name": "Food and Beverage - Ice Cream, packaged",
+            "product_tax_code": "50192303A0000"
+        },
+        {
+            "description": "Electronic content bundle - Delivered electronically with permanent rights of usage and streamed",
+            "name": "Electronic content bundle - Delivered electronically with permanent rights of usage and streamed",
+            "product_tax_code": "55111500A9210"
+        },
+        {
+            "description": "Clothing - Roller skates or roller blades",
+            "name": "Clothing - Roller skates or roller blades",
+            "product_tax_code": "49221509A0000"
+        },
+        {
+            "description": "Clothing - Ice Skates",
+            "name": "Clothing - Ice Skates",
+            "product_tax_code": "49151602A0000"
+        },
+        {
+            "description": "Clothing - Life vests or preservers ",
+            "name": "Clothing - Life vests or preservers ",
+            "product_tax_code": "46161604A0000"
+        },
+        {
+            "description": "Clothing - Swim goggles",
+            "name": "Clothing - Swim goggles",
+            "product_tax_code": "49141606A0000"
+        },
+        {
+            "description": "Clothing - Bowling gloves",
+            "name": "Clothing - Bowling gloves",
+            "product_tax_code": "49211606A0002"
+        },
+        {
+            "description": "Fire Extinguishers",
+            "name": "Fire Extinguishers",
+            "product_tax_code": "46191601A0000"
+        },
+        {
+            "description": "Carbon Monoxide Detectors",
+            "name": "Carbon Monoxide Detectors",
+            "product_tax_code": "46191509A0001"
+        },
+        {
+            "description": "Ladder used for home emergency evacuation.",
+            "name": "Emergency/rescue ladder",
+            "product_tax_code": "30191501A0001"
+        },
+        {
+            "description": "Candles to be used a light source.",
+            "name": "Candles",
+            "product_tax_code": "39112604A0001"
+        },
+        {
+            "description": "Non-electric water container to store water for emergency usage.",
+            "name": "Water storage container",
+            "product_tax_code": "24111810A0001"
+        },
+        {
+            "description": "Duct Tape",
+            "name": "Duct Tape",
+            "product_tax_code": "31201501A0000"
+        },
+        {
+            "description": "Gas-powered chainsaw.",
+            "name": "Garden chainsaw",
+            "product_tax_code": "27112038A0000"
+        },
+        {
+            "description": "Chainsaw accessories include chains, lubricants, motor oil, chain sharpeners, bars, wrenches, carrying cases, repair parts, safety apparel.",
+            "name": "Chainsaw accessories",
+            "product_tax_code": "27112038A0001"
+        },
+        {
+            "description": "Shower curtain/liner used to keep water from escaping a showering area.",
+            "name": "Shower Curtain or Liner",
+            "product_tax_code": "30181607A0000"
+        },
+        {
+            "description": "Dish towels used for kitchenware drying.",
+            "name": "Dish towels",
+            "product_tax_code": "52121601A0000"
+        },
+        {
+            "description": "A bumper/liner that borders the interior walls/slats of the crib to help protect the baby.",
+            "name": "Crib bumpers/liners",
+            "product_tax_code": "56101804A0001"
+        },
+        {
+            "description": "A small mat/rug used to cover portion of bathroom floor.",
+            "name": "Bath Mats/rugs",
+            "product_tax_code": "52101507A0000"
+        },
+        {
+            "description": "A handheld computer that is capable of plotting graphs, solving simultaneous equations, and performing other tasks with variables.",
+            "name": "Graphing Calculators",
+            "product_tax_code": "44101808A0001"
+        },
+        {
+            "description": "Portable locks used by students in a school setting to prevent use, theft, vandalism or harm.",
+            "name": "Padlocks - Student",
+            "product_tax_code": "46171501A0001"
+        },
+        {
+            "description": "Domestic clothes washing appliances carrying Energy Star rating.",
+            "name": "Clothes Washing Machine - Energy Star",
+            "product_tax_code": "52141601A0000"
+        },
+        {
+            "description": "WaterSense labeled showerheads.",
+            "name": "Showerheads - WaterSense",
+            "product_tax_code": "30181801A0000"
+        },
+        {
+            "description": "Domestic dish washing appliances carrying Energy Star rating.",
+            "name": "Dishwashers - Energy Star",
+            "product_tax_code": "52141505A0000"
+        },
+        {
+            "description": "WaterSense labeled sprinkler body is the exterior shell that connects to the irrigation system piping and houses the spray nozzle that applies water on the landscape.",
+            "name": "Spray Water Sprinkler Bodies - WaterSense",
+            "product_tax_code": "21101803A0001"
+        },
+        {
+            "description": "Ropes and Cords",
+            "name": "Ropes and Cords",
+            "product_tax_code": "31151500A0000"
+        },
+        {
+            "description": "Light emitting diode (LED) bulbs carrying an Energy Star rating.",
+            "name": "LED Bulbs - Energy Star",
+            "product_tax_code": "39101628A0001"
+        },
+        {
+            "description": "WaterSense labeled bathroom sink faucets and accessories.",
+            "name": "Bathroom Faucets - WaterSense",
+            "product_tax_code": "30181702A0001"
+        },
+        {
+            "description": "Cables with industry standard connection and termination configurations used to connect various peripherals and equipment to computers.",
+            "name": "Computer Cables",
+            "product_tax_code": "43202222A0001"
+        },
+        {
+            "description": "Canned software delivered electronically that is used for non-recreational purposes, such as Antivirus, Database, Educational, Financial, Word processing, etc.",
+            "name": "Software - Prewritten, Electronic delivery - Non-recreational",
+            "product_tax_code": "43230000A1102"
+        },
+        {
+            "description": "Clothing - Baseball batting gloves",
+            "name": "Clothing - Baseball batting gloves",
+            "product_tax_code": "49211606A0001"
+        },
+        {
+            "description": "Cloud-based platform as a service (PaaS) - Business Use",
+            "name": "Cloud-based platform as a service (PaaS) - Business Use",
+            "product_tax_code": "81162100A9000"
+        },
+        {
+            "description": "Cloud-based Infrastructure as a service (IaaS) - Personal Use",
+            "name": "Cloud-based infrastructure as a service (IaaS) - Personal Use",
+            "product_tax_code": "81162200A0000"
+        },
+        {
+            "description": "Clothing - Costume Mask",
+            "name": "Clothing - Costume Mask",
+            "product_tax_code": "60122800A0000"
+        },
+        {
+            "description": "Clothing - Ski boots",
+            "name": "Clothing - Ski boots",
+            "product_tax_code": "53111900A0005"
+        },
+        {
+            "description": "Personal computer PC application design\r\n",
+            "name": "Personal computer PC application design\r\n",
+            "product_tax_code": "81111502A0000"
+        },
+        {
+            "description": "Network planning services\r\n",
+            "name": "Network planning services\r\n",
+            "product_tax_code": "81111706A0000"
+        },
+        {
+            "description": "ERP or database applications programming services\r\n",
+            "name": "ERP or database applications programming services\r\n",
+            "product_tax_code": "81111507A0000"
+        },
+        {
+            "description": "Content or data classification services\r\n",
+            "name": "Content or data classification services\r\n",
+            "product_tax_code": "81112009A0000"
+        },
+        {
+            "description": "Clothing - Prom Dress",
+            "name": "Clothing - Prom Dress",
+            "product_tax_code": "53101801A0003"
+        },
+        {
+            "description": "Clothing - Formal Dress",
+            "name": "Clothing - Formal Dress",
+            "product_tax_code": "53101801A0002"
+        },
+        {
+            "description": "Clothing - Handkerchiefs",
+            "name": "Clothing - Handkerchiefs",
+            "product_tax_code": "53102512A0000"
+        },
+        {
+            "description": "Clothing - Protective lens",
+            "name": "Clothing - Protective lens",
+            "product_tax_code": "46181811A0001"
+        },
+        {
+            "description": "Clothing - Body shaping garments",
+            "name": "Clothing - Body shaping garments",
+            "product_tax_code": "53102307A0000"
+        },
+        {
+            "description": "Clothing - Underpants",
+            "name": "Clothing - Underpants",
+            "product_tax_code": "53102303A0000"
+        },
+        {
+            "description": "Clothing - Waterproof boot",
+            "name": "Clothing - Waterproof boot",
+            "product_tax_code": "46181611A0000"
+        },
+        {
+            "description": "Electronic software documentation or user manuals - For prewritten software & delivered by load and leave",
+            "name": "Electronic software documentation or user manuals - Prewritten, load and leave delivery",
+            "product_tax_code": "55111601A1300"
+        },
+        {
+            "description": "Electronic software documentation or user manuals - For custom software & delivered on tangible media",
+            "name": "Electronic software documentation or user manuals - Custom, tangible media",
+            "product_tax_code": "55111601A2100"
+        },
+        {
+            "description": "Electronic software documentation or user manuals - For custom software & delivered by load and leave",
+            "name": "Electronic software documentation or user manuals - Custom, load and leave delivery",
+            "product_tax_code": "55111601A2300"
+        },
+        {
+            "description": "Electronic software documentation or user manuals - For custom software & delivered electronically",
+            "name": "Electronic software documentation or user manuals - Custom, electronic delivery",
+            "product_tax_code": "55111601A2200"
+        },
+        {
+            "description": "Electronic publications and music - Streamed",
+            "name": "Electronic publications and music - Streamed",
+            "product_tax_code": "55111500A1500"
+        },
+        {
+            "description": "Electronic publications and music - Delivered electronically with permanent rights of usage",
+            "name": "Electronic publications and music - Delivered electronically with permanent rights of usage",
+            "product_tax_code": "55111500A1210"
+        },
+        {
+            "description": "Electronic publications and music - Delivered electronically with less than permanent rights of usage",
+            "name": "Electronic publications and music - Delivered electronically with less than permanent rights of usage",
+            "product_tax_code": "55111500A1220"
+        },
+        {
+            "description": "Software - Custom & delivered on tangible media",
+            "name": "Software - Custom, tangible media",
+            "product_tax_code": "43230000A2100"
+        },
+        {
+            "description": "Software - Prewritten & delivered by digital keycode printed on tangible media",
+            "name": "Software - Prewritten, delivered by digital keycode printed on tangible media",
+            "product_tax_code": "43230000A1400"
+        },
+        {
+            "description": "Software - Prewritten & delivered by load and leave",
+            "name": "Software - Prewritten, load and leave delivery",
+            "product_tax_code": "43230000A1300"
+        },
+        {
+            "description": "Internet cloud storage service\r\n",
+            "name": "Internet cloud storage service\r\n",
+            "product_tax_code": "81111513A0000"
+        },
+        {
+            "description": "Computer or network or internet security\r\n",
+            "name": "Computer or network or internet security\r\n",
+            "product_tax_code": "81111801A0000"
+        },
+        {
+            "description": "Document scanning service\r\n",
+            "name": "Document scanning service\r\n",
+            "product_tax_code": "81112005A0000"
+        },
+        {
+            "description": "Cloud-based software as a service (SaaS) - Business Use",
+            "name": "Cloud-based software as a service (SaaS) - Business Use",
+            "product_tax_code": "81162000A9000"
+        },
+        {
+            "description": "Demining geographical or geospatial information system GIS\r\n",
+            "name": "Demining geographical or geospatial information system GIS\r\n",
+            "product_tax_code": "81111709A0000"
+        },
+        {
+            "description": "Content or data standardization services\r\n",
+            "name": "Content or data standardization services\r\n",
+            "product_tax_code": "81112007A0000"
+        },
+        {
+            "description": "Data processing or preparation services\r\n",
+            "name": "Data processing or preparation services\r\n",
+            "product_tax_code": "81112002A0000"
+        },
+        {
+            "description": "Database analysis service\r\n",
+            "name": "Database analysis service\r\n",
+            "product_tax_code": "81111806A0000"
+        },
+        {
+            "description": "Electronic software documentation or user manuals - For prewritten software & delivered on tangible media",
+            "name": "Electronic software documentation or user manuals - Prewritten, tangible media",
+            "product_tax_code": "55111601A1100"
+        },
+        {
+            "description": "Cloud-based software as a service (SaaS) - Personal Use",
+            "name": "Cloud-based software as a service (SaaS) - Personal Use",
+            "product_tax_code": "81162000A0000"
+        },
+        {
+            "description": "Clothing - Costume",
+            "name": "Clothing - Costume",
+            "product_tax_code": "60141401A0000"
+        },
+        {
+            "description": "Online data processing service\r\n",
+            "name": "Online data processing service\r\n",
+            "product_tax_code": "81112001A0000"
+        },
+        {
+            "description": "System usability services\r\n",
+            "name": "System usability services\r\n",
+            "product_tax_code": "81111820A0000"
+        },
+        {
+            "description": "Services that provide both essential proactive and reactive operations and maintenance support.",
+            "name": "IT Support Services",
+            "product_tax_code": "81111811A0000"
+        },
+        {
+            "description": "System analysis service\r\n",
+            "name": "System analysis service\r\n",
+            "product_tax_code": "81111808A0000"
+        },
+        {
+            "description": "Data storage service\r\n",
+            "name": "Data storage service\r\n",
+            "product_tax_code": "81112006A0000"
+        },
+        {
+            "description": "Quality assurance services\r\n",
+            "name": "Quality assurance services\r\n",
+            "product_tax_code": "81111819A0000"
+        },
+        {
+            "description": "Software - Custom & delivered by load & leave",
+            "name": "Software - Custom, load and leave delivery",
+            "product_tax_code": "43230000A2300"
+        },
+        {
+            "description": "Food and Beverage - Meat Sticks, Meat Jerky",
+            "name": "Food and Beverage - Meat Sticks, Meat Jerky",
+            "product_tax_code": "50112000A0000"
+        },
+        {
+            "description": "Clothing - Synthetic Fur Poncho or Cape",
+            "name": "Clothing - Synthetic Fur Poncho or Cape",
+            "product_tax_code": "53101806A0002"
+        },
+        {
+            "description": "Clothing - Wetsuit",
+            "name": "Clothing - Wetsuit",
+            "product_tax_code": "49141506A0000"
+        },
+        {
+            "description": "Cloud-based business process as a service - Business Use",
+            "name": "Cloud-based business process as a service - Business Use",
+            "product_tax_code": "81162300A9000"
+        },
+        {
+            "description": "Cloud-based infrastructure as a service (IaaS) - Business Use",
+            "name": "Cloud-based infrastructure as a service (IaaS) - Business Use",
+            "product_tax_code": "81162200A9000"
+        },
+        {
+            "description": "Clothing - Belt Buckle",
+            "name": "Clothing - Belt Buckle",
+            "product_tax_code": "53102501A0001"
+        },
+        {
+            "description": "Clothing - Shower Cap",
+            "name": "Clothing - Shower Cap",
+            "product_tax_code": "53131601A0000"
+        },
+        {
+            "description": "Clothing - Eye shield garters",
+            "name": "Clothing - Eye shield garters",
+            "product_tax_code": "46181809A0001"
+        },
+        {
+            "description": "Clothing - Socks",
+            "name": "Clothing - Socks",
+            "product_tax_code": "53102402A0000"
+        },
+        {
+            "description": "Clothing - Stockings",
+            "name": "Clothing - Stockings",
+            "product_tax_code": "53102401A0000"
+        },
+        {
+            "description": "Food and Beverage - Meat and meat products",
+            "name": "Food and Beverage - Meat and meat products",
+            "product_tax_code": "50110000A0000"
+        },
+        {
+            "description": "Clothing - Slips",
+            "name": "Clothing - Slips",
+            "product_tax_code": "53102302A0000"
+        },
+        {
+            "description": "Clothing - Goggle protective covers",
+            "name": "Clothing - Goggle protective covers",
+            "product_tax_code": "46181808A0001"
+        },
+        {
+            "description": "Clothing - Goggles",
+            "name": "Clothing - Goggles",
+            "product_tax_code": "46181804A0001"
+        },
+        {
+            "description": "Clothing - Football receiver gloves",
+            "name": "Clothing - Football receiver gloves",
+            "product_tax_code": "49211606A0004"
+        },
+        {
+            "description": "Disaster recovery services",
+            "name": "Disaster recovery services",
+            "product_tax_code": "81112004A0000"
+        },
+        {
+            "description": "Clothing - Mountain climbing boot",
+            "name": "Clothing - Mountain climbing boot",
+            "product_tax_code": "46181613A0000"
+        },
+        {
+            "description": "Software maintenance and support - Mandatory maintenance and support charges for prewritten software including items delivered on tangible media",
+            "name": "Software maintenance and support - Mandatory, prewritten, tangible media",
+            "product_tax_code": "81112200A1110"
+        },
+        {
+            "description": "Clothing - Military boot",
+            "name": "Clothing - Military boot",
+            "product_tax_code": "46181612A0000"
+        },
+        {
+            "description": "Carbonated beverages marketed as energy drinks, carrying a Supplement Facts Label, that contain a blend of energy enhancing vitamins, minerals, herbals, stimulants, etc.",
+            "name": "Energy Beverages - Carbonated - with Supplement Facts Label",
+            "product_tax_code": "50202309A0001"
+        },
+        {
+            "description": "Non-carbonated beverages marketed as energy drinks, carrying a Supplement Facts Label, that contain a blend of energy enhancing vitamins, minerals, herbals, stimulants, etc.",
+            "name": "Energy Beverages - Non-Carbonated - with Supplement Facts Label",
+            "product_tax_code": "50202309A0000"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with tangible personal property, with the food comprising 90% or more of the overall value of the bundle, where all food consists of candy (not containing flour).",
+            "name": "Food/TPP Bundle - with Food 90% or more - Food is all Candy",
+            "product_tax_code": "50193400A0001"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with tangible personal property, with the food comprising less 90% or more of the overall value of the bundle.",
+            "name": "Food/TPP Bundle - with Food 90% or more",
+            "product_tax_code": "50193400A0000"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with tangible personal property, with the food comprising between 76% and 89% of the overall value of the bundle, where all food consists of candy (not containing flour).",
+            "name": "Food/TPP Bundle - with Food between 76% and 89% - Food is all Candy",
+            "product_tax_code": "50193400A0005"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with tangible personal property, with the food comprising between 76% and 89% of the overall value of the bundle.",
+            "name": "Food/TPP Bundle - with Food between 76% and 89%",
+            "product_tax_code": "50193400A0004"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with tangible personal property, with the food comprising between 50% and 75% of the overall value of the bundle, where all food consists of candy (not containing flour).",
+            "name": "Food/TPP Bundle - with Food between 50% and 75% - Food is all Candy",
+            "product_tax_code": "50193400A0003"
+        },
+        {
+            "description": "Food bundle or basket containing food staples combined with tangible personal property, with the food comprising between 50% and 75% of the overall value of the bundle.",
+            "name": "Food/TPP Bundle - with Food between 50% and 75%",
+            "product_tax_code": "50193400A0002"
+        },
+        {
+            "description": "Food/TPP Bundle - with Food less than 50%",
+            "name": "Food/TPP Bundle - with Food less than 50%",
+            "product_tax_code": "50193400A0006"
+        },
+        {
+            "description": "Ready to drink beverages, not containing milk, formulated and labled for their nutritional value, such as increased caloric or protein intake and containing natrual or artificial sweeteners.",
+            "name": "Nutritional Supplement/protein drinks, shakes - contains no milk",
+            "product_tax_code": "50501703A0000"
+        },
+        {
+            "description": "Ready to drink beverages, containing milk, formulated and labled for their nutritional value, such as increased caloric or protein intake.",
+            "name": "Nutritional Supplement/protein drinks, shakes - contains milk",
+            "product_tax_code": "50501703A0001"
+        },
+        {
+            "description": "Powdered mixes to be reconstituted into a drinkable beverage using water.",
+            "name": "Powdered Drink Mixes - to be mixed with water",
+            "product_tax_code": "50202311A0000"
+        },
+        {
+            "description": "Powdered mixes to be reconstituted into a drinkable beverage using milk or a milk substitute.",
+            "name": "Powdered Drink Mixes - to be mixed with milk",
+            "product_tax_code": "50202311A0001"
+        },
+        {
+            "description": "Food and Beverage - Granola Bars, Cereal Bars, Energy Bars, Protein Bars containing no flour",
+            "name": "Food and Beverage - Granola Bars, Cereal Bars, Energy Bars, Protein Bars containing no flour",
+            "product_tax_code": "50221202A0002"
+        },
+        {
+            "description": "Food and Beverage - Granola Bars, Cereal Bars, Energy Bars, Protein Bars containing flour",
+            "name": "Food and Beverage - Granola Bars, Cereal Bars, Energy Bars, Protein Bars containing flour",
+            "product_tax_code": "50221202A0001"
+        },
+        {
+            "description": "Nutritional supplement in powder form, dairy based or plant based, focused on increasing ones intake of protein for various benefits.",
+            "name": "Protein Powder",
+            "product_tax_code": "50501703A0002"
+        },
+        {
+            "description": "Ready to drink non-carbonated beverage containing tea with natural or artificial sweeteners.",
+            "name": "Bottled tea - non-carbonated  - sweetened",
+            "product_tax_code": "50201712A0003"
+        },
+        {
+            "description": "Ready to drink non-carbonated beverage containing tea without natural or artificial sweeteners.",
+            "name": "Bottled tea - non-carbonated - unsweetened",
+            "product_tax_code": "50201712A0000"
+        },
+        {
+            "description": "Ready to drink carbonated beverage containing tea with natural or artificial sweeteners.",
+            "name": "Bottled tea - carbonated  - sweetened",
+            "product_tax_code": "50201712A0002"
+        },
+        {
+            "description": "Ready to drink carbonated beverage containing tea and without any natural or artificial sweeteners.",
+            "name": "Bottled tea - carbonated - unsweetened",
+            "product_tax_code": "50201712A0001"
+        },
+        {
+            "description": "Ready to drink coffee based beverage containing milk or milk substitute.",
+            "name": "Bottled coffee - containing milk or milk substitute",
+            "product_tax_code": "50201708A0002"
+        },
+        {
+            "description": "Ready to drink coffee based beverage not containing milk, containing natural or artificial sweetener.",
+            "name": "Bottled coffee - no milk - sweetened",
+            "product_tax_code": "50201708A0001"
+        },
+        {
+            "description": "Ready to drink coffee based beverage containing neither milk nor natural or artificial sweeteners.",
+            "name": "Bottled coffee - no milk - unsweetened",
+            "product_tax_code": "50201708A0000"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 51 - 69% natural vegetable juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 51-69% vegetable juice",
+            "product_tax_code": "50202306A0008"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 1 - 9% natural fruit juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 1-9% fruit juice",
+            "product_tax_code": "50202306A0001"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 70 - 99% natural fruit juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 70-99% fruit juice",
+            "product_tax_code": "50202304A0010"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 51 - 69% natural vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 51-69% vegetable juice",
+            "product_tax_code": "50202304A0009"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 25 - 50% natural vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 25-50% vegetable juice",
+            "product_tax_code": "50202304A0007"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 10 - 24% natural fruit juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 10-24% fruit juice",
+            "product_tax_code": "50202304A0004"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 70 - 99% natural vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 70-99% vegetable juice",
+            "product_tax_code": "50202304A0011"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and zero natural fruit or vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - No fruit or vegetable juice",
+            "product_tax_code": "50202304A0001"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 51 - 69% natural fruit juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 51-69% fruit juice",
+            "product_tax_code": "50202304A0008"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 1 - 9% natural vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 1 -9% vegetable juice",
+            "product_tax_code": "50202304A0003"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 1 - 9% natural fruit juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 1-9% fruit juice",
+            "product_tax_code": "50202304A0002"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 10 - 24% natural vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 10-24% vegetable juice",
+            "product_tax_code": "50202304A0005"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 25 - 50% natural fruit juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 25-50% fruit juice",
+            "product_tax_code": "50202304A0006"
+        },
+        {
+            "description": "Non-carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 100% natural fruit or vegetable juice.  This does not include flavored water.  This does include sweetened cocktail mixes that can be combined with alcohol.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Non-Carbonated - 100% fruit or vegetable juice",
+            "product_tax_code": "50202304A0000"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and zero natural fruit or vegetable juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - No fruit or vegetable juice",
+            "product_tax_code": "50202306A0000"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 70 - 99% natural vegetable juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 70-99% vegetable juice",
+            "product_tax_code": "50202306A0010"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 70 - 99% natural fruit juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 70-99% fruit juice",
+            "product_tax_code": "50202306A0009"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 51 - 69% natural fruit juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 51-69% fruit juice",
+            "product_tax_code": "50202306A0007"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 25 - 50% natural vegetable juice.  This does not flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 25-50% vegetable juice",
+            "product_tax_code": "50202306A0006"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 25 - 50% natural fruit juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 25-50% fruit juice",
+            "product_tax_code": "50202306A0005"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 100% natural fruit or vegetable juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 100% fruit or vegetable juice",
+            "product_tax_code": "50202306A0011"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 10 - 24% natural vegetable juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 10-24% vegetable juice",
+            "product_tax_code": "50202306A0004"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 10 - 24% natural fruit juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 10-24% fruit juice",
+            "product_tax_code": "50202306A0003"
+        },
+        {
+            "description": "Carbonated nonalcoholic beverages that contain natural or artificial sweeteners, and 1 - 9% natural vegetable juice.  This does not include flavored carbonated water.  This does include beverages marketed as energy drinks that carry a Nutrition Facts label and contain a blend of energy enhancing ingredients.",
+            "name": "Soft Drinks - Carbonated - 1 -9% vegetable juice",
+            "product_tax_code": "50202306A0002"
+        },
+        {
+            "description": "Bottled Water for human consumption, unsweetened, non-carbonated. Does not include distilled water.",
+            "name": "Bottled Water",
+            "product_tax_code": "50202301A0000"
+        },
+        {
+            "description": "Bottled Water for human consumption, containing natural or artificial sweeteners, non-carbonated.",
+            "name": "Bottled Water - Flavored",
+            "product_tax_code": "50202301A0001"
+        },
+        {
+            "description": "Bottled Water for human consumption, unsweetened, carbonated naturally.  Includes carbonated waters containing only natural flavors or essences.",
+            "name": "Bottled Water - Carbonated Naturally",
+            "product_tax_code": "50202301A0003"
+        },
+        {
+            "description": "Bottled Water for human consumption, unsweetened, carbonated artificially during bottling process.  Includes carbonated waters containing only natural flavors or essences.",
+            "name": "Bottled Water - Carbonated Artificially",
+            "product_tax_code": "50202301A0002"
+        },
+        {
+            "description": "Bottled Water for human consumption, containing natural or artificial sweeteners, carbonated.",
+            "name": "Bottled Water - Carbonated - Sweetened",
+            "product_tax_code": "50202301A0004"
+        },
+        {
+            "description": "Clothing - Sequins for use in clothing",
+            "name": "Clothing - Sequins for use in clothing",
+            "product_tax_code": "60123900A0000"
+        },
+        {
+            "description": "Clothing - Synthetic Fur Gloves",
+            "name": "Clothing - Synthetic Fur Gloves",
+            "product_tax_code": "53102503A0002"
+        },
+        {
+            "description": "Clothing - Synthetic Fur Coat or Jacket",
+            "name": "Clothing - Synthetic Fur Coat or Jacket",
+            "product_tax_code": "53101800A0002"
+        },
+        {
+            "description": "Clothing - Fur Hat",
+            "name": "Clothing - Fur Hat",
+            "product_tax_code": "53102504A0001"
+        },
+        {
+            "description": "Clothing - Fur Coat or Jacket",
+            "name": "Clothing - Fur Coat or Jacket",
+            "product_tax_code": "53101800A0001"
+        },
+        {
+            "description": "Cloud-based business process as a service",
+            "name": "Cloud-based business process as a service - Personal Use",
+            "product_tax_code": "81162300A0000"
+        },
+        {
+            "description": "Clothing - Shoulder pads for sports",
+            "name": "Clothing - Shoulder pads for sports",
+            "product_tax_code": "46181506A0002"
+        },
+        {
+            "description": "Mainframe software applications design\r\n",
+            "name": "Mainframe software applications design\r\n",
+            "product_tax_code": "81111501A0000"
+        },
+        {
+            "description": "Clothing - Safety shoes",
+            "name": "Clothing - Safety shoes",
+            "product_tax_code": "46181605A0000"
+        },
+        {
+            "description": "Clothing - Protective hood",
+            "name": "Clothing - Protective hood",
+            "product_tax_code": "46181710A0001"
+        },
+        {
+            "description": "Clothing - Face protection kit",
+            "name": "Clothing - Face protection kit",
+            "product_tax_code": "46181709A0001"
+        },
+        {
+            "description": "Clothing - Protective hair net",
+            "name": "Clothing - Protective hair net",
+            "product_tax_code": "46181708A0001"
+        },
+        {
+            "description": "Clothing - Facial shields parts or accessories",
+            "name": "Clothing - Facial shields parts or accessories",
+            "product_tax_code": "46181707A0001"
+        },
+        {
+            "description": "Clothing - Safety helmets",
+            "name": "Clothing - Safety helmets",
+            "product_tax_code": "46181704A0001"
+        },
+        {
+            "description": "Clothing - Poncho",
+            "name": "Clothing - Poncho",
+            "product_tax_code": "53101806A0000"
+        },
+        {
+            "description": "Clothing - Protective insole",
+            "name": "Clothing - Protective insole",
+            "product_tax_code": "46181609A0000"
+        },
+        {
+            "description": "Clothing - Protective clogs",
+            "name": "Clothing - Protective clogs",
+            "product_tax_code": "46181607A0000"
+        },
+        {
+            "description": "Clothing - Waterproof jacket or raincoat",
+            "name": "Clothing - Waterproof jacket or raincoat",
+            "product_tax_code": "46181543A0000"
+        },
+        {
+            "description": "Systems architecture\r\n",
+            "name": "Systems architecture\r\n",
+            "product_tax_code": "81111705A0000"
+        },
+        {
+            "description": "System installation service\r\n",
+            "name": "System installation service\r\n",
+            "product_tax_code": "81111809A0000"
+        },
+        {
+            "description": "Software maintenance and support - Mandatory maintenance and support charges for custom software including items delivered by load and leave",
+            "name": "Software maintenance and support - Mandatory, custom, load and leave delivery",
+            "product_tax_code": "81112200A2310"
+        },
+        {
+            "description": "Software coding service\r\n",
+            "name": "Software coding service\r\n",
+            "product_tax_code": "81111810A0000"
+        },
+        {
+            "description": "Software - Custom & delivered electronically",
+            "name": "Software - Custom, electronic delivery",
+            "product_tax_code": "43230000A2200"
+        },
+        {
+            "description": "Bathing suits and swim suits",
+            "name": "Clothing - Swimwear",
+            "product_tax_code": "20041"
+        },
+        {
+            "description": "Miscellaneous services which are not subject to a service-specific tax levy. This category will only treat services as taxable if the jurisdiction taxes services generally.",
+            "name": "General Services",
+            "product_tax_code": "19000"
+        },
+        {
+            "description": "Services provided to educate users on the proper use of a product.  Live training in person",
+            "name": "Training Services - Live",
+            "product_tax_code": "19004"
+        },
+        {
+            "description": "Admission charges associated with entry to an event.",
+            "name": "Admission Services",
+            "product_tax_code": "19003"
+        },
+        {
+            "description": "Service of providing usage of a parking space.",
+            "name": "Parking Services",
+            "product_tax_code": "19002"
+        },
+        {
+            "description": "A charge separately stated from any sale of the product itself for the installation of tangible personal property.  This a labor charge, with any non-separately stated property transferred in performing the service considered inconsequential.",
+            "name": "Installation Services",
+            "product_tax_code": "10040"
+        },
+        {
+            "description": "Services rendered for advertising which do not include the exchange of tangible personal property.",
+            "name": "Advertising Services",
+            "product_tax_code": "19001"
+        },
+        {
+            "description": "Digital products transferred electronically, meaning obtained by the purchaser by means other than tangible storage media.",
+            "name": "Digital Goods",
+            "product_tax_code": "31000"
+        },
+        {
+            "description": " All human wearing apparel suitable for general use",
+            "name": "Clothing",
+            "product_tax_code": "20010"
+        },
+        {
+            "description": "An over-the-counter drug is a substance that contains a label identifying it as a drug and including a \"drug facts\" panel or a statement of active ingredients, that can be obtained without a prescription.  A drug can be intended for internal (ingestible, implant, injectable) or external (topical) application to the human body.",
+            "name": "Over-the-Counter Drugs",
+            "product_tax_code": "51010"
+        },
+        {
+            "description": "A substance that can only be obtained via a prescription of a licensed professional.  A drug is a compound, substance, or preparation, and any component thereof, not including food or food ingredients, dietary supplements, or alcoholic beverages, that is: recognized in the official United States pharmacopoeia, official homeopathic pharmacopoeia of the United States, or official national formulary, and supplement to any of them; intended for use in the diagnosis, cure, mitigation, treatment, or prevention of disease; or intended to affect the structure or any function of the body.  A drug can be intended for internal (ingestible, implant, injectable) or external (topical) application to the human body.",
+            "name": "Prescription Drugs",
+            "product_tax_code": "51020"
+        },
+        {
+            "description": "Food for humans consumption, unprepared",
+            "name": "Food & Groceries",
+            "product_tax_code": "40030"
+        },
+        {
+            "description": "Pre-written software, delivered electronically, but access remotely.",
+            "name": "Software as a Service",
+            "product_tax_code": "30070"
+        },
+        {
+            "description": "Periodicals, printed, sold by subscription",
+            "name": "Magazines & Subscriptions",
+            "product_tax_code": "81300"
+        },
+        {
+            "description": "Books, printed",
+            "name": "Books",
+            "product_tax_code": "81100"
+        },
+        {
+            "description": "Periodicals, printed, sold individually",
+            "name": "Magazine",
+            "product_tax_code": "81310"
+        },
+        {
+            "description": "Textbooks, printed",
+            "name": "Textbook",
+            "product_tax_code": "81110"
+        },
+        {
+            "description": "Religious books and manuals, printed",
+            "name": "Religious books",
+            "product_tax_code": "81120"
+        },
+        {
+            "description": "Non-food dietary supplements",
+            "name": "Supplements",
+            "product_tax_code": "40020"
+        },
+        {
+            "description": "Candy",
+            "name": "Candy",
+            "product_tax_code": "40010"
+        },
+        {
+            "description": "Soft drinks. Soda and similar drinks. Does not include water, juice, or milk.",
+            "name": "Soft Drinks",
+            "product_tax_code": "40050"
+        },
+        {
+            "description": "Bottled water for human consumption.",
+            "name": "Bottled Water",
+            "product_tax_code": "40060"
+        },
+        {
+            "description": "Ready to eat foods intended to be consumed on site by humans. Foods not considered to be Food & Grocery (not food for home consumption or food which requires further preparation to consume).",
+            "name": "Prepared Foods",
+            "product_tax_code": "41000"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/united_states/setup.py b/erpnext/regional/united_states/setup.py
index 24ab1cf..c0cec3a 100644
--- a/erpnext/regional/united_states/setup.py
+++ b/erpnext/regional/united_states/setup.py
@@ -3,12 +3,41 @@
 
 from __future__ import unicode_literals
 import frappe
+import os
+import json
+from frappe.permissions import add_permission, update_permission_property
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 
 def setup(company=None, patch=True):
+	# Company independent fixtures should be called only once at the first company setup
+	if frappe.db.count('Company', {'country': 'United States'}) <=1:
+		setup_company_independent_fixtures(patch=patch)
+
+def setup_company_independent_fixtures(company=None, patch=True):
+	add_product_tax_categories()
 	make_custom_fields()
+	add_permissions()
+	frappe.enqueue('erpnext.regional.united_states.setup.add_product_tax_categories', now=False)
 	add_print_formats()
 
+# Product Tax categories imported from taxjar api
+def add_product_tax_categories():
+	with open(os.path.join(os.path.dirname(__file__), 'product_tax_category_data.json'), 'r') as f:
+		tax_categories = json.loads(f.read())
+	create_tax_categories(tax_categories['categories'])
+
+def create_tax_categories(data):
+	for d in data:
+		tax_category = frappe.new_doc('Product Tax Category')
+		tax_category.description = d.get("description")
+		tax_category.product_tax_code = d.get("product_tax_code")
+		tax_category.category_name = d.get("name")
+		try:
+			tax_category.db_insert()
+		except frappe.DuplicateEntryError:
+			pass
+
+
 def make_custom_fields(update=True):
 	custom_fields = {
 		'Supplier': [
@@ -30,10 +59,29 @@
 		'Quotation': [
 			dict(fieldname='exempt_from_sales_tax', fieldtype='Check', insert_after='taxes_and_charges',
 				label='Is customer exempted from sales tax?')
+		],
+		'Sales Invoice Item': [
+			dict(fieldname='product_tax_category', fieldtype='Link', insert_after='description', options='Product Tax Category',
+				label='Product Tax Category', fetch_from='item_code.product_tax_category'),
+			dict(fieldname='tax_collectable', fieldtype='Currency', insert_after='net_amount', 
+				label='Tax Collectable', read_only=1),
+			dict(fieldname='taxable_amount', fieldtype='Currency', insert_after='tax_collectable', 
+				label='Taxable Amount', read_only=1)
+		],
+		'Item': [
+			dict(fieldname='product_tax_category', fieldtype='Link', insert_after='item_group', options='Product Tax Category',
+				label='Product Tax Category')
 		]
 	}
 	create_custom_fields(custom_fields, update=update)
 
+def add_permissions():
+	doctype = "Product Tax Category"
+	for role in ('Accounts Manager', 'Accounts User', 'System Manager','Item Manager', 'Stock Manager'):
+		add_permission(doctype, role, 0)
+		update_permission_property(doctype, role, 0, 'write', 1)
+		update_permission_property(doctype, role, 0, 'create', 1)
+
 def add_print_formats():
 	frappe.reload_doc("regional", "print_format", "irs_1099_form")
 	frappe.db.set_value("Print Format", "IRS 1099 Form", "disabled", 0)
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index 2acc64c..5913b84 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -20,6 +20,7 @@
   "tax_withholding_category",
   "default_bank_account",
   "lead_name",
+  "prospect",
   "opportunity_name",
   "image",
   "column_break0",
@@ -213,8 +214,7 @@
    "fieldtype": "Link",
    "ignore_user_permissions": 1,
    "label": "Represents Company",
-   "options": "Company",
-   "unique": 1
+   "options": "Company"
   },
   {
    "depends_on": "represents_company",
@@ -498,6 +498,14 @@
    "options": "Tax Withholding Category"
   },
   {
+   "fieldname": "prospect",
+   "fieldtype": "Link",
+   "label": "Prospect",
+   "no_copy": 1,
+   "options": "Prospect",
+   "print_hide": 1
+  },
+  {
    "fieldname": "opportunity_name",
    "fieldtype": "Link",
    "label": "From Opportunity",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index abf146c..1164f40 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -150,8 +150,14 @@
 				self.db_set('email_id', self.email_id)
 
 	def create_primary_address(self):
+		from frappe.contacts.doctype.address.address import get_address_display
+
 		if self.flags.is_new_doc and self.get('address_line1'):
-			make_address(self)
+			address = make_address(self)
+			address_display = get_address_display(address.name)
+
+			self.db_set("customer_primary_address", address.name)
+			self.db_set("primary_address", address_display)
 
 	def update_lead_status(self):
 		'''If Customer created from Lead, update lead status to "Converted"
@@ -246,9 +252,15 @@
 
 	def on_trash(self):
 		if self.customer_primary_contact:
-			frappe.db.sql("""update `tabCustomer`
-				set customer_primary_contact=null, mobile_no=null, email_id=null
-				where name=%s""", self.name)
+			frappe.db.sql("""
+				UPDATE `tabCustomer`
+				SET
+					customer_primary_contact=null,
+					customer_primary_address=null,
+					mobile_no=null,
+					email_id=null,
+					primary_address=null
+				WHERE name=%(name)s""", {"name": self.name})
 
 		delete_contact_and_address('Customer', self.name)
 		if self.lead_name:
diff --git a/erpnext/selling/doctype/customer/test_customer.js b/erpnext/selling/doctype/customer/test_customer.js
deleted file mode 100644
index 65b81af..0000000
--- a/erpnext/selling/doctype/customer/test_customer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Customer", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Customer
-		() => frappe.tests.make('Customer', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index b1a5b52..5b33731 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -352,3 +352,26 @@
 			'credit_limit': credit_limit
 		})
 		customer.credit_limits[-1].db_insert()
+
+def create_internal_customer(customer_name, represents_company, allowed_to_interact_with):
+	if not frappe.db.exists("Customer", customer_name):
+		customer = frappe.get_doc({
+			"doctype": "Customer",
+			"customer_group": "_Test Customer Group",
+			"customer_name": customer_name,
+			"customer_type": "Individual",
+			"territory": "_Test Territory",
+			"is_internal_customer": 1,
+			"represents_company": represents_company
+		})
+
+		customer.append("companies", {
+			"company": allowed_to_interact_with
+		})
+
+		customer.insert()
+		customer_name = customer.name
+	else:
+		customer_name = frappe.db.get_value("Customer", customer_name)
+
+	return customer_name
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index bba5401..5d44582 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -718,8 +718,7 @@
 				"doctype": "Maintenance Schedule Item",
 				"field_map": {
 					"parent": "sales_order"
-				},
-				"add_if_empty": True
+				}
 			}
 		}, target_doc)
 
@@ -745,8 +744,7 @@
 				"field_map": {
 					"parent": "prevdoc_docname",
 					"parenttype": "prevdoc_doctype"
-				},
-				"add_if_empty": True
+				}
 			}
 		}, target_doc)
 
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.js b/erpnext/selling/doctype/sales_order/test_sales_order.js
deleted file mode 100644
index 57ed19b..0000000
--- a/erpnext/selling/doctype/sales_order/test_sales_order.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sales Order", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Sales Order', [
-		// insert a new Sales Order
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js b/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js
deleted file mode 100644
index 3ed7b46..0000000
--- a/erpnext/selling/doctype/sales_partner_type/test_sales_partner_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Sales Partner Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Sales Partner Type
-		() => frappe.tests.make('Sales Partner Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.js b/erpnext/selling/doctype/selling_settings/selling_settings.js
index d8d3051..cf6fb28 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.js
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.js
@@ -6,26 +6,3 @@
 
 	}
 });
-
-frappe.tour['Selling Settings'] = [
-	{
-		fieldname: "cust_master_name",
-		title: "Customer Naming By",
-		description: __("By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a ") + "<a href='https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series' target='_blank'>Naming Series</a>" + __(" choose the 'Naming Series' option."),
-	},
-	{
-		fieldname: "selling_price_list",
-		title: "Default Selling Price List",
-		description: __("Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.")
-	},
-	{
-		fieldname: "so_required",
-		title: "Sales Order Required for Sales Invoice & Delivery Note Creation",
-		description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.")
-	},
-	{
-		fieldname: "dn_required",
-		title: "Delivery Note Required for Sales Invoice Creation",
-		description: __("If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.")
-	}
-];
diff --git a/erpnext/selling/form_tour/sales_order/sales_order.json b/erpnext/selling/form_tour/sales_order/sales_order.json
new file mode 100644
index 0000000..a81eb4a
--- /dev/null
+++ b/erpnext/selling/form_tour/sales_order/sales_order.json
@@ -0,0 +1,97 @@
+{
+ "creation": "2021-06-29 21:13:36.089054",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-06-29 21:13:36.089054",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Sales Order",
+ "owner": "Administrator",
+ "reference_doctype": "Sales Order",
+ "save_on_complete": 1,
+ "steps": [
+  {
+   "description": "Select a customer.",
+   "field": "",
+   "fieldname": "customer",
+   "fieldtype": "Link",
+   "has_next_condition": 1,
+   "is_table_field": 0,
+   "label": "Customer",
+   "next_step_condition": "customer",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Select Customer"
+  },
+  {
+   "description": "You can add items here.",
+   "field": "",
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Items",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "List of items"
+  },
+  {
+   "child_doctype": "Sales Order Item",
+   "description": "Select an item.",
+   "field": "",
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "has_next_condition": 1,
+   "is_table_field": 1,
+   "label": "Item Code",
+   "next_step_condition": "eval: doc.item_code",
+   "parent_field": "",
+   "parent_fieldname": "items",
+   "position": "Right",
+   "title": "Select Item"
+  },
+  {
+   "child_doctype": "Sales Order Item",
+   "description": "Enter quantity.",
+   "field": "",
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "has_next_condition": 0,
+   "is_table_field": 1,
+   "label": "Quantity",
+   "parent_field": "",
+   "parent_fieldname": "items",
+   "position": "Right",
+   "title": "Enter Quantity"
+  },
+  {
+   "child_doctype": "Sales Order Item",
+   "description": "Enter rate of the item.",
+   "field": "",
+   "fieldname": "rate",
+   "fieldtype": "Currency",
+   "has_next_condition": 0,
+   "is_table_field": 1,
+   "label": "Rate",
+   "parent_field": "",
+   "parent_fieldname": "items",
+   "position": "Right",
+   "title": "Enter Rate"
+  },
+  {
+   "description": "You can add sales taxes and charges here.",
+   "field": "",
+   "fieldname": "taxes",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Sales Taxes and Charges",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Add Sales Taxes and Charges"
+  }
+ ],
+ "title": "Sales Order"
+}
\ No newline at end of file
diff --git a/erpnext/selling/form_tour/selling_settings/selling_settings.json b/erpnext/selling/form_tour/selling_settings/selling_settings.json
new file mode 100644
index 0000000..20c718f
--- /dev/null
+++ b/erpnext/selling/form_tour/selling_settings/selling_settings.json
@@ -0,0 +1,65 @@
+{
+ "creation": "2021-06-29 20:39:19.408763",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-06-29 20:49:01.359489",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Selling Settings",
+ "owner": "Administrator",
+ "reference_doctype": "Selling Settings",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "By default, the Customer Name is set as per the Full Name entered. If you want Customers to be named by a <a href=\"https://docs.erpnext.com/docs/user/manual/en/setting-up/settings/naming-series\" target=\"_blank\">Naming Series</a>. Choose the 'Naming Series' option.",
+   "field": "",
+   "fieldname": "cust_master_name",
+   "fieldtype": "Select",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Customer Naming By",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Customer Naming By"
+  },
+  {
+   "description": "If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice or Delivery Note without creating a Sales Order first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Sales Order' checkbox in the Customer master.",
+   "field": "",
+   "fieldname": "so_required",
+   "fieldtype": "Select",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Is Sales Order Required for Sales Invoice & Delivery Note Creation?",
+   "parent_field": "",
+   "position": "Left",
+   "title": "Sales Order Required for Sales Invoice & Delivery Note Creation"
+  },
+  {
+   "description": "If this option is configured 'Yes', ERPNext will prevent you from creating a Sales Invoice without creating a Delivery Note first. This configuration can be overridden for a particular Customer by enabling the 'Allow Sales Invoice Creation Without Delivery Note' checkbox in the Customer master.",
+   "field": "",
+   "fieldname": "dn_required",
+   "fieldtype": "Select",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Is Delivery Note Required for Sales Invoice Creation?",
+   "parent_field": "",
+   "position": "Left",
+   "title": "Delivery Note Required for Sales Invoice Creation"
+  },
+  {
+   "description": "Configure the default Price List when creating a new Sales transaction. Item prices will be fetched from this Price List.",
+   "field": "",
+   "fieldname": "selling_price_list",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Default Price List",
+   "parent_field": "",
+   "position": "Right",
+   "title": "Default Selling Price List"
+  }
+ ],
+ "title": "Selling Settings"
+}
\ No newline at end of file
diff --git a/erpnext/selling/module_onboarding/selling/selling.json b/erpnext/selling/module_onboarding/selling/selling.json
index 160208f..c02f444 100644
--- a/erpnext/selling/module_onboarding/selling/selling.json
+++ b/erpnext/selling/module_onboarding/selling/selling.json
@@ -19,32 +19,17 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/selling",
  "idx": 0,
  "is_complete": 0,
- "modified": "2020-07-08 14:05:37.669753",
+ "modified": "2021-08-24 18:12:38.052434",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling",
  "owner": "Administrator",
  "steps": [
   {
-   "step": "Introduction to Selling"
-  },
-  {
-   "step": "Create a Customer"
-  },
-  {
-   "step": "Setup your Warehouse"
-  },
-  {
-   "step": "Create a Product"
-  },
-  {
-   "step": "Create a Quotation"
-  },
-  {
-   "step": "Create your first Sales Order"
-  },
-  {
    "step": "Selling Settings"
+  },
+  {
+   "step": "Sales Order"
   }
  ],
  "subtitle": "Products, Sales, Analysis, and more.",
diff --git a/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json b/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json
index 5a403b0..64defbf 100644
--- a/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json
+++ b/erpnext/selling/onboarding_step/create_a_customer/create_a_customer.json
@@ -5,7 +5,6 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
  "modified": "2020-06-01 13:16:19.731719",
@@ -13,6 +12,7 @@
  "name": "Create a Customer",
  "owner": "Administrator",
  "reference_document": "Customer",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Create a Customer",
  "validate_action": 1
diff --git a/erpnext/selling/onboarding_step/create_a_product/create_a_product.json b/erpnext/selling/onboarding_step/create_a_product/create_a_product.json
index d2068e1..52a5861 100644
--- a/erpnext/selling/onboarding_step/create_a_product/create_a_product.json
+++ b/erpnext/selling/onboarding_step/create_a_product/create_a_product.json
@@ -5,14 +5,14 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-05-12 18:30:02.489949",
+ "modified": "2020-10-14 14:53:00.133574",
  "modified_by": "Administrator",
  "name": "Create a Product",
  "owner": "Administrator",
  "reference_document": "Item",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Create a Product",
  "validate_action": 1
diff --git a/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json b/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json
index 27253d1..6f1837e 100644
--- a/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json
+++ b/erpnext/selling/onboarding_step/create_a_quotation/create_a_quotation.json
@@ -5,7 +5,6 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
  "modified": "2020-06-01 13:34:58.958641",
@@ -13,6 +12,7 @@
  "name": "Create a Quotation",
  "owner": "Administrator",
  "reference_document": "Quotation",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create a Quotation",
  "validate_action": 1
diff --git a/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json b/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json
new file mode 100644
index 0000000..378f295
--- /dev/null
+++ b/erpnext/selling/onboarding_step/create_a_sales_order/create_a_sales_order.json
@@ -0,0 +1,21 @@
+{
+ "action": "Create Entry",
+ "action_label": "Create Sales Order",
+ "creation": "2021-06-29 21:22:54.204880",
+ "description": "A Sales Order is a confirmation of an order from your customer. It is also referred to as Proforma Invoice.\n\nSales Order at the heart of your sales and purchase transactions. Sales Orders are linked in Delivery Note, Sales Invoices, Material Request, and Maintenance transactions. Through Sales Order, you can track fulfillment of the overall deal towards the customer.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-06-29 21:22:54.204880",
+ "modified_by": "Administrator",
+ "name": "Create a Sales Order",
+ "owner": "Administrator",
+ "reference_document": "Sales Order",
+ "show_form_tour": 1,
+ "show_full_form": 1,
+ "title": "Create a Sales Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json b/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json
index d21c1f4..6364533 100644
--- a/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json
+++ b/erpnext/selling/onboarding_step/introduction_to_selling/introduction_to_selling.json
@@ -5,13 +5,13 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
  "modified": "2020-06-01 13:29:13.703177",
  "modified_by": "Administrator",
  "name": "Introduction to Selling",
  "owner": "Administrator",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Introduction to Selling",
  "validate_action": 1,
diff --git a/erpnext/selling/onboarding_step/sales_order/sales_order.json b/erpnext/selling/onboarding_step/sales_order/sales_order.json
new file mode 100644
index 0000000..d616fae
--- /dev/null
+++ b/erpnext/selling/onboarding_step/sales_order/sales_order.json
@@ -0,0 +1,21 @@
+{
+ "action": "Show Form Tour",
+ "action_label": "Let\u2019s convert your first Sales Order against a Quotation",
+ "creation": "2021-08-13 14:03:49.217866",
+ "description": "# Sales Order\n\nA Sales Order is a confirmation of an order from your customer. It is also referred to as Proforma Invoice.\n\nSales Order at the heart of your sales and purchase transactions. Sales Orders are linked in Delivery Note, Sales Invoices, Material Request, and Maintenance transactions. Through Sales Order, you can track fulfillment of the overall deal towards the customer.",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 0,
+ "is_skipped": 0,
+ "modified": "2021-08-24 18:12:32.394992",
+ "modified_by": "Administrator",
+ "name": "Sales Order",
+ "owner": "Administrator",
+ "reference_document": "Sales Order",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "Sales Order",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/selling/onboarding_step/selling_settings/selling_settings.json b/erpnext/selling/onboarding_step/selling_settings/selling_settings.json
index 7996d7b..ec30fd3 100644
--- a/erpnext/selling/onboarding_step/selling_settings/selling_settings.json
+++ b/erpnext/selling/onboarding_step/selling_settings/selling_settings.json
@@ -1,19 +1,21 @@
 {
  "action": "Show Form Tour",
+ "action_label": "Let\u2019s walk-through Selling Settings",
  "creation": "2020-06-01 13:01:45.615189",
+ "description": "# Selling Settings\n\nCRM and Selling module\u2019s features are configurable as per your business needs. Selling Settings is the place where you can set your preferences for:\n - Customer naming and default values\n - Billing and shipping preference in sales transactions\n",
  "docstatus": 0,
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 1,
  "is_skipped": 0,
- "modified": "2020-06-01 13:04:14.980743",
+ "modified": "2021-08-24 18:11:21.264335",
  "modified_by": "Administrator",
  "name": "Selling Settings",
  "owner": "Administrator",
  "reference_document": "Selling Settings",
+ "show_form_tour": 0,
  "show_full_form": 0,
- "title": "Configure Selling Settings.",
+ "title": "Selling Settings",
  "validate_action": 0
 }
\ No newline at end of file
diff --git a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
index 9457dee..1e20eb0 100644
--- a/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
+++ b/erpnext/selling/onboarding_step/setup_your_warehouse/setup_your_warehouse.json
@@ -5,15 +5,15 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
- "modified": "2020-07-04 12:33:16.970031",
+ "modified": "2020-10-14 14:53:25.538900",
  "modified_by": "Administrator",
  "name": "Setup your Warehouse",
  "owner": "Administrator",
  "path": "Tree/Warehouse",
  "reference_document": "Warehouse",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Set up your Warehouse",
  "validate_action": 1
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index 63306ad..8e69851 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -253,41 +253,6 @@
 		}
 	}
 
-	setup_listener_for_payments() {
-		frappe.realtime.on("process_phone_payment", (data) => {
-			const doc = this.events.get_frm().doc;
-			const { response, amount, success, failure_message } = data;
-			let message, title;
-
-			if (success) {
-				title = __("Payment Received");
-				if (amount >= doc.grand_total) {
-					frappe.dom.unfreeze();
-					message = __("Payment of {0} received successfully.", [format_currency(amount, doc.currency, 0)]);
-					this.events.submit_invoice();
-					cur_frm.reload_doc();
-
-				} else {
-					message = __("Payment of {0} received successfully. Waiting for other requests to complete...", [format_currency(amount, doc.currency, 0)]);
-				}
-			} else if (failure_message) {
-				message = failure_message;
-				title = __("Payment Failed");
-			}
-
-			frappe.msgprint({ "message": message, "title": title });
-		});
-	}
-
-	auto_set_remaining_amount() {
-		const doc = this.events.get_frm().doc;
-		const remaining_amount = doc.grand_total - doc.paid_amount;
-		const current_value = this.selected_mode ? this.selected_mode.get_value() : undefined;
-		if (!current_value && remaining_amount > 0 && this.selected_mode) {
-			this.selected_mode.set_value(remaining_amount);
-		}
-	}
-
 	attach_shortcuts() {
 		const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
 		this.$component.find('.submit-order-btn').attr("title", `${ctrl_label}+Enter`);
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
index 24ca666..89cfa16 100644
--- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
@@ -44,18 +44,6 @@
 		if d.item_group not in item_groups:
 			item_groups.append(d.item_group)
 
-	if item_groups:
-		child_items = []
-		for item_group in item_groups:
-			if frappe.db.get_value("Item Group", {"name":item_group}, "is_group"):
-				for child_item_group in frappe.get_all("Item Group", {"parent_item_group":item_group}):
-					if child_item_group['name'] not in child_items:
-						child_items.append(child_item_group['name'])
-
-		for item in child_items:
-			if item not in item_groups:
-				item_groups.append(item)
-
 	date_field = ("transaction_date"
 		if filters.get('doctype') == "Sales Order" else "posting_date")
 
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index e4ee3ec..58cb52c 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -716,6 +716,15 @@
    "options": "Account"
   },
   {
+   "fieldname": "hr_settings_section",
+   "fieldtype": "Section Break",
+   "label": "HR & Payroll Settings"
+  },
+  {
+   "fieldname": "column_break_26",
+   "fieldtype": "Column Break"
+  },
+  {
    "collapsible": 1,
    "fieldname": "fixed_asset_defaults",
    "fieldtype": "Section Break",
@@ -731,15 +740,6 @@
    "fieldname": "section_break_28",
    "fieldtype": "Section Break",
    "label": "Chart of Accounts"
-  },
-  {
-   "fieldname": "hr_settings_section",
-   "fieldtype": "Section Break",
-   "label": "HR & Payroll Settings"
-  },
-  {
-   "fieldname": "column_break_26",
-   "fieldtype": "Column Break"
   }
  ],
  "icon": "fa fa-building",
@@ -808,4 +808,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js
deleted file mode 100644
index 19fde2e..0000000
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Currency Exchange", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Currency Exchange
-		() => frappe.tests.make('Currency Exchange', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
index 4ff2dd7..0eb7e7b 100644
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
@@ -1,14 +1,14 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
+
 import frappe, unittest
+from unittest import mock
 from frappe.utils import flt
 from erpnext.setup.utils import get_exchange_rate
 from frappe.utils import cint
 
 test_records = frappe.get_test_records('Currency Exchange')
 
-
 def save_new_records(test_records):
 	for record in test_records:
 		# If both selling and buying enabled
@@ -37,18 +37,45 @@
 			curr_exchange.for_selling = record["for_selling"]
 			curr_exchange.insert()
 
+test_exchange_values = {
+	'2015-12-15': '66.999',
+	'2016-01-15': '65.1'
+}
 
+# Removing API call from get_exchange_rate
+def patched_requests_get(*args, **kwargs):
+	class PatchResponse:
+		def __init__(self, json_data, status_code):
+			self.json_data = json_data
+			self.status_code = status_code
+
+		def raise_for_status(self):
+			if self.status_code != 200:
+				raise frappe.DoesNotExistError
+
+		def json(self):
+			return self.json_data
+
+	if args[0] == "https://api.exchangerate.host/convert" and kwargs.get('params'):
+		if kwargs['params'].get('date') and kwargs['params'].get('from') and kwargs['params'].get('to'):
+			if test_exchange_values.get(kwargs['params']['date']):
+				return PatchResponse({'result': test_exchange_values[kwargs['params']['date']]}, 200)
+
+	return PatchResponse({'result': None}, 404)
+
+@mock.patch('requests.get', side_effect=patched_requests_get)
 class TestCurrencyExchange(unittest.TestCase):
 	def clear_cache(self):
 		cache = frappe.cache()
-		key = "currency_exchange_rate:{0}:{1}".format("USD", "INR")
-		cache.delete(key)
+		for date in test_exchange_values.keys():
+			key = "currency_exchange_rate_{0}:{1}:{2}".format(date, "USD", "INR")
+			cache.delete(key)
 
 	def tearDown(self):
 		frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
 		self.clear_cache()
 
-	def test_exchange_rate(self):
+	def test_exchange_rate(self, mock_get):
 		save_new_records(test_records)
 
 		frappe.db.set_value("Accounts Settings", None, "allow_stale", 1)
@@ -69,7 +96,11 @@
 		self.assertFalse(exchange_rate == 60)
 		self.assertEqual(flt(exchange_rate, 3), 66.999)
 
-	def test_exchange_rate_strict(self):
+		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-20", "for_buying")
+		self.assertFalse(exchange_rate == 60)
+		self.assertEqual(flt(exchange_rate, 3), 65.1)
+
+	def test_exchange_rate_strict(self, mock_get):
 		# strict currency settings
 		frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
 		frappe.db.set_value("Accounts Settings", None, "stale_days", 1)
@@ -79,7 +110,7 @@
 
 		self.clear_cache()
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
-		self.assertEqual(flt(exchange_rate, 3), 67.235)
+		self.assertEqual(flt(exchange_rate, 3), 65.100)
 
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
 		self.assertEqual(exchange_rate, 62.9)
@@ -89,7 +120,7 @@
 		exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
 		self.assertEqual(flt(exchange_rate, 3), 66.999)
 
-	def test_exchange_rate_strict_switched(self):
+	def test_exchange_rate_strict_switched(self, mock_get):
 		# Start with allow_stale is True
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
 		self.assertEqual(exchange_rate, 65.1)
@@ -97,7 +128,7 @@
 		frappe.db.set_value("Accounts Settings", None, "allow_stale", 0)
 		frappe.db.set_value("Accounts Settings", None, "stale_days", 1)
 
-		# Will fetch from fixer.io
 		self.clear_cache()
-		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
-		self.assertEqual(flt(exchange_rate, 3), 67.235)
+		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_buying")
+		self.assertFalse(exchange_rate == 65)
+		self.assertEqual(flt(exchange_rate, 3), 62.9)
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index 6fbd4cd..217829f 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -60,9 +60,6 @@
 						reference_name = self.name,
 						unsubscribe_message = _("Unsubscribe from this Email Digest"))
 
-		frappe.set_user(original_user)
-		frappe.set_user_lang(original_user)
-
 	def get_msg_html(self):
 		"""Build email digest content"""
 		frappe.flags.ignore_account_permission = True
diff --git a/erpnext/setup/doctype/global_defaults/test_global_defaults.js b/erpnext/setup/doctype/global_defaults/test_global_defaults.js
deleted file mode 100644
index 33634eb..0000000
--- a/erpnext/setup/doctype/global_defaults/test_global_defaults.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Global Defaults", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Global Defaults
-		() => frappe.tests.make('Global Defaults', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/setup/doctype/item_group/test_item_group.js b/erpnext/setup/doctype/item_group/test_item_group.js
deleted file mode 100644
index ea322e2..0000000
--- a/erpnext/setup/doctype/item_group/test_item_group.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Item Group", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Item Group
-		() => frappe.tests.make('Item Group', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/setup/doctype/naming_series/test_naming_series.js b/erpnext/setup/doctype/naming_series/test_naming_series.js
deleted file mode 100644
index 22b664b..0000000
--- a/erpnext/setup/doctype/naming_series/test_naming_series.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Naming Series", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Naming Series
-		() => frappe.tests.make('Naming Series', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/setup/doctype/party_type/test_party_type.js b/erpnext/setup/doctype/party_type/test_party_type.js
deleted file mode 100644
index c97dbc5..0000000
--- a/erpnext/setup/doctype/party_type/test_party_type.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Party Type", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Party Type
-		() => frappe.tests.make('Party Type', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/setup/doctype/supplier_group/test_supplier_group.js b/erpnext/setup/doctype/supplier_group/test_supplier_group.js
deleted file mode 100644
index 976dd2c..0000000
--- a/erpnext/setup/doctype/supplier_group/test_supplier_group.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Supplier Group", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Supplier Group
-		() => frappe.tests.make('Supplier Group', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js b/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js
deleted file mode 100644
index afcf74c..0000000
--- a/erpnext/setup/doctype/uom_conversion_factor/test_uom_conversion_factor.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: UOM Conversion Factor", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new UOM Conversion Factor
-		() => frappe.tests.make('UOM Conversion Factor', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js
deleted file mode 100644
index c8485e7..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shopping Cart Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shopping Cart Settings
-		() => frappe.tests.make('Shopping Cart Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js b/erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js
deleted file mode 100644
index 85812d6..0000000
--- a/erpnext/stock/doctype/customs_tariff_number/test_customs_tariff_number.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Customs Tariff Number", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Customs Tariff Number
-		() => frappe.tests.make('Customs Tariff Number', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 91e7c00..b333a6b 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -430,12 +430,19 @@
 		})
 
 	def test_delivery_of_bundled_items_to_target_warehouse(self):
+		from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
+		customer_name = create_internal_customer(
+			customer_name="_Test Internal Customer 2",
+			represents_company="_Test Company with perpetual inventory",
+			allowed_to_interact_with="_Test Company with perpetual inventory"
+		)
 
 		set_valuation_method("_Test Item", "FIFO")
 		set_valuation_method("_Test Item Home Desktop 100", "FIFO")
 
-		target_warehouse=get_warehouse(company=company, abbr="TCP1",
+		target_warehouse = get_warehouse(company=company, abbr="TCP1",
 			warehouse_name="_Test Customer Warehouse").name
 
 		for warehouse in ("Stores - TCP1", target_warehouse):
@@ -444,10 +451,16 @@
 			create_stock_reconciliation(item_code="_Test Item Home Desktop 100", company = company,
 				expense_account = "Stock Adjustment - TCP1", warehouse=warehouse, qty=500, rate=100)
 
-		dn = create_delivery_note(item_code="_Test Product Bundle Item",
-			company='_Test Company with perpetual inventory', cost_center = 'Main - TCP1',
-			expense_account = "Cost of Goods Sold - TCP1", do_not_submit=True, qty=5, rate=500,
-			warehouse="Stores - TCP1", target_warehouse=target_warehouse)
+		dn = create_delivery_note(
+			item_code="_Test Product Bundle Item",
+			company="_Test Company with perpetual inventory",
+			customer=customer_name,
+			cost_center = 'Main - TCP1',
+			expense_account = "Cost of Goods Sold - TCP1",
+			do_not_submit=True,
+			qty=5, rate=500,
+			warehouse="Stores - TCP1",
+			target_warehouse=target_warehouse)
 
 		dn.submit()
 
@@ -487,6 +500,9 @@
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual([gle.debit, gle.credit], expected_values.get(gle.account))
 
+		# tear down
+		frappe.db.rollback()
+
 	def test_closed_delivery_note(self):
 		from erpnext.stock.doctype.delivery_note.delivery_note import update_delivery_note_status
 
diff --git a/erpnext/stock/doctype/delivery_settings/test_delivery_settings.js b/erpnext/stock/doctype/delivery_settings/test_delivery_settings.js
deleted file mode 100644
index 22977c0..0000000
--- a/erpnext/stock/doctype/delivery_settings/test_delivery_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Delivery Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Delivery Settings
-		() => frappe.tests.make('Delivery Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.js b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.js
deleted file mode 100644
index b6d6d1a..0000000
--- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Delivery Trip", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Delivery Trip
-		() => frappe.tests.make('Delivery Trip', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index c587dd5..752a1fe 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -141,9 +141,8 @@
 	is_fixed_asset: function(frm) {
 		// set serial no to false & toggles its visibility
 		frm.set_value('has_serial_no', 0);
+		frm.set_value('has_batch_no', 0);
 		frm.toggle_enable(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
-		frm.toggle_reqd(['asset_category'], frm.doc.is_fixed_asset);
-		frm.toggle_display(['has_serial_no', 'serial_no_series'], !frm.doc.is_fixed_asset);
 
 		frm.call({
 			method: "set_asset_naming_series",
@@ -793,3 +792,48 @@
 		}
 	}
 });
+
+frappe.tour['Item'] = [
+	{
+		fieldname: "item_code",
+		title: "Item Code",
+		description: __("Enter an Item Code, the name will be auto-filled the same as Item Code on clicking inside the Item Name field.")
+	},
+	{
+		fieldname: "item_group",
+		title: "Item Group",
+		description: __("Select an Item Group.")
+	},
+	{
+		fieldname: "is_stock_item",
+		title: "Maintain Stock",
+		description: __("If you are maintaining stock of this Item in your Inventory, ERPNext will make a stock ledger entry for each transaction of this item.")
+	},
+	{
+		fieldname: "include_item_in_manufacturing",
+		title: "Include Item in Manufacturing",
+		description: __("This is for raw material Items that'll be used to create finished goods. If the Item is an additional service like 'washing' that'll be used in the BOM, keep this unchecked.")
+	},
+	{
+		fieldname: "opening_stock",
+		title: "Opening Stock",
+		description: __("Enter the opening stock units.")
+	},
+	{
+		fieldname: "valuation_rate",
+		title: "Valuation Rate",
+		description: __("There are two options to maintain valuation of stock. FIFO (first in - first out) and Moving Average. To understand this topic in detail please visit <a href='https://docs.erpnext.com/docs/v13/user/manual/en/stock/articles/item-valuation-fifo-and-moving-average' target='_blank'>Item Valuation, FIFO and Moving Average.</a>")
+	},
+	{
+		fieldname: "standard_rate",
+		title: "Standard Selling Rate",
+		description: __("When creating an Item, entering a value for this field will automatically create an Item Price at the backend.")
+	},
+	{
+		fieldname: "item_defaults",
+		title: "Item Defaults",
+		description: __("In this section, you can define Company-wide transaction-related defaults for this Item. Eg. Default Warehouse, Default Price List, Supplier, etc.")
+	}
+
+
+];
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index f662bbd..db5caf9 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -204,6 +204,7 @@
   },
   {
    "default": "0",
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "is_item_from_hub",
    "fieldtype": "Check",
    "label": "Is Item from Hub",
@@ -238,6 +239,7 @@
   {
    "bold": 1,
    "default": "1",
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "is_stock_item",
    "fieldtype": "Check",
    "label": "Maintain Stock",
@@ -246,6 +248,7 @@
   },
   {
    "default": "1",
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "include_item_in_manufacturing",
    "fieldtype": "Check",
    "label": "Include Item In Manufacturing"
@@ -282,6 +285,7 @@
    "fieldname": "asset_category",
    "fieldtype": "Link",
    "label": "Asset Category",
+   "mandatory_depends_on": "is_fixed_asset",
    "options": "Asset Category"
   },
   {
@@ -434,8 +438,8 @@
   },
   {
    "collapsible": 1,
-   "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no || doc.is_fixed_asset",
-   "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
+   "collapsible_depends_on": "eval:doc.has_batch_no || doc.has_serial_no",
+   "depends_on": "eval:doc.is_stock_item",
    "fieldname": "serial_nos_and_batches",
    "fieldtype": "Section Break",
    "label": "Serial Nos and Batches"
@@ -492,7 +496,7 @@
   },
   {
    "default": "0",
-   "depends_on": "eval:doc.is_stock_item || doc.is_fixed_asset",
+   "depends_on": "eval:doc.is_stock_item",
    "fieldname": "has_serial_no",
    "fieldtype": "Check",
    "label": "Has Serial No",
@@ -510,6 +514,7 @@
   {
    "collapsible": 1,
    "collapsible_depends_on": "attributes",
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "variants_section",
    "fieldtype": "Section Break",
    "label": "Variants"
@@ -540,6 +545,7 @@
    "options": "Item Variant Attribute"
   },
   {
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "defaults",
    "fieldtype": "Section Break",
    "label": "Sales, Purchase, Accounting Defaults"
@@ -621,6 +627,7 @@
   },
   {
    "collapsible": 1,
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "supplier_details",
    "fieldtype": "Section Break",
    "label": "Supplier Details"
@@ -668,6 +675,7 @@
   },
   {
    "collapsible": 1,
+   "default": "eval:!doc.is_fixed_asset",
    "fieldname": "sales_details",
    "fieldtype": "Section Break",
    "label": "Sales Details",
@@ -761,6 +769,7 @@
   },
   {
    "collapsible": 1,
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "customer_details",
    "fieldtype": "Section Break",
    "label": "Customer Details"
@@ -791,6 +800,7 @@
   },
   {
    "collapsible": 1,
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "inspection_criteria",
    "fieldtype": "Section Break",
    "label": "Inspection Criteria",
@@ -861,6 +871,7 @@
   },
   {
    "collapsible": 1,
+   "depends_on": "eval:!doc.is_fixed_asset",
    "fieldname": "website_section",
    "fieldtype": "Section Break",
    "label": "Website",
@@ -987,7 +998,7 @@
   },
   {
    "collapsible": 1,
-   "depends_on": "eval:(!doc.is_item_from_hub)",
+   "depends_on": "eval:(!doc.is_item_from_hub && !doc.is_fixed_asset)",
    "fieldname": "hub_publishing_sb",
    "fieldtype": "Section Break",
    "label": "Hub Publishing Details"
@@ -1021,7 +1032,7 @@
    "read_only": 1
   },
   {
-   "depends_on": "eval:!doc.__islocal",
+   "depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
    "fieldname": "over_delivery_receipt_allowance",
    "fieldtype": "Float",
    "label": "Over Delivery/Receipt Allowance (%)",
@@ -1029,7 +1040,7 @@
    "oldfieldtype": "Currency"
   },
   {
-   "depends_on": "eval:!doc.__islocal",
+   "depends_on": "eval:!doc.__islocal && !doc.is_fixed_asset",
    "fieldname": "over_billing_allowance",
    "fieldtype": "Float",
    "label": "Over Billing Allowance (%)"
@@ -1067,7 +1078,7 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 1,
- "modified": "2021-07-13 01:29:06.071827",
+ "modified": "2021-08-26 12:23:07.277077",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
diff --git a/erpnext/stock/doctype/item/test_item.js b/erpnext/stock/doctype/item/test_item.js
deleted file mode 100644
index af44278..0000000
--- a/erpnext/stock/doctype/item/test_item.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Item", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Item
-		() => frappe.tests.make('Item', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.js b/erpnext/stock/doctype/item_alternative/test_item_alternative.js
deleted file mode 100644
index 8731849..0000000
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Item Alternative", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Item Alternative
-		() => frappe.tests.make('Item Alternative', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js
deleted file mode 100644
index 3b3bf94..0000000
--- a/erpnext/stock/doctype/item_variant_settings/test_item_variant_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Item Variant Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Item Variant Settings
-		() => frappe.tests.make('Item Variant Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/manufacturer/test_manufacturer.js b/erpnext/stock/doctype/manufacturer/test_manufacturer.js
deleted file mode 100644
index 0254a36..0000000
--- a/erpnext/stock/doctype/manufacturer/test_manufacturer.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Manufacturer", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Manufacturer', [
-		// insert a new Manufacturer
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/material_request/test_material_request.js b/erpnext/stock/doctype/material_request/test_material_request.js
deleted file mode 100644
index 793cad0..0000000
--- a/erpnext/stock/doctype/material_request/test_material_request.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Material Request", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially('Material Request', [
-		// insert a new Material Request
-		() => frappe.tests.make([
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/price_list/test_price_list.js b/erpnext/stock/doctype/price_list/test_price_list.js
deleted file mode 100644
index fe4e07b..0000000
--- a/erpnext/stock/doctype/price_list/test_price_list.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Price List", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Price List
-		() => frappe.tests.make('Price List', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.js b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.js
deleted file mode 100644
index 327484e..0000000
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quality Inspection", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Quality Inspection
-		() => frappe.tests.make('Quality Inspection', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js b/erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js
deleted file mode 100644
index 879c262..0000000
--- a/erpnext/stock/doctype/quality_inspection_template/test_quality_inspection_template.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Quality Inspection Template", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Quality Inspection Template
-		() => frappe.tests.make('Quality Inspection Template', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.js b/erpnext/stock/doctype/serial_no/test_serial_no.js
deleted file mode 100644
index bf82932..0000000
--- a/erpnext/stock/doctype/serial_no/test_serial_no.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Serial No", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Serial No
-		() => frappe.tests.make('Serial No', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 0b4592c..760cb7d 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -272,7 +272,7 @@
 		item_wise_qty = {}
 		if self.purpose == "Manufacture" and self.work_order:
 			for d in self.items:
-				if d.is_finished_item:
+				if d.is_finished_item or d.is_process_loss:
 					item_wise_qty.setdefault(d.item_code, []).append(d.qty)
 
 		for item_code, qty_list in iteritems(item_wise_qty):
@@ -333,7 +333,7 @@
 
 			if self.purpose == "Manufacture":
 				if validate_for_manufacture:
-					if d.is_finished_item or d.is_scrap_item:
+					if d.is_finished_item or d.is_scrap_item or d.is_process_loss:
 						d.s_warehouse = None
 						if not d.t_warehouse:
 							frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
@@ -465,7 +465,7 @@
 		"""
 		# Set rate for outgoing items
 		outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
-		finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
+		finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item or d.is_process_loss)
 
 		# Set basic rate for incoming items
 		for d in self.get('items'):
@@ -486,6 +486,8 @@
 					raise_error_if_no_rate=raise_error_if_no_rate)
 
 			d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
+			if d.is_process_loss:
+				d.basic_rate = flt(0.)
 			d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
 
 	def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
@@ -1043,6 +1045,7 @@
 
 		self.set_scrap_items()
 		self.set_actual_qty()
+		self.update_items_for_process_loss()
 		self.validate_customer_provided_item()
 		self.calculate_rate_and_amount()
 
@@ -1400,6 +1403,7 @@
 				get_default_cost_center(item_dict[d], company = self.company))
 			se_child.is_finished_item = item_dict[d].get("is_finished_item", 0)
 			se_child.is_scrap_item = item_dict[d].get("is_scrap_item", 0)
+			se_child.is_process_loss = item_dict[d].get("is_process_loss", 0)
 
 			for field in ["idx", "po_detail", "original_item",
 				"expense_account", "description", "item_name", "serial_no", "batch_no"]:
@@ -1578,6 +1582,29 @@
 			if material_request and material_request not in material_requests:
 				material_requests.append(material_request)
 				frappe.db.set_value('Material Request', material_request, 'transfer_status', status)
+				
+	def update_items_for_process_loss(self):
+		process_loss_dict = {}
+		for d in self.get("items"):
+			if not d.is_process_loss:
+				continue
+
+			scrap_warehouse = frappe.db.get_single_value("Manufacturing Settings", "default_scrap_warehouse")
+			if scrap_warehouse is not None:
+				d.t_warehouse = scrap_warehouse
+			d.is_scrap_item = 0
+
+			if d.item_code not in process_loss_dict:
+				process_loss_dict[d.item_code] = [flt(0), flt(0)]
+			process_loss_dict[d.item_code][0] += flt(d.transfer_qty)
+			process_loss_dict[d.item_code][1] += flt(d.qty)
+
+		for d in self.get("items"):
+			if not d.is_finished_item or d.item_code not in process_loss_dict:
+				continue
+			# Assumption: 1 finished item has 1 row.
+			d.transfer_qty -= process_loss_dict[d.item_code][0]
+			d.qty -= process_loss_dict[d.item_code][1]
 
 	def set_serial_no_batch_for_finished_good(self):
 		args = {}
diff --git a/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js
new file mode 100644
index 0000000..285ae4f
--- /dev/null
+++ b/erpnext/stock/doctype/stock_entry/tests/test_stock_entry_for_manufacture.js
@@ -0,0 +1,27 @@
+QUnit.module('Stock');
+
+QUnit.test("test manufacture from bom", function(assert) {
+	assert.expect(2);
+	let done = assert.async();
+	frappe.run_serially([
+		() => {
+			return frappe.tests.make("Stock Entry", [
+				{ purpose: "Manufacture" },
+				{ from_bom: 1 },
+				{ bom_no: "BOM-_Test Item - Non Whole UOM-001" },
+				{ fg_completed_qty: 2 }
+			]);
+		},
+		() => cur_frm.save(),
+		() => frappe.click_button("Update Rate and Availability"),
+		() => {
+			assert.ok(cur_frm.doc.items[1] === 0.75, " Finished Item Qty correct");
+			assert.ok(cur_frm.doc.items[2] === 0.25, " Process Loss Item Qty correct");
+		},
+		() => frappe.tests.click_button('Submit'),
+		() => frappe.tests.click_button('Yes'),
+		() => frappe.timeout(0.3),
+		() => done()
+	]);
+});
+
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 22f412a..df65706 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -19,6 +19,7 @@
   "is_finished_item",
   "is_scrap_item",
   "quality_inspection",
+  "is_process_loss",
   "subcontracted_item",
   "section_break_8",
   "description",
@@ -543,13 +544,19 @@
    "no_copy": 1,
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_process_loss",
+   "fieldtype": "Check",
+   "label": "Is Process Loss"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-21 16:03:18.834880",
+ "modified": "2021-06-22 16:47:11.268975",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index cda7c1d..24b7b9a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -390,7 +390,7 @@
 				sl_entries = self.merge_similar_item_serial_nos(sl_entries)
 
 			sl_entries.reverse()
-			allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
+			allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
 			self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
 
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 94b006c..e438127 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -15,6 +15,7 @@
 from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.tests.utils import change_settings
 
 
 class TestStockReconciliation(unittest.TestCase):
@@ -310,6 +311,7 @@
 		pr2.cancel()
 		pr1.cancel()
 
+	@change_settings("Stock Settings", {"allow_negative_stock": 0})
 	def test_backdated_stock_reco_future_negative_stock(self):
 		"""
 			Test if a backdated stock reco causes future negative stock and is blocked.
@@ -327,8 +329,6 @@
 		warehouse = "_Test Warehouse - _TC"
 		create_item(item_code)
 
-		negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
-		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0)
 
 		pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
 			posting_date=add_days(nowdate(), -2))
@@ -348,11 +348,50 @@
 		self.assertRaises(NegativeStockError, sr3.submit)
 
 		# teardown
-		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting)
 		sr3.cancel()
 		dn2.cancel()
 		pr1.cancel()
 
+
+	@change_settings("Stock Settings", {"allow_negative_stock": 0})
+	def test_backdated_stock_reco_cancellation_future_negative_stock(self):
+		"""
+			Test if a backdated stock reco cancellation that causes future negative stock is blocked.
+			-------------------------------------------
+			Var | Doc  | Qty | Balance
+			-------------------------------------------
+			SR  | Reco | 100 | 100     (posting date: today-1) (shouldn't be cancelled after DN)
+			DN  | DN   | 100 |   0     (posting date: today)
+		"""
+		from erpnext.stock.stock_ledger import NegativeStockError
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+		frappe.db.commit()
+
+		item_code = "Backdated-Reco-Cancellation-Item"
+		warehouse = "_Test Warehouse - _TC"
+		create_item(item_code)
+
+
+		sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=100, rate=100,
+			posting_date=add_days(nowdate(), -1))
+
+		dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=100, rate=120,
+			posting_date=nowdate())
+
+		dn_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn.name, "is_cancelled": 0},
+			"qty_after_transaction")
+		self.assertEqual(dn_balance, 0)
+
+		# check if cancellation of stock reco is blocked
+		self.assertRaises(NegativeStockError, sr.cancel)
+
+		repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
+		self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
+
+		# teardown
+		frappe.db.rollback()
+
+
 	def test_valid_batch(self):
 		create_batch_item_with_batch("Testing Batch Item 1", "001")
 		create_batch_item_with_batch("Testing Batch Item 2", "002")
diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.js b/erpnext/stock/doctype/stock_settings/test_stock_settings.js
deleted file mode 100644
index 57d9fc6..0000000
--- a/erpnext/stock/doctype/stock_settings/test_stock_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Stock Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Stock Settings
-		() => frappe.tests.make('Stock Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/uom_category/test_uom_category.js b/erpnext/stock/doctype/uom_category/test_uom_category.js
deleted file mode 100644
index 4b5972e..0000000
--- a/erpnext/stock/doctype/uom_category/test_uom_category.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: UOM Category", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new UOM Category
-		() => frappe.tests.make('UOM Category', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/doctype/variant_field/test_variant_field.js b/erpnext/stock/doctype/variant_field/test_variant_field.js
deleted file mode 100644
index 2600a10..0000000
--- a/erpnext/stock/doctype/variant_field/test_variant_field.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Variant Field", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Variant Field
-		() => frappe.tests.make('Variant Field', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/stock/form_tour/item/item.json b/erpnext/stock/form_tour/item/item.json
new file mode 100644
index 0000000..821e91b
--- /dev/null
+++ b/erpnext/stock/form_tour/item/item.json
@@ -0,0 +1,89 @@
+{
+ "creation": "2021-08-24 17:56:40.754909",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-24 18:04:50.928431",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Item",
+ "owner": "Administrator",
+ "reference_doctype": "Item",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "Enter code for Asset Item",
+   "field": "",
+   "fieldname": "item_code",
+   "fieldtype": "Data",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Item Code",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Asset Item Code"
+  },
+  {
+   "description": "Enter name for Asset Item",
+   "field": "",
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Item Name",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Asset Item Name"
+  },
+  {
+   "description": "Check this field to make this an Asset Item",
+   "field": "",
+   "fieldname": "is_fixed_asset",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Is Fixed Asset",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Is this a Fixed Asset?"
+  },
+  {
+   "description": "On checking it, the system will create an Asset automatically on purchase",
+   "field": "",
+   "fieldname": "auto_create_assets",
+   "fieldtype": "Check",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Auto Create Assets on Purchase",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Auto Create Asset on Purchase"
+  },
+  {
+   "description": "Select an Asset Category for this Asset Item",
+   "field": "",
+   "fieldname": "asset_category",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Asset Category",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Asset Category"
+  },
+  {
+   "description": "Select a naming series which will be used to create an Asset automatically",
+   "field": "",
+   "fieldname": "asset_naming_series",
+   "fieldtype": "Select",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Asset Naming Series",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Asset Naming Series"
+  }
+ ],
+ "title": "Item"
+}
diff --git a/erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json b/erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json
new file mode 100644
index 0000000..6fba3f4
--- /dev/null
+++ b/erpnext/stock/form_tour/purchase_receipt/purchase_receipt.json
@@ -0,0 +1,41 @@
+{
+ "creation": "2021-08-24 13:03:21.333088",
+ "docstatus": 0,
+ "doctype": "Form Tour",
+ "idx": 0,
+ "is_standard": 1,
+ "modified": "2021-08-24 13:03:21.333088",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Purchase Receipt",
+ "owner": "Administrator",
+ "reference_doctype": "Purchase Receipt",
+ "save_on_complete": 0,
+ "steps": [
+  {
+   "description": "Select Asset Supplier",
+   "field": "",
+   "fieldname": "supplier",
+   "fieldtype": "Link",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Supplier",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Supplier"
+  },
+  {
+   "description": "Select an Asset Item, Enter rate and quantity",
+   "field": "",
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "has_next_condition": 0,
+   "is_table_field": 0,
+   "label": "Items",
+   "parent_field": "",
+   "position": "Bottom",
+   "title": "Items"
+  }
+ ],
+ "title": "Purchase Receipt"
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
index da593a4..7532d02 100644
--- a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -43,13 +43,13 @@
 def get_columns() -> Columns:
 	return [
 		{
-			'label': 'Item Group',
+			'label': _('Item Group'),
 			'fieldname': 'item_group',
 			'fieldtype': 'Data',
 			'width': '200'
 		},
 		{
-			'label': 'COGS Debit',
+			'label': _('COGS Debit'),
 			'fieldname': 'cogs_debit',
 			'fieldtype': 'Currency',
 			'width': '200'
diff --git a/erpnext/hr/doctype/employee_onboarding_activity/__init__.py b/erpnext/stock/report/process_loss_report/__init__.py
similarity index 100%
copy from erpnext/hr/doctype/employee_onboarding_activity/__init__.py
copy to erpnext/stock/report/process_loss_report/__init__.py
diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.js b/erpnext/stock/report/process_loss_report/process_loss_report.js
new file mode 100644
index 0000000..b0c2b94
--- /dev/null
+++ b/erpnext/stock/report/process_loss_report/process_loss_report.js
@@ -0,0 +1,44 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Process Loss Report"] = {
+	filters: [
+    {
+      label: __("Company"),
+      fieldname: "company",
+      fieldtype: "Link",
+      options: "Company",
+      mandatory: true,
+      default: frappe.defaults.get_user_default("Company"),
+    },
+		{
+      label: __("Item"),
+      fieldname: "item",
+      fieldtype: "Link",
+      options: "Item",
+      mandatory: false,
+		},
+		{
+      label: __("Work Order"),
+      fieldname: "work_order",
+      fieldtype: "Link",
+      options: "Work Order",
+      mandatory: false,
+		},
+    {
+      label: __("From Date"),
+      fieldname: "from_date",
+      fieldtype: "Date",
+      mandatory: true,
+      default: frappe.datetime.year_start(),
+    },
+    {
+      label: __("To Date"),
+      fieldname: "to_date",
+      fieldtype: "Date",
+      mandatory: true,
+      default: frappe.datetime.get_today(),
+    },
+	]
+};
diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.json b/erpnext/stock/report/process_loss_report/process_loss_report.json
new file mode 100644
index 0000000..afe4aff
--- /dev/null
+++ b/erpnext/stock/report/process_loss_report/process_loss_report.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-08-24 16:38:15.233395",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-08-24 16:38:15.233395",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Process Loss Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Work Order",
+ "report_name": "Process Loss Report",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Manufacturing User"
+  },
+  {
+   "role": "Stock User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/process_loss_report/process_loss_report.py b/erpnext/stock/report/process_loss_report/process_loss_report.py
new file mode 100644
index 0000000..3d48ebd
--- /dev/null
+++ b/erpnext/stock/report/process_loss_report/process_loss_report.py
@@ -0,0 +1,133 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from typing import Dict, List, Tuple
+
+Filters = frappe._dict
+Row = frappe._dict
+Data = List[Row]
+Columns = List[Dict[str, str]]
+QueryArgs = Dict[str, str]
+
+def execute(filters: Filters) -> Tuple[Columns, Data]:
+	columns = get_columns()
+	data = get_data(filters)
+	return columns, data
+
+def get_data(filters: Filters) -> Data:
+	query_args = get_query_args(filters)
+	data = run_query(query_args)
+	update_data_with_total_pl_value(data)
+	return data
+
+def get_columns() -> Columns:
+	return [
+		{
+			'label': _('Work Order'),
+			'fieldname': 'name',
+			'fieldtype': 'Link',
+			'options': 'Work Order',
+			'width': '200'
+		},
+		{
+			'label': _('Item'),
+			'fieldname': 'production_item',
+			'fieldtype': 'Link',
+			'options': 'Item',
+			'width': '100'
+		},
+		{
+			'label': _('Status'),
+			'fieldname': 'status',
+			'fieldtype': 'Data',
+			'width': '100'
+		},
+		{
+			'label': _('Manufactured Qty'),
+			'fieldname': 'produced_qty',
+			'fieldtype': 'Float',
+			'width': '150'
+		},
+		{
+			'label': _('Loss Qty'),
+			'fieldname': 'process_loss_qty',
+			'fieldtype': 'Float',
+			'width': '150'
+		},
+		{
+			'label': _('Actual Manufactured Qty'),
+			'fieldname': 'actual_produced_qty',
+			'fieldtype': 'Float',
+			'width': '150'
+		},
+		{
+			'label': _('Loss Value'),
+			'fieldname': 'total_pl_value',
+			'fieldtype': 'Float',
+			'width': '150'
+		},
+		{
+			'label': _('FG Value'),
+			'fieldname': 'total_fg_value',
+			'fieldtype': 'Float',
+			'width': '150'
+		},
+		{
+			'label': _('Raw Material Value'),
+			'fieldname': 'total_rm_value',
+			'fieldtype': 'Float',
+			'width': '150'
+		}
+	]
+
+def get_query_args(filters: Filters) -> QueryArgs:
+	query_args = {}
+	query_args.update(filters)
+	query_args.update(
+		get_filter_conditions(filters)
+	)
+	return query_args
+
+def run_query(query_args: QueryArgs) -> Data:
+	return frappe.db.sql("""
+		SELECT 
+			wo.name, wo.status, wo.production_item, wo.qty,
+			wo.produced_qty, wo.process_loss_qty,
+			(wo.produced_qty - wo.process_loss_qty) as actual_produced_qty,
+			sum(se.total_incoming_value) as total_fg_value,
+			sum(se.total_outgoing_value) as total_rm_value
+		FROM
+			`tabWork Order` wo INNER JOIN `tabStock Entry` se
+			ON wo.name=se.work_order
+		WHERE
+			process_loss_qty > 0
+			AND wo.company = %(company)s
+			AND se.docstatus = 1
+			AND se.posting_date BETWEEN %(from_date)s AND %(to_date)s
+			{item_filter}
+			{work_order_filter}
+		GROUP BY
+			se.work_order
+	""".format(**query_args), query_args, as_dict=1, debug=1)
+
+def update_data_with_total_pl_value(data: Data) -> None:
+	for row in data:
+		value_per_unit_fg = row['total_fg_value'] / row['actual_produced_qty']
+		row['total_pl_value'] = row['process_loss_qty'] * value_per_unit_fg
+
+def get_filter_conditions(filters: Filters) -> QueryArgs:
+	filter_conditions = dict(item_filter="", work_order_filter="")
+	if "item" in filters:
+		production_item = filters.get("item")
+		filter_conditions.update(
+			{"item_filter": f"AND wo.production_item='{production_item}'"}
+		)
+	if "work_order" in filters:
+		work_order_name = filters.get("work_order")
+		filter_conditions.update(
+			{"work_order_filter": f"AND wo.name='{work_order_name}'"}
+		)
+	return filter_conditions
+
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index 623dc2f..8a9f0a5 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -26,7 +26,7 @@
 		average_age = get_average_age(fifo_queue, to_date)
 		earliest_age = date_diff(to_date, fifo_queue[0][1])
 		latest_age = date_diff(to_date, fifo_queue[-1][1])
-		range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date)
+		range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
 
 		row = [details.name, details.item_name,
 			details.description, details.item_group, details.brand]
@@ -58,19 +58,21 @@
 
 	return flt(age_qty / total_qty, 2) if total_qty else 0.0
 
-def get_range_age(filters, fifo_queue, to_date):
+def get_range_age(filters, fifo_queue, to_date, item_dict):
 	range1 = range2 = range3 = above_range3 = 0.0
+
 	for item in fifo_queue:
 		age = date_diff(to_date, item[1])
+		qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
 
 		if age <= filters.range1:
-			range1 += flt(item[0])
+			range1 += qty
 		elif age <= filters.range2:
-			range2 += flt(item[0])
+			range2 += qty
 		elif age <= filters.range3:
-			range3 += flt(item[0])
+			range3 += qty
 		else:
-			above_range3 += flt(item[0])
+			above_range3 += qty
 
 	return range1, range2, range3, above_range3
 
@@ -197,9 +199,7 @@
 					fifo_queue.append([d.actual_qty, d.posting_date])
 		else:
 			if serial_no_list:
-				for serial_no in fifo_queue:
-					if serial_no[0] in serial_no_list:
-						fifo_queue.remove(serial_no)
+				fifo_queue[:] = [serial_no for serial_no in fifo_queue if serial_no[0] not in serial_no_list]
 			else:
 				qty_to_pop = abs(d.actual_qty)
 				while qty_to_pop:
@@ -222,14 +222,16 @@
 		else:
 			item_details[key]["total_qty"] += d.actual_qty
 
+		item_details[key]["has_serial_no"] = d.has_serial_no
+
 	return item_details
 
 def get_stock_ledger_entries(filters):
 	return frappe.db.sql("""select
-			item.name, item.item_name, item_group, brand, description, item.stock_uom,
+			item.name, item.item_name, item_group, brand, description, item.stock_uom, item.has_serial_no,
 			actual_qty, posting_date, voucher_type, voucher_no, serial_no, batch_no, qty_after_transaction, warehouse
 		from `tabStock Ledger Entry` sle,
-			(select name, item_name, description, stock_uom, brand, item_group
+			(select name, item_name, description, stock_uom, brand, item_group, has_serial_no
 				from `tabItem` {item_conditions}) item
 		where item_code = item.name and
 			company = %(company)s and
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index eddd048..840ca3e 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -279,13 +279,15 @@
 			}
 
 		"""
+		self.data.setdefault(args.warehouse, frappe._dict())
+		warehouse_dict = self.data[args.warehouse]
 		previous_sle = get_previous_sle_of_current_voucher(args)
+		warehouse_dict.previous_sle = previous_sle
 
-		self.data[args.warehouse] = frappe._dict({
-			"previous_sle": previous_sle,
-			"qty_after_transaction": flt(previous_sle.qty_after_transaction),
-			"valuation_rate": flt(previous_sle.valuation_rate),
-			"stock_value": flt(previous_sle.stock_value),
+		for key in ("qty_after_transaction", "valuation_rate", "stock_value"):
+			setattr(warehouse_dict, key, flt(previous_sle.get(key)))
+
+		warehouse_dict.update({
 			"prev_stock_value": previous_sle.stock_value or 0.0,
 			"stock_queue": json.loads(previous_sle.stock_queue or "[]"),
 			"stock_value_difference": 0.0
@@ -332,6 +334,7 @@
 			where
 				item_code = %(item_code)s
 				and warehouse = %(warehouse)s
+				and is_cancelled = 0
 				and timestamp(posting_date, time_format(posting_time, %(time_format)s)) = timestamp(%(posting_date)s, time_format(%(posting_time)s, %(time_format)s))
 
 			order by
@@ -954,7 +957,7 @@
 
 	return valuation_rate
 
-def update_qty_in_future_sle(args, allow_negative_stock=None):
+def update_qty_in_future_sle(args, allow_negative_stock=False):
 	"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
 	datetime_limit_condition = ""
 	qty_shift = args.actual_qty
@@ -1043,8 +1046,8 @@
 			)
 		)"""
 
-def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
-	allow_negative_stock = allow_negative_stock \
+def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
+	allow_negative_stock = cint(allow_negative_stock) \
 		or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
 
 	if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock:
diff --git a/erpnext/support/doctype/support_settings/test_support_settings.js b/erpnext/support/doctype/support_settings/test_support_settings.js
deleted file mode 100644
index 0787306..0000000
--- a/erpnext/support/doctype/support_settings/test_support_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Support Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Support Settings
-		() => frappe.tests.make('Support Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/yarn.lock b/yarn.lock
index 82e9821..0a49c52 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3244,9 +3244,9 @@
     readable-stream "^3.1.1"
 
 tar@^6.1.0:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
-  integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
+  version "6.1.11"
+  resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
+  integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
   dependencies:
     chownr "^2.0.0"
     fs-minipass "^2.0.0"