Merge branch 'develop' into emp-adv-status
diff --git a/.flake8 b/.flake8
index 56c9b9a..5735456 100644
--- a/.flake8
+++ b/.flake8
@@ -28,6 +28,7 @@
     B007,
     B950,
     W191,
+    E124, # closing bracket, irritating while writing QB code
 
 max-line-length = 200
 exclude=.github/helper/semgrep_rules
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 9031968..859146b 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -8,7 +8,10 @@
 
 pip install frappe-bench
 
-git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
+frappeuser=${FRAPPE_USER:-"frappe"}
+frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
+
+git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
 
 mkdir ~/frappe-bench/sites/test_site
@@ -37,10 +40,14 @@
     echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres;
 fi
 
-wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
-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
+
+install_whktml() {
+    wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
+    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
+}
+install_whktml &
 
 cd ~/frappe-bench || exit
 
@@ -54,5 +61,5 @@
 if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
 
 bench start &> bench_run_logs.txt &
+CI=Yes bench build --app frappe &
 bench --site test_site reinstall --yes
-bench build --app frappe
diff --git a/.github/stale.yml b/.github/stale.yml
index 8b7cb9b..1c2dcf3 100644
--- a/.github/stale.yml
+++ b/.github/stale.yml
@@ -30,6 +30,7 @@
   exemptLabels:
     - valid
     - to-validate
+    - QA
   markComment: >
     This issue has been automatically marked as inactive because it has not had
     recent activity and it wasn't validated by maintainer team. It will be
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 7347a58..40f9365 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -6,12 +6,23 @@
       - '**.js'
       - '**.md'
       - '**.html'
-  workflow_dispatch:
   push:
     branches: [ develop ]
     paths-ignore:
       - '**.js'
       - '**.md'
+  workflow_dispatch:
+    inputs:
+      user:
+        description: 'user'
+        required: true
+        default: 'frappe'
+        type: string
+      branch:
+        description: 'Branch name'
+        default: 'develop'
+        required: false
+        type: string
 
 concurrency:
   group: server-mariadb-develop-${{ github.event.number }}
@@ -95,6 +106,8 @@
         env:
           DB: mariadb
           TYPE: server
+          FRAPPE_USER: ${{ github.event.inputs.user }}
+          FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
 
       - name: Run Tests
         run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
diff --git a/.mergify.yml b/.mergify.yml
index f3d0409..b7d1df4 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -14,9 +14,39 @@
       close:
       comment:
           message: |
-            @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch. 
+            @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
             https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
 
+  - name: backport to develop
+    conditions:
+      - label="backport develop"
+    actions:
+      backport:
+        branches:
+          - develop
+        assignees:
+          - "{{ author }}"
+
+  - name: backport to version-14-hotfix
+    conditions:
+      - label="backport version-14-hotfix"
+    actions:
+      backport:
+        branches:
+          - version-14-hotfix
+        assignees:
+          - "{{ author }}"
+
+  - name: backport to version-14-pre-release
+    conditions:
+      - label="backport version-14-pre-release"
+    actions:
+      backport:
+        branches:
+          - version-14-pre-release
+        assignees:
+          - "{{ author }}"
+
   - name: backport to version-13-hotfix
     conditions:
       - label="backport version-13-hotfix"
@@ -55,4 +85,4 @@
         branches:
           - version-12-pre-release
         assignees:
-          - "{{ author }}"
\ No newline at end of file
+          - "{{ author }}"
diff --git a/cypress/integration/test_bulk_transaction_processing.js b/cypress/integration/test_bulk_transaction_processing.js
new file mode 100644
index 0000000..428ec51
--- /dev/null
+++ b/cypress/integration/test_bulk_transaction_processing.js
@@ -0,0 +1,44 @@
+describe("Bulk Transaction Processing", () => {
+	before(() => {
+		cy.login();
+		cy.visit("/app/website");
+	});
+
+	it("Creates To Sales Order", () => {
+		cy.visit("/app/sales-order");
+		cy.url().should("include", "/sales-order");
+		cy.window()
+			.its("frappe.csrf_token")
+			.then((csrf_token) => {
+				return cy
+					.request({
+						url: "/api/method/erpnext.tests.ui_test_bulk_transaction_processing.create_records",
+						method: "POST",
+						headers: {
+							Accept: "application/json",
+							"Content-Type": "application/json",
+							"X-Frappe-CSRF-Token": csrf_token,
+						},
+						timeout: 60000,
+					})
+					.then((res) => {
+						expect(res.status).eq(200);
+					});
+			});
+		cy.wait(5000);
+		cy.get(
+			".list-row-head > .list-header-subject > .list-row-col > .list-check-all"
+		).check({ force: true });
+		cy.wait(3000);
+		cy.get(".actions-btn-group > .btn-primary").click({ force: true });
+		cy.wait(3000);
+		cy.get(".dropdown-menu-right > .user-action > .dropdown-item")
+			.contains("Sales Invoice")
+			.click({ force: true });
+		cy.wait(3000);
+		cy.get(".modal-content > .modal-footer > .standard-actions")
+			.contains("Yes")
+			.click({ force: true });
+		cy.contains("Creation of Sales Invoice successful");
+	});
+});
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index 0b4696c..dcfad1f 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -2,8 +2,6 @@
 
 import frappe
 
-from erpnext.hooks import regional_overrides
-
 __version__ = '14.0.0-dev'
 
 def get_default_company(user=None):
@@ -121,20 +119,15 @@
 	@erpnext.allow_regional
 	def myfunction():
 	  pass'''
+
 	def caller(*args, **kwargs):
-		region = get_region()
-		fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
-		if region in regional_overrides and fn_name in regional_overrides[region]:
-			return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
-		else:
+		overrides = frappe.get_hooks("regional_overrides", {}).get(get_region())
+		function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}"
+
+		if not overrides or function_path not in overrides:
 			return fn(*args, **kwargs)
 
+		# Priority given to last installed app
+		return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
+
 	return caller
-
-def get_last_membership(member):
-	'''Returns last membership if exists'''
-	last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
-		dict(member=member, paid=1), order_by='to_date desc', limit=1)
-
-	if last_membership:
-		return last_membership[0]
diff --git a/erpnext/accounts/deferred_revenue.py b/erpnext/accounts/deferred_revenue.py
index 9e2cdff..ab1061b 100644
--- a/erpnext/accounts/deferred_revenue.py
+++ b/erpnext/accounts/deferred_revenue.py
@@ -120,6 +120,7 @@
 	prev_gl_entry = frappe.db.sql('''
 		select name, posting_date from `tabGL Entry` where company=%s and account=%s and
 		voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+		and is_cancelled = 0
 		order by posting_date desc limit 1
 	''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
 
@@ -227,6 +228,7 @@
 	gl_entries_details = frappe.db.sql('''
 		select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
 		from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
+		and is_cancelled = 0
 		group by voucher_detail_no
 	'''.format(total_credit_debit, total_credit_debit_currency),
 		(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
@@ -282,7 +284,7 @@
 			return
 
 		# check if books nor frozen till endate:
-		if getdate(end_date) >= getdate(accounts_frozen_upto):
+		if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
 			end_date = get_last_day(add_days(accounts_frozen_upto, 1))
 
 		if via_journal_entry:
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 55ea571..9a35a24 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -7,35 +7,30 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "accounts_transactions_settings_section",
-  "over_billing_allowance",
-  "role_allowed_to_over_bill",
-  "credit_controller",
-  "make_payment_via_journal_entry",
-  "column_break_11",
-  "check_supplier_invoice_uniqueness",
+  "invoice_and_billing_tab",
+  "enable_features_section",
   "unlink_payment_on_cancellation_of_invoice",
-  "automatically_fetch_payment_terms",
-  "delete_linked_ledger_entries",
-  "book_asset_depreciation_entry_automatically",
   "unlink_advance_payment_on_cancelation_of_order",
+  "column_break_13",
+  "delete_linked_ledger_entries",
+  "invoicing_features_section",
+  "check_supplier_invoice_uniqueness",
+  "automatically_fetch_payment_terms",
+  "column_break_17",
   "enable_common_party_accounting",
-  "post_change_gl_entries",
   "enable_discount_accounting",
-  "tax_settings_section",
-  "determine_address_tax_category_from",
-  "column_break_19",
-  "add_taxes_from_item_tax_template",
-  "period_closing_settings_section",
-  "acc_frozen_upto",
-  "frozen_accounts_modifier",
-  "column_break_4",
+  "report_setting_section",
+  "use_custom_cash_flow",
   "deferred_accounting_settings_section",
   "book_deferred_entries_based_on",
   "column_break_18",
   "automatically_process_deferred_accounting_entry",
   "book_deferred_entries_via_journal_entry",
   "submit_journal_entries",
+  "tax_settings_section",
+  "determine_address_tax_category_from",
+  "column_break_19",
+  "add_taxes_from_item_tax_template",
   "print_settings",
   "show_inclusive_tax_in_print",
   "column_break_12",
@@ -43,8 +38,25 @@
   "currency_exchange_section",
   "allow_stale",
   "stale_days",
-  "report_settings_sb",
-  "use_custom_cash_flow"
+  "invoicing_settings_tab",
+  "accounts_transactions_settings_section",
+  "over_billing_allowance",
+  "column_break_11",
+  "role_allowed_to_over_bill",
+  "credit_controller",
+  "make_payment_via_journal_entry",
+  "pos_tab",
+  "pos_setting_section",
+  "post_change_gl_entries",
+  "assets_tab",
+  "asset_settings_section",
+  "book_asset_depreciation_entry_automatically",
+  "closing_settings_tab",
+  "period_closing_settings_section",
+  "acc_frozen_upto",
+  "column_break_25",
+  "frozen_accounts_modifier",
+  "report_settings_sb"
  ],
  "fields": [
   {
@@ -71,10 +83,6 @@
    "options": "Billing Address\nShipping Address"
   },
   {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "credit_controller",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -83,6 +91,7 @@
   },
   {
    "default": "0",
+   "description": "Enabling ensure each Sales Invoice has a unique value in Supplier Invoice No. field",
    "fieldname": "check_supplier_invoice_uniqueness",
    "fieldtype": "Check",
    "label": "Check Supplier Invoice Number Uniqueness"
@@ -168,7 +177,7 @@
    "description": "Only select this if you have set up the Cash Flow Mapper documents",
    "fieldname": "use_custom_cash_flow",
    "fieldtype": "Check",
-   "label": "Use Custom Cash Flow Format"
+   "label": "Enable Custom Cash Flow Format"
   },
   {
    "default": "0",
@@ -241,7 +250,7 @@
   {
    "fieldname": "accounts_transactions_settings_section",
    "fieldtype": "Section Break",
-   "label": "Transactions Settings"
+   "label": "Credit Limit Settings"
   },
   {
    "fieldname": "column_break_11",
@@ -272,9 +281,72 @@
   },
   {
    "default": "0",
+   "description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
    "fieldname": "enable_common_party_accounting",
    "fieldtype": "Check",
    "label": "Enable Common Party Accounting"
+  },
+  {
+   "fieldname": "enable_features_section",
+   "fieldtype": "Section Break",
+   "label": "Invoice Cancellation"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_25",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "asset_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Asset Settings"
+  },
+  {
+   "fieldname": "invoicing_settings_tab",
+   "fieldtype": "Tab Break",
+   "label": "Credit Limits"
+  },
+  {
+   "fieldname": "assets_tab",
+   "fieldtype": "Tab Break",
+   "label": "Assets"
+  },
+  {
+   "fieldname": "closing_settings_tab",
+   "fieldtype": "Tab Break",
+   "label": "Accounts Closing"
+  },
+  {
+   "fieldname": "pos_setting_section",
+   "fieldtype": "Section Break",
+   "label": "POS Setting"
+  },
+  {
+   "fieldname": "invoice_and_billing_tab",
+   "fieldtype": "Tab Break",
+   "label": "Invoice and Billing"
+  },
+  {
+   "fieldname": "invoicing_features_section",
+   "fieldtype": "Section Break",
+   "label": "Invoicing Features"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "pos_tab",
+   "fieldtype": "Tab Break",
+   "label": "POS"
+  },
+  {
+   "fieldname": "report_setting_section",
+   "fieldtype": "Section Break",
+   "label": "Report Setting"
   }
  ],
  "icon": "icon-cog",
@@ -282,7 +354,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-10-11 17:42:36.427699",
+ "modified": "2022-02-04 12:32:36.805652",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
@@ -309,5 +381,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index 335f850..46ba27c 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -14,6 +14,10 @@
 		});
 	},
 
+	onload: function (frm) {
+		frm.trigger('bank_account');
+	},
+
 	refresh: function (frm) {
 		frappe.require("bank-reconciliation-tool.bundle.js", () =>
 			frm.trigger("make_reconciliation_tool")
@@ -51,7 +55,7 @@
 	bank_account: function (frm) {
 		frappe.db.get_value(
 			"Bank Account",
-			frm.bank_account,
+			frm.doc.bank_account,
 			"account",
 			(r) => {
 				frappe.db.get_value(
@@ -60,6 +64,7 @@
 					"account_currency",
 					(r) => {
 						frm.currency = r.account_currency;
+						frm.trigger("render_chart");
 					}
 				);
 			}
@@ -124,7 +129,7 @@
 		}
 	},
 
-	render_chart(frm) {
+	render_chart: frappe.utils.debounce((frm) => {
 		frm.cards_manager = new erpnext.accounts.bank_reconciliation.NumberCardManager(
 			{
 				$reconciliation_tool_cards: frm.get_field(
@@ -136,7 +141,7 @@
 				currency: frm.currency,
 			}
 		);
-	},
+	}, 500),
 
 	render(frm) {
 		if (frm.doc.bank_account) {
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 4211bd0..f3351dd 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -7,6 +7,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import flt
 
 from erpnext import get_company_currency
@@ -275,6 +276,10 @@
 		}
 
 	matching_vouchers = []
+
+	matching_vouchers.extend(get_loan_vouchers(bank_account, transaction,
+		document_types, filters))
+
 	for query in subquery:
 		matching_vouchers.extend(
 			frappe.db.sql(query, filters,)
@@ -311,6 +316,114 @@
 
 	return queries
 
+def get_loan_vouchers(bank_account, transaction, document_types, filters):
+	vouchers = []
+	amount_condition = True if "exact_match" in document_types else False
+
+	if transaction.withdrawal > 0 and "loan_disbursement" in document_types:
+		vouchers.extend(get_ld_matching_query(bank_account, amount_condition, filters))
+
+	if transaction.deposit > 0 and "loan_repayment" in document_types:
+		vouchers.extend(get_lr_matching_query(bank_account, amount_condition, filters))
+
+	return vouchers
+
+def get_ld_matching_query(bank_account, amount_condition, filters):
+	loan_disbursement = frappe.qb.DocType("Loan Disbursement")
+	matching_reference = loan_disbursement.reference_number == filters.get("reference_number")
+	matching_party = loan_disbursement.applicant_type == filters.get("party_type") and \
+			loan_disbursement.applicant == filters.get("party")
+
+	rank = (
+			frappe.qb.terms.Case()
+			.when(matching_reference, 1)
+			.else_(0)
+		)
+
+	rank1 = (
+			frappe.qb.terms.Case()
+			.when(matching_party, 1)
+			.else_(0)
+		)
+
+	query = frappe.qb.from_(loan_disbursement).select(
+		rank + rank1 + 1,
+		ConstantColumn("Loan Disbursement").as_("doctype"),
+		loan_disbursement.name,
+		loan_disbursement.disbursed_amount,
+		loan_disbursement.reference_number,
+		loan_disbursement.reference_date,
+		loan_disbursement.applicant_type,
+		loan_disbursement.disbursement_date
+	).where(
+		loan_disbursement.docstatus == 1
+	).where(
+		loan_disbursement.clearance_date.isnull()
+	).where(
+		loan_disbursement.disbursement_account == bank_account
+	)
+
+	if amount_condition:
+		query.where(
+			loan_disbursement.disbursed_amount == filters.get('amount')
+		)
+	else:
+		query.where(
+			loan_disbursement.disbursed_amount <= filters.get('amount')
+		)
+
+	vouchers = query.run(as_list=True)
+
+	return vouchers
+
+def get_lr_matching_query(bank_account, amount_condition, filters):
+	loan_repayment = frappe.qb.DocType("Loan Repayment")
+	matching_reference = loan_repayment.reference_number == filters.get("reference_number")
+	matching_party = loan_repayment.applicant_type == filters.get("party_type") and \
+			loan_repayment.applicant == filters.get("party")
+
+	rank = (
+			frappe.qb.terms.Case()
+			.when(matching_reference, 1)
+			.else_(0)
+		)
+
+	rank1 = (
+			frappe.qb.terms.Case()
+			.when(matching_party, 1)
+			.else_(0)
+		)
+
+	query = frappe.qb.from_(loan_repayment).select(
+		rank + rank1 + 1,
+		ConstantColumn("Loan Repayment").as_("doctype"),
+		loan_repayment.name,
+		loan_repayment.amount_paid,
+		loan_repayment.reference_number,
+		loan_repayment.reference_date,
+		loan_repayment.applicant_type,
+		loan_repayment.posting_date
+	).where(
+		loan_repayment.docstatus == 1
+	).where(
+		loan_repayment.clearance_date.isnull()
+	).where(
+		loan_repayment.payment_account == bank_account
+	)
+
+	if amount_condition:
+		query.where(
+			loan_repayment.amount_paid == filters.get('amount')
+		)
+	else:
+		query.where(
+			loan_repayment.amount_paid <= filters.get('amount')
+		)
+
+	vouchers = query.run()
+
+	return vouchers
+
 def get_pe_matching_query(amount_condition, account_from_to, transaction):
 	# get matching payment entries query
 	if transaction.deposit > 0:
@@ -348,7 +461,6 @@
 	# We have mapping at the bank level
 	# So one bank could have both types of bank accounts like asset and liability
 	# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
-	company_account = frappe.get_value("Bank Account", transaction.bank_account, "account")
 	cr_or_dr = "credit" if transaction.withdrawal > 0 else "debit"
 
 	return f"""
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index e786d13..1403303 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -16,6 +16,7 @@
 from openpyxl.styles import Font
 from openpyxl.utils import get_column_letter
 
+INVALID_VALUES = ("", None)
 
 class BankStatementImport(DataImport):
 	def __init__(self, *args, **kwargs):
@@ -95,6 +96,18 @@
 	data_import = frappe.get_doc("Bank Statement Import", data_import_name)
 	data_import.export_errored_rows()
 
+def parse_data_from_template(raw_data):
+	data = []
+
+	for i, row in enumerate(raw_data):
+		if all(v in INVALID_VALUES for v in row):
+			# empty row
+			continue
+
+		data.append(row)
+
+	return data
+
 def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
 	"""This method runs in background job"""
 
@@ -104,7 +117,8 @@
 	file = import_file_path if import_file_path else google_sheets_url
 
 	import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
-	data = import_file.raw_data
+
+	data = parse_data_from_template(import_file.raw_data)
 
 	if import_file_path:
 		add_bank_account(data, bank_account)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 44cea31..a476cab 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -2,9 +2,10 @@
 # For license information, please see license.txt
 
 
+from functools import reduce
+
 import frappe
 from frappe.utils import flt
-from six.moves import reduce
 
 from erpnext.controllers.status_updater import StatusUpdater
 
@@ -48,7 +49,8 @@
 
 	def clear_linked_payment_entries(self, for_cancel=False):
 		for payment_entry in self.payment_entries:
-			if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim"]:
+			if payment_entry.payment_document in ["Payment Entry", "Journal Entry", "Purchase Invoice", "Expense Claim", "Loan Repayment",
+				"Loan Disbursement"]:
 				self.clear_simple_entry(payment_entry, for_cancel=for_cancel)
 
 			elif payment_entry.payment_document == "Sales Invoice":
@@ -115,11 +117,18 @@
 			payment_entry.payment_entry, paid_amount_field)
 
 	elif payment_entry.payment_document == "Journal Entry":
-		return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account}, "sum(credit_in_account_currency)")
+		return frappe.db.get_value('Journal Entry Account', {'parent': payment_entry.payment_entry, 'account': bank_account},
+			"sum(credit_in_account_currency)")
 
 	elif payment_entry.payment_document == "Expense Claim":
 		return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "total_amount_reimbursed")
 
+	elif payment_entry.payment_document == "Loan Disbursement":
+		return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "disbursed_amount")
+
+	elif payment_entry.payment_document == "Loan Repayment":
+		return frappe.db.get_value(payment_entry.payment_document, payment_entry.payment_entry, "amount_paid")
+
 	else:
 		frappe.throw("Please reconcile {0}: {1} manually".format(payment_entry.payment_document, payment_entry.payment_entry))
 
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 72b6893..d84b8e0 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -109,7 +109,7 @@
 		frappe.get_doc({
 			"doctype": "Bank",
 			"bank_name":bank_name,
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
@@ -119,7 +119,7 @@
 			"account_name":"Checking Account",
 			"bank": bank_name,
 			"account": account_name
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
@@ -184,7 +184,7 @@
 			"supplier_group":"All Supplier Groups",
 			"supplier_type": "Company",
 			"supplier_name": "Conrad Electronic"
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 
 	except frappe.DuplicateEntryError:
 		pass
@@ -203,7 +203,7 @@
 			"supplier_group":"All Supplier Groups",
 			"supplier_type": "Company",
 			"supplier_name": "Mr G"
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
@@ -227,7 +227,7 @@
 			"supplier_group":"All Supplier Groups",
 			"supplier_type": "Company",
 			"supplier_name": "Poore Simon's"
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
@@ -237,7 +237,7 @@
 			"customer_group":"All Customer Groups",
 			"customer_type": "Company",
 			"customer_name": "Poore Simon's"
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
@@ -266,7 +266,7 @@
 			"customer_group":"All Customer Groups",
 			"customer_type": "Company",
 			"customer_name": "Fayva"
-		}).insert()
+		}).insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 
diff --git a/erpnext/accounts/doctype/cash_flow_mapping_template_details/cash_flow_mapping_template_details.json b/erpnext/accounts/doctype/cash_flow_mapping_template_details/cash_flow_mapping_template_details.json
index 22cf797..02c6875 100644
--- a/erpnext/accounts/doctype/cash_flow_mapping_template_details/cash_flow_mapping_template_details.json
+++ b/erpnext/accounts/doctype/cash_flow_mapping_template_details/cash_flow_mapping_template_details.json
@@ -1,94 +1,34 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "field:mapping", 
- "beta": 0, 
- "creation": "2018-02-08 10:18:48.513608", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2018-02-08 10:18:48.513608",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "mapping"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "mapping", 
-   "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": "Mapping", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Cash Flow Mapping", 
-   "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": "mapping",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Mapping",
+   "options": "Cash Flow Mapping",
+   "reqd": 1,
+   "unique": 1
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-02-08 10:33:39.413930", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Cash Flow Mapping Template Details", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-02-21 03:34:57.902332",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Cash Flow Mapping Template Details",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.js b/erpnext/accounts/doctype/cost_center/cost_center.js
index ee23b1b..632fab0 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.js
+++ b/erpnext/accounts/doctype/cost_center/cost_center.js
@@ -15,17 +15,6 @@
 				}
 			}
 		});
-
-		frm.set_query("cost_center", "distributed_cost_center", function() {
-			return {
-				filters: {
-					company: frm.doc.company,
-					is_group: 0,
-					enable_distributed_cost_center: 0,
-					name: ['!=', frm.doc.name]
-				}
-			};
-		});
 	},
 	refresh: function(frm) {
 		if (!frm.is_new()) {
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.json b/erpnext/accounts/doctype/cost_center/cost_center.json
index e7fa954..7cbb290 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.json
+++ b/erpnext/accounts/doctype/cost_center/cost_center.json
@@ -16,9 +16,6 @@
   "cb0",
   "is_group",
   "disabled",
-  "section_break_9",
-  "enable_distributed_cost_center",
-  "distributed_cost_center",
   "lft",
   "rgt",
   "old_parent"
@@ -122,31 +119,13 @@
    "fieldname": "disabled",
    "fieldtype": "Check",
    "label": "Disabled"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_distributed_cost_center",
-   "fieldtype": "Check",
-   "label": "Enable Distributed Cost Center"
-  },
-  {
-   "depends_on": "eval:doc.is_group==0",
-   "fieldname": "section_break_9",
-   "fieldtype": "Section Break"
-  },
-  {
-   "depends_on": "enable_distributed_cost_center",
-   "fieldname": "distributed_cost_center",
-   "fieldtype": "Table",
-   "label": "Distributed Cost Center",
-   "options": "Distributed Cost Center"
   }
  ],
  "icon": "fa fa-money",
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2020-06-17 16:09:30.025214",
+ "modified": "2022-01-31 13:22:58.916273",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Cost Center",
@@ -189,5 +168,6 @@
  "search_fields": "parent_cost_center, is_group",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 7ae0a72..07cc076 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -4,7 +4,6 @@
 
 import frappe
 from frappe import _
-from frappe.utils import cint
 from frappe.utils.nestedset import NestedSet
 
 from erpnext.accounts.utils import validate_field_number
@@ -20,24 +19,6 @@
 	def validate(self):
 		self.validate_mandatory()
 		self.validate_parent_cost_center()
-		self.validate_distributed_cost_center()
-
-	def validate_distributed_cost_center(self):
-		if cint(self.enable_distributed_cost_center):
-			if not self.distributed_cost_center:
-				frappe.throw(_("Please enter distributed cost center"))
-			if sum(x.percentage_allocation for x in self.distributed_cost_center) != 100:
-				frappe.throw(_("Total percentage allocation for distributed cost center should be equal to 100"))
-			if not self.get('__islocal'):
-				if not cint(frappe.get_cached_value("Cost Center", {"name": self.name}, "enable_distributed_cost_center")) \
-					and self.check_if_part_of_distributed_cost_center():
-					frappe.throw(_("Cannot enable Distributed Cost Center for a Cost Center already allocated in another Distributed Cost Center"))
-				if next((True for x in self.distributed_cost_center if x.cost_center == x.parent), False):
-					frappe.throw(_("Parent Cost Center cannot be added in Distributed Cost Center"))
-			if check_if_distributed_cost_center_enabled(list(x.cost_center for x in self.distributed_cost_center)):
-				frappe.throw(_("A Distributed Cost Center cannot be added in the Distributed Cost Center allocation table."))
-		else:
-			self.distributed_cost_center = []
 
 	def validate_mandatory(self):
 		if self.cost_center_name != self.company and not self.parent_cost_center:
@@ -64,10 +45,10 @@
 
 	@frappe.whitelist()
 	def convert_ledger_to_group(self):
-		if cint(self.enable_distributed_cost_center):
-			frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
-		if self.check_if_part_of_distributed_cost_center():
-			frappe.throw(_("Cost Center Already Allocated in a Distributed Cost Center cannot be converted to group"))
+		if self.if_allocation_exists_against_cost_center():
+			frappe.throw(_("Cost Center with Allocation records can not be converted to a group"))
+		if self.check_if_part_of_cost_center_allocation():
+			frappe.throw(_("Cost Center is a part of Cost Center Allocation, hence cannot be converted to a group"))
 		if self.check_gle_exists():
 			frappe.throw(_("Cost Center with existing transactions can not be converted to group"))
 		self.is_group = 1
@@ -81,8 +62,17 @@
 		return frappe.db.sql("select name from `tabCost Center` where \
 			parent_cost_center = %s and docstatus != 2", self.name)
 
-	def check_if_part_of_distributed_cost_center(self):
-		return frappe.db.get_value("Distributed Cost Center", {"cost_center": self.name})
+	def if_allocation_exists_against_cost_center(self):
+		return frappe.db.get_value("Cost Center Allocation", filters = {
+			"main_cost_center": self.name,
+			"docstatus": 1
+		})
+
+	def check_if_part_of_cost_center_allocation(self):
+		return frappe.db.get_value("Cost Center Allocation Percentage", filters = {
+			"cost_center": self.name,
+			"docstatus": 1
+		})
 
 	def before_rename(self, olddn, newdn, merge=False):
 		# Add company abbr if not provided
@@ -126,8 +116,4 @@
 def get_name_with_number(new_account, account_number):
 	if account_number and not new_account[0].isdigit():
 		new_account = account_number + " - " + new_account
-	return new_account
-
-def check_if_distributed_cost_center_enabled(cost_center_list):
-	value_list = frappe.get_list("Cost Center", {"name": ["in", cost_center_list]}, "enable_distributed_cost_center", as_list=1)
-	return next((True for x in value_list if x[0]), False)
+	return new_account
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center/test_cost_center.py b/erpnext/accounts/doctype/cost_center/test_cost_center.py
index f8615ec..ff50a21 100644
--- a/erpnext/accounts/doctype/cost_center/test_cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/test_cost_center.py
@@ -23,33 +23,6 @@
 
 		self.assertRaises(frappe.ValidationError, cost_center.save)
 
-	def test_validate_distributed_cost_center(self):
-
-		if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center - _TC'}):
-			frappe.get_doc(test_records[0]).insert()
-
-		if not frappe.db.get_value('Cost Center', {'name': '_Test Cost Center 2 - _TC'}):
-			frappe.get_doc(test_records[1]).insert()
-
-		invalid_distributed_cost_center = frappe.get_doc({
-			"company": "_Test Company",
-			"cost_center_name": "_Test Distributed Cost Center",
-			"doctype": "Cost Center",
-			"is_group": 0,
-			"parent_cost_center": "_Test Company - _TC",
-			"enable_distributed_cost_center": 1,
-			"distributed_cost_center": [{
-				"cost_center": "_Test Cost Center - _TC",
-				"percentage_allocation": 40
-				}, {
-				"cost_center": "_Test Cost Center 2 - _TC",
-				"percentage_allocation": 50
-				}
-			]
-		})
-
-		self.assertRaises(frappe.ValidationError, invalid_distributed_cost_center.save)
-
 def create_cost_center(**args):
 	args = frappe._dict(args)
 	if args.cost_center_name:
diff --git a/erpnext/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/cost_center_allocation/__init__.py
similarity index 100%
rename from erpnext/accounts/doctype/distributed_cost_center/__init__.py
rename to erpnext/accounts/doctype/cost_center_allocation/__init__.py
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js
new file mode 100644
index 0000000..ab0baab
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.js
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Cost Center Allocation', {
+	setup: function(frm) {
+		let filters = {"is_group": 0};
+		if (frm.doc.company) {
+			$.extend(filters, {
+				"company": frm.doc.company
+			});
+		}
+
+		frm.set_query('main_cost_center', function() {
+			return {
+				filters: filters
+			};
+		});
+	}
+});
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json
new file mode 100644
index 0000000..45ab886
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.json
@@ -0,0 +1,128 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "CC-ALLOC-.#####",
+ "creation": "2022-01-13 20:07:29.871109",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "main_cost_center",
+  "company",
+  "column_break_2",
+  "valid_from",
+  "section_break_5",
+  "allocation_percentages",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "main_cost_center",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Main Cost Center",
+   "options": "Cost Center",
+   "reqd": 1
+  },
+  {
+   "default": "Today",
+   "fieldname": "valid_from",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Valid From",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fetch_from": "main_cost_center.company",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  },
+  {
+   "fieldname": "allocation_percentages",
+   "fieldtype": "Table",
+   "label": "Cost Center Allocation Percentages",
+   "options": "Cost Center Allocation Percentage",
+   "reqd": 1
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Cost Center Allocation",
+   "print_hide": 1,
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-01-31 11:47:12.086253",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Cost Center Allocation",
+ "name_case": "UPPER CASE",
+ "naming_rule": "Expression (old style)",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
new file mode 100644
index 0000000..bad3fb4
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/cost_center_allocation.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import add_days, format_date, getdate
+
+
+class MainCostCenterCantBeChild(frappe.ValidationError):
+	pass
+class InvalidMainCostCenter(frappe.ValidationError):
+	pass
+class InvalidChildCostCenter(frappe.ValidationError):
+	pass
+class WrongPercentageAllocation(frappe.ValidationError):
+	pass
+class InvalidDateError(frappe.ValidationError):
+	pass
+
+class CostCenterAllocation(Document):
+	def validate(self):
+		self.validate_total_allocation_percentage()
+		self.validate_from_date_based_on_existing_gle()
+		self.validate_backdated_allocation()
+		self.validate_main_cost_center()
+		self.validate_child_cost_centers()
+
+	def validate_total_allocation_percentage(self):
+		total_percentage = sum([d.percentage for d in self.get("allocation_percentages", [])])
+
+		if total_percentage != 100:
+			frappe.throw(_("Total percentage against cost centers should be 100"), WrongPercentageAllocation)
+
+	def validate_from_date_based_on_existing_gle(self):
+		# Check if GLE exists against the main cost center
+		# If exists ensure from date is set after posting date of last GLE
+
+		last_gle_date = frappe.db.get_value("GL Entry",
+			{"cost_center": self.main_cost_center, "is_cancelled": 0},
+			"posting_date", order_by="posting_date desc")
+
+		if last_gle_date:
+			if getdate(self.valid_from) <= getdate(last_gle_date):
+				frappe.throw(_("Valid From must be after {0} as last GL Entry against the cost center {1} posted on this date")
+					.format(last_gle_date, self.main_cost_center), InvalidDateError)
+
+	def validate_backdated_allocation(self):
+		# Check if there are any future existing allocation records against the main cost center
+		# If exists, warn the user about it
+
+		future_allocation = frappe.db.get_value("Cost Center Allocation", filters = {
+			"main_cost_center": self.main_cost_center,
+			"valid_from": (">=", self.valid_from),
+			"name": ("!=", self.name),
+			"docstatus": 1
+		}, fieldname=['valid_from', 'name'], order_by='valid_from', as_dict=1)
+
+		if future_allocation:
+			frappe.msgprint(_("Another Cost Center Allocation record {0} applicable from {1}, hence this allocation will be applicable upto {2}")
+				.format(frappe.bold(future_allocation.name), frappe.bold(format_date(future_allocation.valid_from)),
+				frappe.bold(format_date(add_days(future_allocation.valid_from, -1)))),
+				title=_("Warning!"), indicator="orange", alert=1
+			)
+
+	def validate_main_cost_center(self):
+		# Main cost center itself cannot be entered in child table
+		if self.main_cost_center in [d.cost_center for d in self.allocation_percentages]:
+			frappe.throw(_("Main Cost Center {0} cannot be entered in the child table")
+				.format(self.main_cost_center), MainCostCenterCantBeChild)
+
+		# If main cost center is used for allocation under any other cost center,
+		# allocation cannot be done against it
+		parent = frappe.db.get_value("Cost Center Allocation Percentage", filters = {
+			"cost_center": self.main_cost_center,
+			"docstatus": 1
+		}, fieldname='parent')
+		if parent:
+			frappe.throw(_("{0} cannot be used as a Main Cost Center because it has been used as child in Cost Center Allocation {1}")
+				.format(self.main_cost_center, parent), InvalidMainCostCenter)
+
+	def validate_child_cost_centers(self):
+		# Check if child cost center is used as main cost center in any existing allocation
+		main_cost_centers = [d.main_cost_center for d in
+			frappe.get_all("Cost Center Allocation", {'docstatus': 1}, 'main_cost_center')]
+
+		for d in self.allocation_percentages:
+			if d.cost_center in main_cost_centers:
+				frappe.throw(_("Cost Center {0} cannot be used for allocation as it is used as main cost center in other allocation record.")
+					.format(d.cost_center), InvalidChildCostCenter)
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
new file mode 100644
index 0000000..9cf4c00
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation/test_cost_center_allocation.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+
+import frappe
+from frappe.utils import add_days, today
+
+from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+from erpnext.accounts.doctype.cost_center_allocation.cost_center_allocation import (
+	InvalidChildCostCenter,
+	InvalidDateError,
+	InvalidMainCostCenter,
+	MainCostCenterCantBeChild,
+	WrongPercentageAllocation,
+)
+from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
+
+
+class TestCostCenterAllocation(unittest.TestCase):
+	def setUp(self):
+		cost_centers = ["Main Cost Center 1", "Main Cost Center 2", "Sub Cost Center 1", "Sub Cost Center 2"]
+		for cc in cost_centers:
+			create_cost_center(cost_center_name=cc, company="_Test Company")
+
+	def test_gle_based_on_cost_center_allocation(self):
+		cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 1 - _TC": 60,
+				"Sub Cost Center 2 - _TC": 40
+			}
+		)
+
+		jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
+			cost_center = "Main Cost Center 1 - _TC", submit=True)
+
+		expected_values = [
+			["Sub Cost Center 1 - _TC", 0.0, 60],
+			["Sub Cost Center 2 - _TC", 0.0, 40]
+		]
+
+		gle = frappe.qb.DocType("GL Entry")
+		gl_entries = (
+			frappe.qb.from_(gle)
+			.select(gle.cost_center, gle.debit, gle.credit)
+			.where(gle.voucher_type == 'Journal Entry')
+			.where(gle.voucher_no == jv.name)
+			.where(gle.account == 'Sales - _TC')
+			.orderby(gle.cost_center)
+		).run(as_dict=1)
+
+		self.assertTrue(gl_entries)
+
+		for i, gle in enumerate(gl_entries):
+			self.assertEqual(expected_values[i][0], gle.cost_center)
+			self.assertEqual(expected_values[i][1], gle.debit)
+			self.assertEqual(expected_values[i][2], gle.credit)
+
+		cca.cancel()
+		jv.cancel()
+
+	def test_main_cost_center_cant_be_child(self):
+		# Main cost center itself cannot be entered in child table
+		cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 1 - _TC": 60,
+				"Main Cost Center 1 - _TC": 40
+			}, save=False
+		)
+
+		self.assertRaises(MainCostCenterCantBeChild, cca.save)
+
+	def test_invalid_main_cost_center(self):
+		# If main cost center is used for allocation under any other cost center,
+		# allocation cannot be done against it
+		cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 1 - _TC": 60,
+				"Sub Cost Center 2 - _TC": 40
+			}
+		)
+
+		cca2 = create_cost_center_allocation("_Test Company", "Sub Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 2 - _TC": 100
+			}, save=False
+		)
+
+		self.assertRaises(InvalidMainCostCenter, cca2.save)
+
+		cca1.cancel()
+
+	def test_if_child_cost_center_has_any_allocation_record(self):
+		# Check if any child cost center is used as main cost center in any other existing allocation
+		cca1 = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 1 - _TC": 60,
+				"Sub Cost Center 2 - _TC": 40
+			}
+		)
+
+		cca2 = create_cost_center_allocation("_Test Company", "Main Cost Center 2 - _TC",
+			{
+				"Main Cost Center 1 - _TC": 60,
+				"Sub Cost Center 1 - _TC": 40
+			}, save=False
+		)
+
+		self.assertRaises(InvalidChildCostCenter, cca2.save)
+
+		cca1.cancel()
+
+	def test_total_percentage(self):
+		cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 1 - _TC": 40,
+				"Sub Cost Center 2 - _TC": 40
+			}, save=False
+		)
+		self.assertRaises(WrongPercentageAllocation, cca.save)
+
+	def test_valid_from_based_on_existing_gle(self):
+		# GLE posted against Sub Cost Center 1 on today
+		jv = make_journal_entry("_Test Cash - _TC", "Sales - _TC", 100,
+			cost_center = "Main Cost Center 1 - _TC", posting_date=today(), submit=True)
+
+		# try to set valid from as yesterday
+		cca = create_cost_center_allocation("_Test Company", "Main Cost Center 1 - _TC",
+			{
+				"Sub Cost Center 1 - _TC": 60,
+				"Sub Cost Center 2 - _TC": 40
+			}, valid_from=add_days(today(), -1), save=False
+		)
+
+		self.assertRaises(InvalidDateError, cca.save)
+
+		jv.cancel()
+
+def create_cost_center_allocation(company, main_cost_center, allocation_percentages,
+		valid_from=None, valid_upto=None, save=True, submit=True):
+	doc = frappe.new_doc("Cost Center Allocation")
+	doc.main_cost_center = main_cost_center
+	doc.company = company
+	doc.valid_from = valid_from or today()
+	doc.valid_upto = valid_upto
+	for cc, percentage in allocation_percentages.items():
+		doc.append("allocation_percentages", {
+			"cost_center": cc,
+			"percentage": percentage
+		})
+	if save:
+		doc.save()
+		if submit:
+			doc.submit()
+
+	return doc
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/distributed_cost_center/__init__.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py
similarity index 100%
copy from erpnext/accounts/doctype/distributed_cost_center/__init__.py
copy to erpnext/accounts/doctype/cost_center_allocation_percentage/__init__.py
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
similarity index 62%
rename from erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
rename to erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
index 45b0e2d..7e50962 100644
--- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.json
+++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.json
@@ -1,12 +1,13 @@
 {
  "actions": [],
- "creation": "2020-03-19 12:34:01.500390",
+ "allow_rename": 1,
+ "creation": "2022-01-13 20:07:30.096306",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
   "cost_center",
-  "percentage_allocation"
+  "percentage"
  ],
  "fields": [
   {
@@ -18,23 +19,23 @@
    "reqd": 1
   },
   {
-   "fieldname": "percentage_allocation",
-   "fieldtype": "Float",
+   "fieldname": "percentage",
+   "fieldtype": "Percent",
    "in_list_view": 1,
-   "label": "Percentage Allocation",
+   "label": "Percentage (%)",
    "reqd": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-03-19 12:54:43.674655",
+ "modified": "2022-02-01 22:22:31.589523",
  "modified_by": "Administrator",
  "module": "Accounts",
- "name": "Distributed Cost Center",
+ "name": "Cost Center Allocation Percentage",
  "owner": "Administrator",
  "permissions": [],
- "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 1
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py
new file mode 100644
index 0000000..7d20efb
--- /dev/null
+++ b/erpnext/accounts/doctype/cost_center_allocation_percentage/cost_center_allocation_percentage.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class CostCenterAllocationPercentage(Document):
+	pass
diff --git a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
index 5ba0691..ca482c8 100644
--- a/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
+++ b/erpnext/accounts/doctype/coupon_code/test_coupon_code.py
@@ -39,9 +39,6 @@
 		"selling_cost_center": "Main - _TC",
 		"income_account": "Sales - _TC"
 		}],
-		"show_in_website": 1,
-		"route":"-test-tesla-car",
-		"website_warehouse": "Stores - _TC"
 		})
 		item.insert()
 	# create test item price
diff --git a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
index 77c9e95..b42d712 100644
--- a/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
+++ b/erpnext/accounts/doctype/item_tax_template/item_tax_template.json
@@ -2,7 +2,7 @@
  "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
- "creation": "2018-11-22 22:45:00.370913",
+ "creation": "2022-01-19 01:09:13.297137",
  "doctype": "DocType",
  "document_type": "Setup",
  "editable_grid": 1,
@@ -10,6 +10,9 @@
  "field_order": [
   "title",
   "company",
+  "column_break_3",
+  "disabled",
+  "section_break_5",
   "taxes"
  ],
  "fields": [
@@ -36,10 +39,24 @@
    "label": "Company",
    "options": "Company",
    "reqd": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "disabled",
+   "fieldtype": "Check",
+   "label": "Disabled"
+  },
+  {
+   "fieldname": "section_break_5",
+   "fieldtype": "Section Break"
   }
  ],
  "links": [],
- "modified": "2021-03-08 19:50:21.416513",
+ "modified": "2022-01-18 21:11:23.105589",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Item Tax Template",
@@ -82,6 +99,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "title",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 617b376..3cc28a3 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -8,6 +8,7 @@
 frappe.ui.form.on("Journal Entry", {
 	setup: function(frm) {
 		frm.add_fetch("bank_account", "account", "account");
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
 	},
 
 	refresh: function(frm) {
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index ddb833f..6e7b80e 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -135,7 +135,7 @@
 			default_uom = frappe.db.get_single_value("Stock Settings", "stock_uom") or _("Nos")
 			rate = flt(row.outstanding_amount) / flt(row.qty)
 
-			return frappe._dict({
+			item_dict = frappe._dict({
 				"uom": default_uom,
 				"rate": rate or 0.0,
 				"qty": row.qty,
@@ -146,6 +146,13 @@
 				"cost_center": cost_center
 			})
 
+			for dimension in get_accounting_dimensions():
+				item_dict.update({
+					dimension: row.get(dimension)
+				})
+
+			return item_dict
+
 		item = get_item_dict()
 
 		invoice = frappe._dict({
@@ -159,14 +166,15 @@
 			frappe.scrub(row.party_type): row.party,
 			"is_pos": 0,
 			"doctype": "Sales Invoice" if self.invoice_type == "Sales" else "Purchase Invoice",
-			"update_stock": 0,
-			"invoice_number": row.invoice_number
+			"update_stock": 0,   # important: https://github.com/frappe/erpnext/pull/23559
+			"invoice_number": row.invoice_number,
+			"disable_rounded_total": 1
 		})
 
 		accounting_dimension = get_accounting_dimensions()
 		for dimension in accounting_dimension:
 			invoice.update({
-				dimension: item.get(dimension)
+				dimension: self.get(dimension) or item.get(dimension)
 			})
 
 		return invoice
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index b5aae98..77d54a6 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -1,51 +1,49 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
-
 import frappe
-from frappe.cache_manager import clear_doctype_cache
-from frappe.custom.doctype.property_setter.property_setter import make_property_setter
+from frappe.tests.utils import FrappeTestCase
 
+from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
+	create_dimension,
+	disable_dimension,
+)
 from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import (
 	get_temporary_opening_account,
 )
 
-test_dependencies = ["Customer", "Supplier"]
+test_dependencies = ["Customer", "Supplier", "Accounting Dimension"]
 
-class TestOpeningInvoiceCreationTool(unittest.TestCase):
-	def setUp(self):
+class TestOpeningInvoiceCreationTool(FrappeTestCase):
+	@classmethod
+	def setUpClass(self):
 		if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
 			make_company()
+		create_dimension()
+		return super().setUpClass()
 
-	def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None):
+	def make_invoices(self, invoice_type="Sales", company=None, party_1=None, party_2=None, invoice_number=None, department=None):
 		doc = frappe.get_single("Opening Invoice Creation Tool")
 		args = get_opening_invoice_creation_dict(invoice_type=invoice_type, company=company,
-			party_1=party_1, party_2=party_2, invoice_number=invoice_number)
+			party_1=party_1, party_2=party_2, invoice_number=invoice_number, department=department)
 		doc.update(args)
 		return doc.make_invoices()
 
 	def test_opening_sales_invoice_creation(self):
-		property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
-		try:
-			invoices = self.make_invoices(company="_Test Opening Invoice Company")
+		invoices = self.make_invoices(company="_Test Opening Invoice Company")
 
-			self.assertEqual(len(invoices), 2)
-			expected_value = {
-				"keys": ["customer", "outstanding_amount", "status"],
-				0: ["_Test Customer", 300, "Overdue"],
-				1: ["_Test Customer 1", 250, "Overdue"],
-			}
-			self.check_expected_values(invoices, expected_value)
+		self.assertEqual(len(invoices), 2)
+		expected_value = {
+			"keys": ["customer", "outstanding_amount", "status"],
+			0: ["_Test Customer", 300, "Overdue"],
+			1: ["_Test Customer 1", 250, "Overdue"],
+		}
+		self.check_expected_values(invoices, expected_value)
 
-			si = frappe.get_doc("Sales Invoice", invoices[0])
+		si = frappe.get_doc("Sales Invoice", invoices[0])
 
-			# Check if update stock is not enabled
-			self.assertEqual(si.update_stock, 0)
-
-		finally:
-			property_setter.delete()
-			clear_doctype_cache("Sales Invoice")
+		# Check if update stock is not enabled
+		self.assertEqual(si.update_stock, 0)
 
 	def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
 		doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@@ -106,6 +104,19 @@
 			doc = frappe.get_doc('Sales Invoice', inv)
 			doc.cancel()
 
+	def test_opening_invoice_with_accounting_dimension(self):
+		invoices = self.make_invoices(invoice_type="Sales", company="_Test Opening Invoice Company", department='Sales - _TOIC')
+
+		expected_value = {
+			"keys": ["customer", "outstanding_amount", "status", "department"],
+			0: ["_Test Customer", 300, "Overdue", "Sales - _TOIC"],
+			1: ["_Test Customer 1", 250, "Overdue", "Sales - _TOIC"],
+		}
+		self.check_expected_values(invoices, expected_value, invoice_type="Sales")
+
+	def tearDown(self):
+		disable_dimension()
+
 def get_opening_invoice_creation_dict(**args):
 	party = "Customer" if args.get("invoice_type", "Sales") == "Sales" else "Supplier"
 	company = args.get("company", "_Test Company")
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 3be3925..cc32a6c 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -114,8 +114,6 @@
 				var doctypes = ["Expense Claim", "Journal Entry"];
 			} else if (frm.doc.party_type == "Student") {
 				var doctypes = ["Fees"];
-			} else if (frm.doc.party_type == "Donor") {
-				var doctypes = ["Donation"];
 			} else {
 				var doctypes = ["Journal Entry"];
 			}
@@ -144,7 +142,7 @@
 			const child = locals[cdt][cdn];
 			const filters = {"docstatus": 1, "company": doc.company};
 			const party_type_doctypes = ['Sales Invoice', 'Sales Order', 'Purchase Invoice',
-				'Purchase Order', 'Expense Claim', 'Fees', 'Dunning', 'Donation'];
+				'Purchase Order', 'Expense Claim', 'Fees', 'Dunning'];
 
 			if (in_list(party_type_doctypes, child.reference_doctype)) {
 				filters[doc.party_type.toLowerCase()] = doc.party;
@@ -196,8 +194,14 @@
 			frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
 
 		frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
-		frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
-			(frm.doc.paid_from_account_currency != company_currency));
+
+		if (frm.doc.payment_type == "Pay") {
+			frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
+				(frm.doc.paid_to_account_currency != company_currency));
+		} else {
+			frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
+				(frm.doc.paid_from_account_currency != company_currency));
+		}
 
 		frm.toggle_display("base_received_amount", (
 			frm.doc.paid_to_account_currency != company_currency
@@ -232,7 +236,8 @@
 		var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
 
 		frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
-			"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
+			"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax",
+			"base_total_taxes_and_charges"], company_currency);
 
 		frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
 		frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
@@ -747,8 +752,7 @@
 						(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
 						(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier")  ||
 						(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
-						(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
-						(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
+						(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
 					) {
 						if(total_positive_outstanding > total_negative_outstanding)
 							if (!frm.doc.paid_amount)
@@ -791,8 +795,7 @@
 				(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
 				(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Supplier") ||
 				(frm.doc.payment_type=="Pay" && frm.doc.party_type=="Employee") ||
-				(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student") ||
-				(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Donor")
+				(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Student")
 			) {
 				if(total_positive_outstanding_including_order > paid_amount) {
 					var remaining_outstanding = total_positive_outstanding_including_order - paid_amount;
@@ -951,12 +954,6 @@
 				frappe.msgprint(__("Row #{0}: Reference Document Type must be one of Expense Claim or Journal Entry", [row.idx]));
 				return false;
 			}
-
-			if (frm.doc.party_type == "Donor" && row.reference_doctype != "Donation") {
-				frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
-				frappe.msgprint(__("Row #{0}: Reference Document Type must be Donation", [row.idx]));
-				return false;
-			}
 		}
 
 		if (row) {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.json b/erpnext/accounts/doctype/payment_entry/payment_entry.json
index c8d1db9..3fc1adf 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.json
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.json
@@ -66,7 +66,9 @@
   "tax_withholding_category",
   "section_break_56",
   "taxes",
+  "section_break_60",
   "base_total_taxes_and_charges",
+  "column_break_61",
   "total_taxes_and_charges",
   "deductions_or_loss_section",
   "deductions",
@@ -715,12 +717,21 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Paid To Account Type"
+  },
+  {
+   "fieldname": "column_break_61",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_60",
+   "fieldtype": "Section Break",
+   "hide_border": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-24 18:58:24.919764",
+ "modified": "2022-02-23 20:08:39.559814",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry",
@@ -763,6 +774,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "title",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 02a144d..f9f3350 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -91,7 +91,6 @@
 		self.update_expense_claim()
 		self.update_outstanding_amounts()
 		self.update_advance_paid()
-		self.update_donation()
 		self.update_payment_schedule()
 		self.set_status()
 
@@ -101,7 +100,6 @@
 		self.update_expense_claim()
 		self.update_outstanding_amounts()
 		self.update_advance_paid()
-		self.update_donation(cancel=1)
 		self.delink_advance_entry_references()
 		self.update_payment_schedule(cancel=1)
 		self.set_payment_req_status()
@@ -284,8 +282,6 @@
 			valid_reference_doctypes = ("Expense Claim", "Journal Entry", "Employee Advance", "Gratuity")
 		elif self.party_type == "Shareholder":
 			valid_reference_doctypes = ("Journal Entry")
-		elif self.party_type == "Donor":
-			valid_reference_doctypes = ("Donation")
 
 		for d in self.get("references"):
 			if not d.allocated_amount:
@@ -843,13 +839,6 @@
 					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:
-			for d in self.get("references"):
-				if d.reference_doctype=="Donation" and d.reference_name:
-					is_paid = 0 if cancel else 1
-					frappe.db.set_value("Donation", d.reference_name, "paid", is_paid)
-
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		self.reference_no = reference_doc.name
 		self.reference_date = nowdate()
@@ -945,8 +934,12 @@
 
 			tax.base_total = tax.total * self.source_exchange_rate
 
-			self.total_taxes_and_charges += current_tax_amount
-			self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
+			if self.payment_type == 'Pay':
+				self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
+				self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
+			else:
+				self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
+				self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
 
 		if self.get('taxes'):
 			self.paid_amount_after_tax = self.get('taxes')[-1].base_total
@@ -1077,7 +1070,7 @@
 		if d.voucher_type in ("Purchase Invoice"):
 			d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
 
-	# Get all SO / PO which are not fully billed or aginst which full advance not paid
+	# Get all SO / PO which are not fully billed or against which full advance not paid
 	orders_to_be_billed = []
 	if (args.get("party_type") != "Student"):
 		orders_to_be_billed =  get_orders_to_be_billed(args.get("posting_date"),args.get("party_type"),
@@ -1337,10 +1330,6 @@
 		total_amount = ref_doc.get("grand_total")
 		exchange_rate = 1
 		outstanding_amount = ref_doc.get("outstanding_amount")
-	elif reference_doctype == "Donation":
-		total_amount = ref_doc.get("amount")
-		outstanding_amount = total_amount
-		exchange_rate = 1
 	elif reference_doctype == "Dunning":
 		total_amount = ref_doc.get("dunning_amount")
 		exchange_rate = 1
@@ -1611,8 +1600,6 @@
 		party_type = "Employee"
 	elif dt == "Fees":
 		party_type = "Student"
-	elif dt == "Donation":
-		party_type = "Donor"
 	return party_type
 
 def set_party_account(dt, dn, doc, party_type):
@@ -1640,7 +1627,7 @@
 	return party_account_currency
 
 def set_payment_type(dt, doc):
-	if (dt in ("Sales Order", "Donation") or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
+	if (dt == "Sales Order" or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)) \
 		or (dt=="Purchase Invoice" and doc.outstanding_amount < 0):
 			payment_type = "Receive"
 	else:
@@ -1673,9 +1660,6 @@
 	elif dt == "Dunning":
 		grand_total = doc.grand_total
 		outstanding_amount = doc.grand_total
-	elif dt == "Donation":
-		grand_total = doc.amount
-		outstanding_amount = doc.amount
 	elif dt == "Gratuity":
 		grand_total = doc.amount
 		outstanding_amount = flt(doc.amount) - flt(doc.paid_amount)
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index cc3528e..349b8bb 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -633,6 +633,45 @@
 		self.assertEqual(flt(expected_party_balance), party_balance)
 		self.assertEqual(flt(expected_party_account_balance), party_account_balance)
 
+	def test_multi_currency_payment_entry_with_taxes(self):
+		payment_entry = create_payment_entry(party='_Test Supplier USD', paid_to = '_Test Payable USD - _TC',
+			save=True)
+		payment_entry.append('taxes', {
+			'account_head': '_Test Account Service Tax - _TC',
+			'charge_type': 'Actual',
+			'tax_amount': 10,
+			'add_deduct_tax': 'Add',
+			'description': 'Test'
+		})
+
+		payment_entry.save()
+		self.assertEqual(payment_entry.base_total_taxes_and_charges, 10)
+		self.assertEqual(flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2))
+
+def create_payment_entry(**args):
+	payment_entry = frappe.new_doc('Payment Entry')
+	payment_entry.company = args.get('company') or '_Test Company'
+	payment_entry.payment_type = args.get('payment_type') or 'Pay'
+	payment_entry.party_type = args.get('party_type') or 'Supplier'
+	payment_entry.party = args.get('party') or '_Test Supplier'
+	payment_entry.paid_from = args.get('paid_from') or '_Test Bank - _TC'
+	payment_entry.paid_to = args.get('paid_to') or 'Creditors - _TC'
+	payment_entry.paid_amount = args.get('paid_amount') or 1000
+
+	payment_entry.setup_party_account_field()
+	payment_entry.set_missing_values()
+	payment_entry.set_exchange_rate()
+	payment_entry.received_amount = payment_entry.paid_amount / payment_entry.target_exchange_rate
+	payment_entry.reference_no = 'Test001'
+	payment_entry.reference_date = nowdate()
+
+	if args.get('save'):
+		payment_entry.save()
+		if args.get('submit'):
+			payment_entry.submit()
+
+	return payment_entry
+
 def create_payment_terms_template():
 
 	create_payment_term('Basic Amount Receivable')
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 6a84a65..d72d8f7 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -291,7 +291,7 @@
 		if not status:
 			return
 
-		shopping_cart_settings = frappe.get_doc("Shopping Cart Settings")
+		shopping_cart_settings = frappe.get_doc("E Commerce Settings")
 
 		if status in ["Authorized", "Completed"]:
 			redirect_to = None
@@ -435,13 +435,13 @@
 	""", (ref_dt, ref_dn))
 	return flt(existing_payment_request_amount[0][0]) if existing_payment_request_amount else 0
 
-def get_gateway_details(args):
+def get_gateway_details(args): # nosemgrep
 	"""return gateway and payment account of default payment gateway"""
 	if args.get("payment_gateway_account"):
 		return get_payment_gateway_account(args.get("payment_gateway_account"))
 
 	if args.order_type == "Shopping Cart":
-		payment_gateway_account = frappe.get_doc("Shopping Cart Settings").payment_gateway_account
+		payment_gateway_account = frappe.get_doc("E Commerce Settings").payment_gateway_account
 		return get_payment_gateway_account(payment_gateway_account)
 
 	gateway_account = get_payment_gateway_account({"is_default": 1})
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 134bccf..9b3b3aa 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -42,7 +42,6 @@
 		self.validate_serialised_or_batched_item()
 		self.validate_stock_availablility()
 		self.validate_return_items_qty()
-		self.validate_non_stock_items()
 		self.set_status()
 		self.set_account_for_mode_of_payment()
 		self.validate_pos()
@@ -158,22 +157,40 @@
 			frappe.throw(_("Row #{}: Serial No. {} has already been transacted into another Sales Invoice. Please select valid serial no.")
 						.format(item.idx, bold_delivered_serial_nos), title=_("Item Unavailable"))
 
+	def validate_invalid_serial_nos(self, item):
+		serial_nos = get_serial_nos(item.serial_no)
+		error_msg = []
+		invalid_serials, msg = "", ""
+		for serial_no in serial_nos:
+			if not frappe.db.exists('Serial No', serial_no):
+				invalid_serials = invalid_serials + (", " if invalid_serials else "") + serial_no
+		msg = (_("Row #{}: Following Serial numbers for item {} are <b>Invalid</b>: {}").format(item.idx, frappe.bold(item.get("item_code")), frappe.bold(invalid_serials)))
+		if invalid_serials:
+			error_msg.append(msg)
+
+		if error_msg:
+			frappe.throw(error_msg, title=_("Invalid Item"), as_list=True)
+
 	def validate_stock_availablility(self):
+		from erpnext.stock.stock_ledger import is_negative_stock_allowed
+
 		if self.is_return or self.docstatus != 1:
 			return
-
-		allow_negative_stock = frappe.db.get_single_value('Stock Settings', 'allow_negative_stock')
 		for d in self.get('items'):
+			is_service_item = not (frappe.db.get_value('Item', d.get('item_code'), 'is_stock_item'))
+			if is_service_item:
+				return
 			if d.serial_no:
 				self.validate_pos_reserved_serial_nos(d)
 				self.validate_delivered_serial_nos(d)
+				self.validate_invalid_serial_nos(d)
 			elif d.batch_no:
 				self.validate_pos_reserved_batch_qty(d)
 			else:
-				if allow_negative_stock:
+				if is_negative_stock_allowed(item_code=d.item_code):
 					return
 
-				available_stock = get_stock_availability(d.item_code, d.warehouse)
+				available_stock, is_stock_item = get_stock_availability(d.item_code, d.warehouse)
 
 				item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
 				if flt(available_stock) <= 0:
@@ -244,14 +261,6 @@
 							.format(d.idx, bold_serial_no, bold_return_against)
 						)
 
-	def validate_non_stock_items(self):
-		for d in self.get("items"):
-			is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
-			if not is_stock_item:
-				if not frappe.db.exists('Product Bundle', d.item_code):
-					frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
-						.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
-
 	def validate_mode_of_payment(self):
 		if len(self.payments) == 0:
 			frappe.throw(_("At least one mode of payment is required for POS invoice."))
@@ -430,7 +439,6 @@
 			self.paid_amount = 0
 
 	def set_account_for_mode_of_payment(self):
-		self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
 		for pay in self.payments:
 			if not pay.account:
 				pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
@@ -491,12 +499,18 @@
 @frappe.whitelist()
 def get_stock_availability(item_code, warehouse):
 	if frappe.db.get_value('Item', item_code, 'is_stock_item'):
+		is_stock_item = True
 		bin_qty = get_bin_qty(item_code, warehouse)
 		pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
-		return bin_qty - pos_sales_qty
+		return bin_qty - pos_sales_qty, is_stock_item
 	else:
+		is_stock_item = False
 		if frappe.db.exists('Product Bundle', item_code):
-			return get_bundle_availability(item_code, warehouse)
+			return get_bundle_availability(item_code, warehouse), is_stock_item
+		else:
+			# Is a service item
+			return 0, is_stock_item
+
 
 def get_bundle_availability(bundle_item_code, warehouse):
 	product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index 56479a0..cf8affd 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -354,6 +354,24 @@
 		pos2.insert()
 		self.assertRaises(frappe.ValidationError, pos2.submit)
 
+	def test_invalid_serial_no_validation(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
+
+		se = make_serialized_item(company='_Test Company',
+			target_warehouse="Stores - _TC", cost_center='Main - _TC', expense_account='Cost of Goods Sold - _TC')
+		serial_nos = se.get("items")[0].serial_no + 'wrong'
+
+		pos = create_pos_invoice(company='_Test Company', debit_to='Debtors - _TC',
+			account_for_change_amount='Cash - _TC', warehouse='Stores - _TC', income_account='Sales - _TC',
+			expense_account='Cost of Goods Sold - _TC', cost_center='Main - _TC',
+			item=se.get("items")[0].item_code, rate=1000, qty=2, do_not_save=1)
+
+		pos.get('items')[0].has_serial_no = 1
+		pos.get('items')[0].serial_no = serial_nos
+		pos.insert()
+
+		self.assertRaises(frappe.ValidationError, pos.submit)
+
 	def test_loyalty_points(self):
 		from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
 			get_loyalty_program_details_with_points,
@@ -568,23 +586,29 @@
 		item_price.insert()
 		pr = make_pricing_rule(selling=1, priority=5, discount_percentage=10)
 		pr.save()
-		pos_inv = create_pos_invoice(qty=1, do_not_submit=1)
-		pos_inv.items[0].rate = 300
-		pos_inv.save()
-		self.assertEquals(pos_inv.items[0].discount_percentage, 10)
-		# rate shouldn't change
-		self.assertEquals(pos_inv.items[0].rate, 405)
 
-		pos_inv.ignore_pricing_rule = 1
-		pos_inv.items[0].rate = 300
-		pos_inv.save()
-		self.assertEquals(pos_inv.ignore_pricing_rule, 1)
-		# rate should change since pricing rules are ignored
-		self.assertEquals(pos_inv.items[0].rate, 300)
+		try:
+			pos_inv = create_pos_invoice(qty=1, do_not_submit=1)
+			pos_inv.items[0].rate = 300
+			pos_inv.save()
+			self.assertEquals(pos_inv.items[0].discount_percentage, 10)
+			# rate shouldn't change
+			self.assertEquals(pos_inv.items[0].rate, 405)
 
-		item_price.delete()
-		pos_inv.delete()
-		pr.delete()
+			pos_inv.ignore_pricing_rule = 1
+			pos_inv.save()
+			self.assertEquals(pos_inv.ignore_pricing_rule, 1)
+			# rate should reset since pricing rules are ignored
+			self.assertEquals(pos_inv.items[0].rate, 450)
+
+			pos_inv.items[0].rate = 300
+			pos_inv.save()
+			self.assertEquals(pos_inv.items[0].rate, 300)
+
+		finally:
+			item_price.delete()
+			pos_inv.delete()
+			pr.delete()
 
 
 def create_pos_invoice(**args):
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 0720d9b..ddca68a 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -84,12 +84,20 @@
 		sales_invoice.set_posting_time = 1
 		sales_invoice.posting_date = getdate(self.posting_date)
 		sales_invoice.save()
+		self.write_off_fractional_amount(sales_invoice, data)
 		sales_invoice.submit()
 
 		self.consolidated_invoice = sales_invoice.name
 
 		return sales_invoice.name
 
+	def write_off_fractional_amount(self, invoice, data):
+		pos_invoice_grand_total = sum(d.grand_total for d in data)
+
+		if abs(pos_invoice_grand_total - invoice.grand_total) < 1:
+			invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total)
+			invoice.save()
+
 	def process_merging_into_credit_note(self, data):
 		credit_note = self.get_new_sales_invoice()
 		credit_note.is_return = 1
@@ -102,6 +110,7 @@
 		# TODO: return could be against multiple sales invoice which could also have been consolidated?
 		# credit_note.return_against = self.consolidated_invoice
 		credit_note.save()
+		self.write_off_fractional_amount(credit_note, data)
 		credit_note.submit()
 
 		self.consolidated_credit_note = credit_note.name
@@ -135,9 +144,15 @@
 						i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse):
 						found = True
 						i.qty = i.qty + item.qty
+						i.amount = i.amount + item.net_amount
+						i.net_amount = i.amount
+						i.base_amount = i.base_amount + item.base_net_amount
+						i.base_net_amount = i.base_amount
 
 				if not found:
 					item.rate = item.net_rate
+					item.amount = item.net_amount
+					item.base_amount = item.base_net_amount
 					item.price_list_rate = 0
 					si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
 					items.append(si_item)
@@ -169,6 +184,7 @@
 						found = True
 				if not found:
 					payments.append(payment)
+
 			rounding_adjustment += doc.rounding_adjustment
 			rounded_total += doc.rounded_total
 			base_rounding_adjustment += doc.base_rounding_adjustment
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index 3555da8..5930aa0 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -12,6 +12,7 @@
 from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
 	consolidate_pos_invoices,
 )
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 
 
 class TestPOSInvoiceMergeLog(unittest.TestCase):
@@ -150,3 +151,132 @@
 			frappe.set_user("Administrator")
 			frappe.db.sql("delete from `tabPOS Profile`")
 			frappe.db.sql("delete from `tabPOS Invoice`")
+
+
+	def test_consolidation_round_off_error_1(self):
+		'''
+		Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
+		'''
+
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			make_stock_entry(
+				to_warehouse="_Test Warehouse - _TC",
+				item_code="_Test Item",
+				rate=8000,
+				qty=10,
+			)
+
+			init_user_and_profile()
+
+			inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
+			inv.append("taxes", {
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 7.5,
+				"included_in_print_rate": 1
+			})
+			inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 30000
+			})
+			inv.insert()
+			inv.submit()
+
+			inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
+			inv2.append("taxes", {
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 7.5,
+				"included_in_print_rate": 1
+			})
+			inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 30000
+			})
+			inv2.insert()
+			inv2.submit()
+
+			consolidate_pos_invoices()
+
+			inv.load_from_db()
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			self.assertEqual(consolidated_invoice.outstanding_amount, 0)
+			self.assertEqual(consolidated_invoice.status, 'Paid')
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	def test_consolidation_round_off_error_2(self):
+		'''
+		Test the same case as above but with an Unpaid POS Invoice
+		'''
+		frappe.db.sql("delete from `tabPOS Invoice`")
+
+		try:
+			make_stock_entry(
+				to_warehouse="_Test Warehouse - _TC",
+				item_code="_Test Item",
+				rate=8000,
+				qty=10,
+			)
+
+			init_user_and_profile()
+
+			inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
+			inv.append("taxes", {
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 7.5,
+				"included_in_print_rate": 1
+			})
+			inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60000
+			})
+			inv.insert()
+			inv.submit()
+
+			inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
+			inv2.append("taxes", {
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 7.5,
+				"included_in_print_rate": 1
+			})
+			inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60000
+			})
+			inv2.insert()
+			inv2.submit()
+
+			inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
+			inv3.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000
+			})
+			inv3.insert()
+			inv3.submit()
+
+			consolidate_pos_invoices()
+
+			inv.load_from_db()
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			self.assertEqual(consolidated_invoice.outstanding_amount, 800)
+			self.assertNotEqual(consolidated_invoice.status, 'Paid')
+
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index ac96b04..933fda8 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -249,13 +249,17 @@
 		"free_item_data": [],
 		"parent": args.parent,
 		"parenttype": args.parenttype,
-		"child_docname": args.get('child_docname')
+		"child_docname": args.get('child_docname'),
 	})
 
 	if args.ignore_pricing_rule or not args.item_code:
 		if frappe.db.exists(args.doctype, args.name) and args.get("pricing_rules"):
-			item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
-				item_details, args.get('item_code'))
+			item_details = remove_pricing_rule_for_item(
+				args.get("pricing_rules"),
+				item_details,
+				item_code=args.get("item_code"),
+				rate=args.get("price_list_rate"),
+			)
 		return item_details
 
 	update_args_for_pricing_rule(args)
@@ -308,8 +312,12 @@
 		if not doc: return item_details
 
 	elif args.get("pricing_rules"):
-		item_details = remove_pricing_rule_for_item(args.get("pricing_rules"),
-			item_details, args.get('item_code'))
+		item_details = remove_pricing_rule_for_item(
+			args.get("pricing_rules"),
+			item_details,
+			item_code=args.get("item_code"),
+			rate=args.get("price_list_rate"),
+		)
 
 	return item_details
 
@@ -390,7 +398,7 @@
 			item_details[field] += (pricing_rule.get(field, 0)
 				if pricing_rule else args.get(field, 0))
 
-def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None):
+def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
 	from erpnext.accounts.doctype.pricing_rule.utils import (
 		get_applied_pricing_rules,
 		get_pricing_rule_items,
@@ -403,6 +411,7 @@
 			if pricing_rule.rate_or_discount == 'Discount Percentage':
 				item_details.discount_percentage = 0.0
 				item_details.discount_amount = 0.0
+				item_details.rate = rate or 0.0
 
 			if pricing_rule.rate_or_discount == 'Discount Amount':
 				item_details.discount_amount = 0.0
@@ -421,6 +430,7 @@
 			item_details.applied_on_items = ','.join(items)
 
 	item_details.pricing_rules = ''
+	item_details.pricing_rule_removed = True
 
 	return item_details
 
@@ -432,9 +442,12 @@
 	out = []
 	for item in item_list:
 		item = frappe._dict(item)
-		if item.get('pricing_rules'):
-			out.append(remove_pricing_rule_for_item(item.get("pricing_rules"),
-				item, item.item_code))
+		if item.get("pricing_rules"):
+			out.append(
+				remove_pricing_rule_for_item(
+					item.get("pricing_rules"), item, item.item_code, item.get("price_list_rate")
+				)
+			)
 
 	return out
 
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index 5746a84..8338a5b0 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -628,6 +628,67 @@
 		for doc in [si, si1]:
 			doc.delete()
 
+	def test_remove_pricing_rule(self):
+		item = make_item("Water Flask")
+		make_item_price("Water Flask", "_Test Price List", 100)
+
+		pricing_rule_record = {
+			"doctype": "Pricing Rule",
+			"title": "_Test Water Flask Rule",
+			"apply_on": "Item Code",
+			"price_or_product_discount": "Price",
+			"items": [{
+				"item_code": "Water Flask",
+			}],
+			"selling": 1,
+			"currency": "INR",
+			"rate_or_discount": "Discount Percentage",
+			"discount_percentage": 20,
+			"company": "_Test Company"
+		}
+		rule = frappe.get_doc(pricing_rule_record)
+		rule.insert()
+
+		si = create_sales_invoice(do_not_save=True, item_code="Water Flask")
+		si.selling_price_list = "_Test Price List"
+		si.save()
+
+		self.assertEqual(si.items[0].price_list_rate, 100)
+		self.assertEqual(si.items[0].discount_percentage, 20)
+		self.assertEqual(si.items[0].rate, 80)
+
+		si.ignore_pricing_rule = 1
+		si.save()
+
+		self.assertEqual(si.items[0].discount_percentage, 0)
+		self.assertEqual(si.items[0].rate, 100)
+
+		si.delete()
+		rule.delete()
+		frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
+		item.delete()
+
+	def test_multiple_pricing_rules_with_min_qty(self):
+		make_pricing_rule(discount_percentage=20, selling=1, priority=1, min_qty=4,
+			apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 1")
+		make_pricing_rule(discount_percentage=10, selling=1, priority=2, min_qty=4,
+			apply_multiple_pricing_rules=1, title="_Test Pricing Rule with Min Qty - 2")
+
+		si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", qty=1, currency="USD")
+		item = si.items[0]
+		item.stock_qty = 1
+		si.save()
+		self.assertFalse(item.discount_percentage)
+		item.qty = 5
+		item.stock_qty = 5
+		si.save()
+		self.assertEqual(item.discount_percentage, 30)
+		si.delete()
+
+		frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1")
+		frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2")
+
+
 test_dependencies = ["Campaign"]
 
 def make_pricing_rule(**args):
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 02bfc9d..7792590 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -73,7 +73,7 @@
 	for key in sorted(pricing_rule_dict):
 		pricing_rules_list.extend(pricing_rule_dict.get(key))
 
-	return pricing_rules_list or pricing_rules
+	return pricing_rules_list
 
 def filter_pricing_rule_based_on_condition(pricing_rules, doc=None):
 	filtered_pricing_rules = []
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index 09aa723..1b34d6d 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -73,7 +73,7 @@
 			'to_date': doc.to_date,
 			'company': doc.company,
 			'finance_book': doc.finance_book if doc.finance_book else None,
-			'account': doc.account if doc.account else None,
+			'account': [doc.account] if doc.account else None,
 			'party_type': 'Customer',
 			'party': [entry.customer],
 			'presentation_currency': presentation_currency,
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index b364218..2c31561 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -178,8 +178,8 @@
 
 		if self.supplier and account.account_type != "Payable":
 			frappe.throw(
-				_("Please ensure {} account is a Payable account. Change the account type to Payable or select a different account.")
-				.format(frappe.bold("Credit To")), title=_("Invalid Account")
+				_("Please ensure {} account {} is a Payable account. Change the account type to Payable or select a different account.")
+				.format(frappe.bold("Credit To"), frappe.bold(self.credit_to)), title=_("Invalid Account")
 			)
 
 		self.party_account_currency = account.account_currency
@@ -537,8 +537,11 @@
 
 		voucher_wise_stock_value = {}
 		if self.update_stock:
-			for d in frappe.get_all('Stock Ledger Entry',
-				fields = ["voucher_detail_no", "stock_value_difference", "warehouse"], filters={'voucher_no': self.name}):
+			stock_ledger_entries = frappe.get_all("Stock Ledger Entry",
+				fields = ["voucher_detail_no", "stock_value_difference", "warehouse"],
+				filters={"voucher_no": self.name, "voucher_type": self.doctype, "is_cancelled": 0}
+			)
+			for d in stock_ledger_entries:
 				voucher_wise_stock_value.setdefault((d.voucher_detail_no, d.warehouse), d.stock_value_difference)
 
 		valuation_tax_accounts = [d.account_head for d in self.get("taxes")
@@ -548,6 +551,10 @@
 		exchange_rate_map, net_rate_map = get_purchase_document_details(self)
 
 		enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
+		provisional_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, \
+			'enable_provisional_accounting_for_non_stock_items'))
+
+		purchase_receipt_doc_map = {}
 
 		for item in self.get("items"):
 			if flt(item.base_net_amount):
@@ -643,19 +650,23 @@
 					else:
 						amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
 
-					auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
-
-					if auto_accounting_for_non_stock_items:
-						service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
-
+					if provisional_accounting_for_non_stock_items:
 						if item.purchase_receipt:
+							provisional_account = self.get_company_default("default_provisional_account")
+							purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
+
+							if not purchase_receipt_doc:
+								purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt)
+								purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
+
 							# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
 							expense_booked_in_pr = frappe.db.get_value('GL Entry', {'is_cancelled': 0,
 								'voucher_type': 'Purchase Receipt', 'voucher_no': item.purchase_receipt, 'voucher_detail_no': item.pr_detail,
-								'account':service_received_but_not_billed_account}, ['name'])
+								'account':provisional_account}, ['name'])
 
 							if expense_booked_in_pr:
-								expense_account = service_received_but_not_billed_account
+								# Intentionally passing purchase invoice item to handle partial billing
+								purchase_receipt_doc.add_provisional_gl_entry(item, gl_entries, self.posting_date, reverse=1)
 
 					if not self.is_internal_transfer():
 						gl_entries.append(self.get_gl_dict({
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
index f6ff83a..82d0030 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice_list.js
@@ -56,4 +56,14 @@
 			];
 		}
 	},
+
+	onload: function(listview) {
+		listview.page.add_action_item(__("Purchase Receipt"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Purchase Receipt");
+		});
+
+		listview.page.add_action_item(__("Payment"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Purchase Invoice", "Payment");
+		});
+	}
 };
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 21846bb..d51a008 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -11,12 +11,17 @@
 import erpnext
 from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
 from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
+from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
 from erpnext.buying.doctype.supplier.test_supplier import create_supplier
 from erpnext.controllers.accounts_controller import get_payment_terms
 from erpnext.controllers.buying_controller import QtyMismatchError
 from erpnext.exceptions import InvalidCurrency
 from erpnext.projects.doctype.project.test_project import make_project
 from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
+	make_purchase_invoice as create_purchase_invoice_from_receipt,
+)
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
 	get_taxes,
 	make_purchase_receipt,
@@ -1147,8 +1152,6 @@
 
 	def test_purchase_invoice_advance_taxes(self):
 		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-		from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
-		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
 
 		# create a new supplier to test
 		supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
@@ -1221,6 +1224,45 @@
 		payment_entry.load_from_db()
 		self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
 
+	def test_provisional_accounting_entry(self):
+		item = create_item("_Test Non Stock Item", is_stock_item=0)
+		provisional_account = create_account(account_name="Provision Account",
+			parent_account="Current Liabilities - _TC", company="_Test Company")
+
+		company = frappe.get_doc('Company', '_Test Company')
+		company.enable_provisional_accounting_for_non_stock_items = 1
+		company.default_provisional_account = provisional_account
+		company.save()
+
+		pr = make_purchase_receipt(item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2))
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		pi.set_posting_time = 1
+		pi.posting_date = add_days(pr.posting_date, -1)
+		pi.items[0].expense_account = 'Cost of Goods Sold - _TC'
+		pi.save()
+		pi.submit()
+
+		# Check GLE for Purchase Invoice
+		expected_gle = [
+			['Cost of Goods Sold - _TC', 250, 0, add_days(pr.posting_date, -1)],
+			['Creditors - _TC', 0, 250, add_days(pr.posting_date, -1)]
+		]
+
+		check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
+
+		expected_gle_for_purchase_receipt = [
+			["Provision Account - _TC", 250, 0, pr.posting_date],
+			["_Test Account Cost for Goods Sold - _TC", 0, 250, pr.posting_date],
+			["Provision Account - _TC", 0, 250, pi.posting_date],
+			["_Test Account Cost for Goods Sold - _TC", 250, 0, pi.posting_date]
+		]
+
+		check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
+
+		company.enable_provisional_accounting_for_non_stock_items = 0
+		company.save()
+
 def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
 	gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
 		from `tabGL Entry`
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js
index 6336db1..f54bce8 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js
@@ -1,6 +1,8 @@
 {% include "erpnext/regional/india/taxes.js" %}
+{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
 
 erpnext.setup_auto_gst_taxation('Sales Invoice');
+erpnext.setup_einvoice_actions('Sales Invoice')
 
 frappe.ui.form.on("Sales Invoice", {
 	setup: function(frm) {
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
index d9d6634..f01325d 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
@@ -36,4 +36,139 @@
 	};
 
 	list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
+
+	const generate_irns = () => {
+		const docnames = list_view.get_checked_items(true);
+		if (docnames && docnames.length) {
+			frappe.call({
+				method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
+				args: { docnames },
+				freeze: true,
+				freeze_message: __('Generating E-Invoices...')
+			});
+		} else {
+			frappe.msgprint({
+				message: __('Please select at least one sales invoice to generate IRN'),
+				title: __('No Invoice Selected'),
+				indicator: 'red'
+			});
+		}
+	};
+
+	const cancel_irns = () => {
+		const docnames = list_view.get_checked_items(true);
+
+		const fields = [
+			{
+				"label": "Reason",
+				"fieldname": "reason",
+				"fieldtype": "Select",
+				"reqd": 1,
+				"default": "1-Duplicate",
+				"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+			},
+			{
+				"label": "Remark",
+				"fieldname": "remark",
+				"fieldtype": "Data",
+				"reqd": 1
+			}
+		];
+
+		const d = new frappe.ui.Dialog({
+			title: __("Cancel IRN"),
+			fields: fields,
+			primary_action: function() {
+				const data = d.get_values();
+				frappe.call({
+					method: 'erpnext.regional.india.e_invoice.utils.cancel_irns',
+					args: {
+						doctype: list_view.doctype,
+						docnames,
+						reason: data.reason.split('-')[0],
+						remark: data.remark
+					},
+					freeze: true,
+					freeze_message: __('Cancelling E-Invoices...'),
+				});
+				d.hide();
+			},
+			primary_action_label: __('Submit')
+		});
+		d.show();
+	};
+
+	let einvoicing_enabled = false;
+	frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
+		einvoicing_enabled = enabled;
+	});
+
+	list_view.$result.on("change", "input[type=checkbox]", () => {
+		if (einvoicing_enabled) {
+			const docnames = list_view.get_checked_items(true);
+			// show/hide e-invoicing actions when no sales invoices are checked
+			if (docnames && docnames.length) {
+				// prevent adding actions twice if e-invoicing action group already exists
+				if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
+					list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
+					list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
+				}
+			} else {
+				list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
+				list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
+			}
+		}
+	});
+
+	frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
+		const { failures, user, invoices } = data;
+
+		if (invoices.length != failures.length) {
+			frappe.msgprint({
+				message: __('{0} e-invoices generated successfully', [invoices.length]),
+				title: __('Bulk E-Invoice Generation Complete'),
+				indicator: 'orange'
+			});
+		}
+
+		if (failures && failures.length && user == frappe.session.user) {
+			let message = `
+				Failed to generate IRNs for following ${failures.length} sales invoices:
+				<ul style="padding-left: 20px; padding-top: 5px;">
+					${failures.map(d => `<li>${d.docname}</li>`).join('')}
+				</ul>
+			`;
+			frappe.msgprint({
+				message: message,
+				title: __('Bulk E-Invoice Generation Complete'),
+				indicator: 'orange'
+			});
+		}
+	});
+
+	frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
+		const { failures, user, invoices } = data;
+
+		if (invoices.length != failures.length) {
+			frappe.msgprint({
+				message: __('{0} e-invoices cancelled successfully', [invoices.length]),
+				title: __('Bulk E-Invoice Cancellation Complete'),
+				indicator: 'orange'
+			});
+		}
+
+		if (failures && failures.length && user == frappe.session.user) {
+			let message = `
+				Failed to cancel IRNs for following ${failures.length} sales invoices:
+				<ul style="padding-left: 20px; padding-top: 5px;">
+					${failures.map(d => `<li>${d.docname}</li>`).join('')}
+				</ul>
+			`;
+			frappe.msgprint({
+				message: message,
+				title: __('Bulk E-Invoice Cancellation Complete'),
+				indicator: 'orange'
+			});
+		}
+	});
 };
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 39dfd8d..af6a52a 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -469,7 +469,7 @@
 				let row = frappe.get_doc(d.doctype, d.name)
 				set_timesheet_detail_rate(row.doctype, row.name, me.frm.doc.currency, row.timesheet_detail)
 			});
-			frm.trigger("calculate_timesheet_totals");
+			this.frm.trigger("calculate_timesheet_totals");
 		}
 	}
 };
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 98bc953..b894f90 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -43,6 +43,7 @@
 from erpnext.stock.doctype.batch.batch import set_batch_nos
 from erpnext.stock.doctype.delivery_note.delivery_note import update_billed_amount_based_on_so
 from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no, get_serial_nos
+from erpnext.stock.utils import calculate_mapped_packed_items_return
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -284,7 +285,7 @@
 				filters={ invoice_or_credit_note: self.name },
 				pluck="pos_closing_entry"
 			)
-			if pos_closing_entry:
+			if pos_closing_entry and pos_closing_entry[0]:
 				msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
 					frappe.bold("Consolidated Sales Invoice"),
 					get_link_to_form("POS Closing Entry", pos_closing_entry[0])
@@ -293,6 +294,8 @@
 
 	def before_cancel(self):
 		self.check_if_consolidated_invoice()
+
+		super(SalesInvoice, self).before_cancel()
 		self.update_time_sheet(None)
 
 	def on_cancel(self):
@@ -569,7 +572,10 @@
 			frappe.throw(msg, title=_("Invalid Account"))
 
 		if self.customer and account.account_type != "Receivable":
-			msg = _("Please ensure {} account is a Receivable account.").format(frappe.bold("Debit To")) + " "
+			msg = _("Please ensure {} account {} is a Receivable account.").format(
+				frappe.bold("Debit To"),
+				frappe.bold(self.debit_to)
+			) + " "
 			msg += _("Change the account type to Receivable or select a different account.")
 			frappe.throw(msg, title=_("Invalid Account"))
 
@@ -728,8 +734,11 @@
 
 	def update_packing_list(self):
 		if cint(self.update_stock) == 1:
-			from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
-			make_packing_list(self)
+			if cint(self.is_return) and self.return_against:
+				calculate_mapped_packed_items_return(self)
+			else:
+				from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+				make_packing_list(self)
 		else:
 			self.set('packed_items', [])
 
@@ -1243,14 +1252,14 @@
 	def update_billing_status_in_dn(self, update_modified=True):
 		updated_delivery_notes = []
 		for d in self.get("items"):
-			if d.dn_detail:
+			if d.so_detail:
+				updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
+			elif d.dn_detail:
 				billed_amt = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
 					where dn_detail=%s and docstatus=1""", d.dn_detail)
 				billed_amt = billed_amt and billed_amt[0][0] or 0
 				frappe.db.set_value("Delivery Note Item", d.dn_detail, "billed_amt", billed_amt, update_modified=update_modified)
 				updated_delivery_notes.append(d.delivery_note)
-			elif d.so_detail:
-				updated_delivery_notes += update_billed_amount_based_on_so(d.so_detail, update_modified)
 
 		for dn in set(updated_delivery_notes):
 			frappe.get_doc("Delivery Note", dn).update_billing_percentage(update_modified=update_modified)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
index 06e6f51..1130284 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_list.js
@@ -21,5 +21,15 @@
 		};
 		return [__(doc.status), status_colors[doc.status], "status,=,"+doc.status];
 	},
-	right_column: "grand_total"
+	right_column: "grand_total",
+
+	onload: function(listview) {
+		listview.page.add_action_item(__("Delivery Note"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Delivery Note");
+		});
+
+		listview.page.add_action_item(__("Payment"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Sales Invoice", "Payment");
+		});
+	}
 };
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index cfa42f6..6d929e4 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1595,6 +1595,56 @@
 			self.assertEqual(expected_values[gle.account][1], gle.debit)
 			self.assertEqual(expected_values[gle.account][2], gle.credit)
 
+	def test_rounding_adjustment_3(self):
+		si = create_sales_invoice(do_not_save=True)
+		si.items = []
+		for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
+			si.append("items", {
+				"item_code": "_Test Item",
+				"gst_hsn_code": "999800",
+				"warehouse": "_Test Warehouse - _TC",
+				"qty": d[1],
+				"rate": d[0],
+				"income_account": "Sales - _TC",
+				"cost_center": "_Test Cost Center - _TC"
+			})
+		for tax_account in ["_Test Account VAT - _TC", "_Test Account Service Tax - _TC"]:
+			si.append("taxes", {
+				"charge_type": "On Net Total",
+				"account_head": tax_account,
+				"description": tax_account,
+				"rate": 6,
+				"cost_center": "_Test Cost Center - _TC",
+				"included_in_print_rate": 1
+			})
+		si.save()
+		si.submit()
+		self.assertEqual(si.net_total, 4007.16)
+		self.assertEqual(si.grand_total, 4488.02)
+		self.assertEqual(si.total_taxes_and_charges, 480.86)
+		self.assertEqual(si.rounding_adjustment, -0.02)
+
+		expected_values = dict((d[0], d) for d in [
+			[si.debit_to, 4488.0, 0.0],
+			["_Test Account Service Tax - _TC", 0.0, 240.43],
+			["_Test Account VAT - _TC", 0.0, 240.43],
+			["Sales - _TC", 0.0, 4007.15],
+			["Round Off - _TC", 0.01, 0]
+		])
+
+		gl_entries = frappe.db.sql("""select account, debit, credit
+			from `tabGL Entry` where voucher_type='Sales Invoice' and voucher_no=%s
+			order by account asc""", si.name, as_dict=1)
+
+		debit_credit_diff = 0
+		for gle in gl_entries:
+			self.assertEqual(expected_values[gle.account][0], gle.account)
+			self.assertEqual(expected_values[gle.account][1], gle.debit)
+			self.assertEqual(expected_values[gle.account][2], gle.credit)
+			debit_credit_diff += (gle.debit - gle.credit)
+
+		self.assertEqual(debit_credit_diff, 0)
+
 	def test_sales_invoice_with_shipping_rule(self):
 		from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
 
@@ -2100,6 +2150,54 @@
 		self.assertEqual(data['billLists'][0]['actualFromStateCode'],7)
 		self.assertEqual(data['billLists'][0]['fromStateCode'],27)
 
+	def test_einvoice_submission_without_irn(self):
+		# init
+		einvoice_settings = frappe.get_doc('E Invoice Settings')
+		einvoice_settings.enable = 1
+		einvoice_settings.applicable_from = nowdate()
+		einvoice_settings.append('credentials', {
+			'company': '_Test Company',
+			'gstin': '27AAECE4835E1ZR',
+			'username': 'test',
+			'password': 'test'
+		})
+		einvoice_settings.save()
+
+		country = frappe.flags.country
+		frappe.flags.country = 'India'
+
+		si = make_sales_invoice_for_ewaybill()
+		self.assertRaises(frappe.ValidationError, si.submit)
+
+		si.irn = 'test_irn'
+		si.submit()
+
+		# reset
+		einvoice_settings = frappe.get_doc('E Invoice Settings')
+		einvoice_settings.enable = 0
+		frappe.flags.country = country
+
+	def test_einvoice_json(self):
+		from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
+
+		si = get_sales_invoice_for_e_invoice()
+		si.discount_amount = 100
+		si.save()
+
+		einvoice = make_einvoice(si)
+		self.assertTrue(einvoice['EwbDtls'])
+		validate_totals(einvoice)
+
+		si.apply_discount_on = 'Net Total'
+		si.save()
+		einvoice = make_einvoice(si)
+		validate_totals(einvoice)
+
+		[d.set('included_in_print_rate', 1) for d in si.taxes]
+		si.save()
+		einvoice = make_einvoice(si)
+		validate_totals(einvoice)
+
 	def test_item_tax_net_range(self):
 		item = create_item("T Shirt")
 
@@ -2192,9 +2290,9 @@
 		asset.load_from_db()
 
 		expected_values = [
-			["2020-06-30", 1311.48, 1311.48],
-			["2021-06-30", 20000.0, 21311.48],
-			["2021-09-30", 5041.1, 26352.58]
+			["2020-06-30", 1366.12, 1366.12],
+			["2021-06-30", 20000.0, 21366.12],
+			["2021-09-30", 5041.1, 26407.22]
 		]
 
 		for i, schedule in enumerate(asset.schedules):
@@ -2242,12 +2340,12 @@
 		asset.load_from_db()
 
 		expected_values = [
-			["2020-06-30", 1311.48, 1311.48, True],
-			["2021-06-30", 20000.0, 21311.48, True],
-			["2022-06-30", 20000.0, 41311.48, False],
-			["2023-06-30", 20000.0, 61311.48, False],
-			["2024-06-30",  20000.0, 81311.48,  False],
-			["2025-06-06",  18688.52,  100000.0, False]
+			["2020-06-30", 1366.12, 1366.12, True],
+			["2021-06-30", 20000.0, 21366.12, True],
+			["2022-06-30", 20000.0, 41366.12, False],
+			["2023-06-30", 20000.0, 61366.12, False],
+			["2024-06-30",  20000.0, 81366.12,  False],
+			["2025-06-06",  18633.88,  100000.0, False]
 		]
 
 		for i, schedule in enumerate(asset.schedules):
@@ -2381,14 +2479,22 @@
 
 
 	def test_sales_commission(self):
-		si = frappe.copy_doc(test_records[0])
+		si = frappe.copy_doc(test_records[2])
+
+		frappe.db.set_value('Item', si.get('items')[0].item_code, 'grant_commission', 1)
+		frappe.db.set_value('Item', si.get('items')[1].item_code, 'grant_commission', 0)
+
 		item = copy.deepcopy(si.get('items')[0])
 		item.update({
 			"qty": 1,
 			"rate": 500,
-			"grant_commission": 1
 		})
-		si.append("items", item)
+
+		item = copy.deepcopy(si.get('items')[1])
+		item.update({
+			"qty": 1,
+			"rate": 500,
+		})
 
 		# Test valid values
 		for commission_rate, total_commission in ((0, 0), (10, 50), (100, 500)):
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 ae9ac35..2901cf0 100644
--- a/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
+++ b/erpnext/accounts/doctype/sales_invoice_item/sales_invoice_item.json
@@ -832,6 +832,7 @@
   },
   {
    "default": "0",
+   "fetch_from": "item_code.grant_commission",
    "fieldname": "grant_commission",
    "fieldtype": "Check",
    "label": "Grant Commission",
@@ -841,7 +842,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-10-05 12:24:54.968907",
+ "modified": "2022-02-24 14:41:36.392560",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice Item",
@@ -849,5 +850,6 @@
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index b590944..8043a1b 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -46,7 +46,7 @@
 
 	for tax in doc.get("taxes"):
 		validate_taxes_and_charges(tax)
-		validate_account_head(tax, doc)
+		validate_account_head(tax.idx, tax.account_head, doc.company)
 		validate_cost_center(tax, doc)
 		validate_inclusive_tax(tax, doc)
 
@@ -55,5 +55,8 @@
 		frappe.throw(_("Disabled template must not be default template"))
 
 def validate_for_tax_category(doc):
+	if not doc.tax_category:
+		return
+
 	if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
 		frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
diff --git a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
index 7e51299..792e7d2 100644
--- a/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
+++ b/erpnext/accounts/doctype/shipping_rule/shipping_rule.py
@@ -71,7 +71,8 @@
 		if doc.currency != doc.company_currency:
 			shipping_amount = flt(shipping_amount / doc.conversion_rate, 2)
 
-		self.add_shipping_rule_to_tax_table(doc, shipping_amount)
+		if shipping_amount:
+			self.add_shipping_rule_to_tax_table(doc, shipping_amount)
 
 	def get_shipping_amount_from_rules(self, value):
 		for condition in self.get("conditions"):
diff --git a/erpnext/accounts/doctype/tax_category/tax_category.json b/erpnext/accounts/doctype/tax_category/tax_category.json
index f7145af..44a339f 100644
--- a/erpnext/accounts/doctype/tax_category/tax_category.json
+++ b/erpnext/accounts/doctype/tax_category/tax_category.json
@@ -2,12 +2,13 @@
  "actions": [],
  "allow_rename": 1,
  "autoname": "field:title",
- "creation": "2018-11-22 23:38:39.668804",
+ "creation": "2022-01-19 01:09:28.920486",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "title"
+  "title",
+  "disabled"
  ],
  "fields": [
   {
@@ -18,14 +19,21 @@
    "label": "Title",
    "reqd": 1,
    "unique": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "disabled",
+   "fieldtype": "Check",
+   "label": "Disabled"
   }
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-03-03 11:50:38.748872",
+ "modified": "2022-01-18 21:13:41.161017",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Tax Category",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
@@ -65,5 +73,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index a16377c..2d94bc3 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -98,7 +98,7 @@
 	def validate_use_for_shopping_cart(self):
 		'''If shopping cart is enabled and no tax rule exists for shopping cart, enable this one'''
 		if (not self.use_for_shopping_cart
-			and cint(frappe.db.get_single_value('Shopping Cart Settings', 'enabled'))
+			and cint(frappe.db.get_single_value('E Commerce Settings', 'enabled'))
 			and not frappe.db.get_value('Tax Rule', {'use_for_shopping_cart': 1, 'name': ['!=', self.name]})):
 
 			self.use_for_shopping_cart = 1
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
index 7de9ae1..02e30c3 100644
--- 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
@@ -2,15 +2,17 @@
  "creation": "2021-08-24 12:28:18.044902",
  "docstatus": 0,
  "doctype": "Form Tour",
+ "first_document": 0,
  "idx": 0,
+ "include_name_field": 0,
  "is_standard": 1,
- "modified": "2021-08-24 12:28:18.044902",
+ "modified": "2022-01-18 18:32:17.102330",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Taxes and Charges Template",
  "owner": "Administrator",
  "reference_doctype": "Sales Taxes and Charges Template",
- "save_on_complete": 0,
+ "save_on_complete": 1,
  "steps": [
   {
    "description": "A name by which you will identify this template. You can change this later.",
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 1836db6..0cd5e86 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -2,6 +2,8 @@
 # License: GNU General Public License v3. See license.txt
 
 
+import copy
+
 import frappe
 from frappe import _
 from frappe.model.meta import get_field_precision
@@ -51,49 +53,57 @@
 			.format(frappe.bold(accounting_periods[0].name)), ClosedAccountingPeriod)
 
 def process_gl_map(gl_map, merge_entries=True, precision=None):
+	if not gl_map:
+		return []
+
+	gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
+
 	if merge_entries:
 		gl_map = merge_similar_entries(gl_map, precision)
-	for entry in gl_map:
-		# toggle debit, credit if negative entry
-		if flt(entry.debit) < 0:
-			entry.credit = flt(entry.credit) - flt(entry.debit)
-			entry.debit = 0.0
 
-		if flt(entry.debit_in_account_currency) < 0:
-			entry.credit_in_account_currency = \
-				flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
-			entry.debit_in_account_currency = 0.0
-
-		if flt(entry.credit) < 0:
-			entry.debit = flt(entry.debit) - flt(entry.credit)
-			entry.credit = 0.0
-
-		if flt(entry.credit_in_account_currency) < 0:
-			entry.debit_in_account_currency = \
-				flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
-			entry.credit_in_account_currency = 0.0
-
-		update_net_values(entry)
+	gl_map = toggle_debit_credit_if_negative(gl_map)
 
 	return gl_map
 
-def update_net_values(entry):
-	# In some scenarios net value needs to be shown in the ledger
-	# This method updates net values as debit or credit
-	if entry.post_net_value and entry.debit and entry.credit:
-		if entry.debit > entry.credit:
-			entry.debit = entry.debit - entry.credit
-			entry.debit_in_account_currency = entry.debit_in_account_currency \
-				- entry.credit_in_account_currency
-			entry.credit = 0
-			entry.credit_in_account_currency = 0
-		else:
-			entry.credit = entry.credit - entry.debit
-			entry.credit_in_account_currency = entry.credit_in_account_currency \
-				- entry.debit_in_account_currency
+def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None):
+	cost_center_allocation = get_cost_center_allocation_data(gl_map[0]["company"], gl_map[0]["posting_date"])
+	if not cost_center_allocation:
+		return gl_map
 
-			entry.debit = 0
-			entry.debit_in_account_currency = 0
+	new_gl_map = []
+	for d in gl_map:
+		cost_center = d.get("cost_center")
+		if cost_center and cost_center_allocation.get(cost_center):
+			for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
+				gle = copy.deepcopy(d)
+				gle.cost_center = sub_cost_center
+				for field in ("debit", "credit", "debit_in_account_currency", "credit_in_company_currency"):
+					gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
+				new_gl_map.append(gle)
+		else:
+			new_gl_map.append(d)
+
+	return new_gl_map
+
+def get_cost_center_allocation_data(company, posting_date):
+	par = frappe.qb.DocType("Cost Center Allocation")
+	child = frappe.qb.DocType("Cost Center Allocation Percentage")
+
+	records = (
+		frappe.qb.from_(par).inner_join(child).on(par.name == child.parent)
+		.select(par.main_cost_center, child.cost_center, child.percentage)
+		.where(par.docstatus == 1)
+		.where(par.company == company)
+		.where(par.valid_from <= posting_date)
+		.orderby(par.valid_from, order=frappe.qb.desc)
+	).run(as_dict=True)
+
+	cc_allocation = frappe._dict()
+	for d in records:
+		cc_allocation.setdefault(d.main_cost_center, frappe._dict())\
+			.setdefault(d.cost_center, d.percentage)
+
+	return cc_allocation
 
 def merge_similar_entries(gl_map, precision=None):
 	merged_gl_map = []
@@ -145,6 +155,49 @@
 		if same_head:
 			return e
 
+def toggle_debit_credit_if_negative(gl_map):
+	for entry in gl_map:
+		# toggle debit, credit if negative entry
+		if flt(entry.debit) < 0:
+			entry.credit = flt(entry.credit) - flt(entry.debit)
+			entry.debit = 0.0
+
+		if flt(entry.debit_in_account_currency) < 0:
+			entry.credit_in_account_currency = \
+				flt(entry.credit_in_account_currency) - flt(entry.debit_in_account_currency)
+			entry.debit_in_account_currency = 0.0
+
+		if flt(entry.credit) < 0:
+			entry.debit = flt(entry.debit) - flt(entry.credit)
+			entry.credit = 0.0
+
+		if flt(entry.credit_in_account_currency) < 0:
+			entry.debit_in_account_currency = \
+				flt(entry.debit_in_account_currency) - flt(entry.credit_in_account_currency)
+			entry.credit_in_account_currency = 0.0
+
+		update_net_values(entry)
+
+	return gl_map
+
+def update_net_values(entry):
+	# In some scenarios net value needs to be shown in the ledger
+	# This method updates net values as debit or credit
+	if entry.post_net_value and entry.debit and entry.credit:
+		if entry.debit > entry.credit:
+			entry.debit = entry.debit - entry.credit
+			entry.debit_in_account_currency = entry.debit_in_account_currency \
+				- entry.credit_in_account_currency
+			entry.credit = 0
+			entry.credit_in_account_currency = 0
+		else:
+			entry.credit = entry.credit - entry.debit
+			entry.credit_in_account_currency = entry.credit_in_account_currency \
+				- entry.debit_in_account_currency
+
+			entry.debit = 0
+			entry.debit_in_account_currency = 0
+
 def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
 	if not from_repost:
 		validate_cwip_accounts(gl_map)
@@ -221,7 +274,7 @@
 				debit_credit_diff += flt(d.credit)
 			round_off_account_exists = True
 
-	if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
+	if round_off_account_exists and abs(debit_credit_diff) < (1.0 / (10**precision)):
 		gl_map.remove(round_off_gle)
 		return
 
@@ -266,13 +319,18 @@
 	"""
 
 	if not gl_entries:
-		gl_entries = frappe.get_all("GL Entry",
-			fields = ["*"],
-			filters = {
-				"voucher_type": voucher_type,
-				"voucher_no": voucher_no,
-				"is_cancelled": 0
-			})
+		gl_entry = frappe.qb.DocType("GL Entry")
+		gl_entries = (frappe.qb.from_(
+			gl_entry
+		).select(
+			'*'
+		).where(
+			gl_entry.voucher_type == voucher_type
+		).where(
+			gl_entry.voucher_no == voucher_no
+		).where(
+			gl_entry.is_cancelled == 0
+		).for_update()).run(as_dict=1)
 
 	if gl_entries:
 		validate_accounting_period(gl_entries)
@@ -280,23 +338,24 @@
 		set_as_cancel(gl_entries[0]['voucher_type'], gl_entries[0]['voucher_no'])
 
 		for entry in gl_entries:
-			entry['name'] = None
-			debit = entry.get('debit', 0)
-			credit = entry.get('credit', 0)
+			new_gle = copy.deepcopy(entry)
+			new_gle['name'] = None
+			debit = new_gle.get('debit', 0)
+			credit = new_gle.get('credit', 0)
 
-			debit_in_account_currency = entry.get('debit_in_account_currency', 0)
-			credit_in_account_currency = entry.get('credit_in_account_currency', 0)
+			debit_in_account_currency = new_gle.get('debit_in_account_currency', 0)
+			credit_in_account_currency = new_gle.get('credit_in_account_currency', 0)
 
-			entry['debit'] = credit
-			entry['credit'] = debit
-			entry['debit_in_account_currency'] = credit_in_account_currency
-			entry['credit_in_account_currency'] = debit_in_account_currency
+			new_gle['debit'] = credit
+			new_gle['credit'] = debit
+			new_gle['debit_in_account_currency'] = credit_in_account_currency
+			new_gle['credit_in_account_currency'] = debit_in_account_currency
 
-			entry['remarks'] = "On cancellation of " + entry['voucher_no']
-			entry['is_cancelled'] = 1
+			new_gle['remarks'] = "On cancellation of " + new_gle['voucher_no']
+			new_gle['is_cancelled'] = 1
 
-			if entry['debit'] or entry['credit']:
-				make_entry(entry, adv_adj, "Yes")
+			if new_gle['debit'] or new_gle['credit']:
+				make_entry(new_gle, adv_adj, "Yes")
 
 
 def check_freezing_date(posting_date, adv_adj=False):
diff --git a/erpnext/accounts/module_onboarding/accounts/accounts.json b/erpnext/accounts/module_onboarding/accounts/accounts.json
index 2e0ab43..aa7cdf7 100644
--- a/erpnext/accounts/module_onboarding/accounts/accounts.json
+++ b/erpnext/accounts/module_onboarding/accounts/accounts.json
@@ -13,16 +13,13 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
  "idx": 0,
  "is_complete": 0,
- "modified": "2021-08-13 11:59:35.690443",
+ "modified": "2022-01-18 18:35:52.326688",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts",
  "owner": "Administrator",
  "steps": [
   {
-   "step": "Company"
-  },
-  {
    "step": "Chart of Accounts"
   },
   {
diff --git a/erpnext/accounts/onboarding_step/company/company.json b/erpnext/accounts/onboarding_step/company/company.json
deleted file mode 100644
index 4992e4d..0000000
--- a/erpnext/accounts/onboarding_step/company/company.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "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/party.py b/erpnext/accounts/party.py
index 6b4b43d..d6f6c5b 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -58,7 +58,7 @@
 		frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
 
 	party = frappe.get_doc(party_type, party)
-	currency = party.default_currency if party.get("default_currency") else get_company_currency(company)
+	currency = party.get("default_currency") or currency or get_company_currency(company)
 
 	party_address, shipping_address = set_address_details(party_details, party, party_type, doctype, company, party_address, company_address, shipping_address)
 	set_contact_details(party_details, party, party_type)
@@ -307,7 +307,7 @@
 			.format(frappe.bold(party_type), frappe.bold(party), frappe.bold(existing_gle_currency), frappe.bold(company)), InvalidAccountCurrency)
 
 def validate_party_accounts(doc):
-
+	from erpnext.controllers.accounts_controller import validate_account_head
 	companies = []
 
 	for account in doc.get("accounts"):
@@ -330,6 +330,9 @@
 			if doc.default_currency != party_account_currency and doc.default_currency != company_default_currency:
 				frappe.throw(_("Billing currency must be equal to either default company's currency or party account currency"))
 
+		# validate if account is mapped for same company
+		validate_account_head(account.idx, account.account, account.company)
+
 
 @frappe.whitelist()
 def get_due_date(posting_date, party_type, party, company=None, bill_date=None):
diff --git a/erpnext/accounts/print_format/gst_pos_invoice/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py
similarity index 100%
rename from erpnext/accounts/print_format/gst_pos_invoice/__init__.py
rename to erpnext/accounts/print_format/gst_e_invoice/__init__.py
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
new file mode 100644
index 0000000..e658049
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -0,0 +1,173 @@
+{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
+{%- set einvoice = json.loads(doc.signed_einvoice) -%}
+
+<div class="page-break">
+	<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
+		{% if letter_head and not no_letterhead %}
+			<div class="letter-head">{{ letter_head }}</div>
+		{% endif %}
+		<div class="print-heading">
+			<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
+		</div>
+	</div>
+	{% if print_settings.repeat_header_footer %}
+	<div id="footer-html" class="visible-pdf">
+		{% if not no_letterhead and footer %}
+		<div class="letter-head-footer">
+			{{ footer }}
+		</div>
+		{% endif %}
+		<p class="text-center small page-number visible-pdf">
+			{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
+		</p>
+	</div>
+	{% endif %}
+	<h5 class="font-bold" style="margin-top: 0px;">1. Transaction Details</h5>
+	<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+		<div class="col-xs-8 column-break">
+			<div class="row data-field">
+				<div class="col-xs-4"><label>IRN</label></div>
+				<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Ack. No</label></div>
+				<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Ack. Date</label></div>
+				<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Category</label></div>
+				<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Document Type</label></div>
+				<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Document No</label></div>
+				<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
+			</div>
+		</div>
+		<div class="col-xs-4 column-break">
+			<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
+		</div>
+	</div>
+	<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">2. Party Details</h5>
+	<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+		{%- set seller = einvoice.SellerDtls -%}
+		<div class="col-xs-6 column-break">
+			<h5 style="margin-bottom: 5px;">Seller</h5>
+			<p>{{ seller.Gstin }}</p>
+			<p>{{ seller.LglNm }}</p>
+			<p>{{ seller.Addr1 }}</p>
+			{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
+			<p>{{ seller.Loc }}</p>
+			<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
+
+			{%- if einvoice.ShipDtls -%}
+				{%- set shipping = einvoice.ShipDtls -%}
+				<h5 style="margin-bottom: 5px;">Shipped From</h5>
+				<p>{{ shipping.Gstin }}</p>
+				<p>{{ shipping.LglNm }}</p>
+				<p>{{ shipping.Addr1 }}</p>
+				{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
+				<p>{{ shipping.Loc }}</p>
+				<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
+			{% endif %}
+		</div>
+		{%- set buyer = einvoice.BuyerDtls -%}
+		<div class="col-xs-6 column-break">
+			<h5 style="margin-bottom: 5px;">Buyer</h5>
+			<p>{{ buyer.Gstin }}</p>
+			<p>{{ buyer.LglNm }}</p>
+			<p>{{ buyer.Addr1 }}</p>
+			{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
+			<p>{{ buyer.Loc }}</p>
+			<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
+
+			{%- if einvoice.DispDtls -%}
+				{%- set dispatch = einvoice.DispDtls -%}
+				<h5 style="margin-bottom: 5px;">Dispatched From</h5>
+				{%- if dispatch.Gstin -%} <p>{{ dispatch.Gstin }}</p> {% endif %}
+				<p>{{ dispatch.LglNm }}</p>
+				<p>{{ dispatch.Addr1 }}</p>
+				{%- if dispatch.Addr2 -%} <p>{{ dispatch.Addr2 }}</p> {% endif %}
+				<p>{{ dispatch.Loc }}</p>
+				<p>{{ frappe.db.get_value("Address", doc.dispatch_address_name, "gst_state") }} - {{ dispatch.Pin }}</p>
+			{% endif %}
+		</div>
+	</div>
+	<div style="overflow-x: auto;">
+		<h5 class="font-bold" style="margin-top: 15px; margin-bottom: 10px;">3. Item Details</h5>
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th class="text-left" style="width: 3%;">Sr. No.</th>
+					<th class="text-left">Item</th>
+					<th class="text-left" style="width: 10%;">HSN Code</th>
+					<th class="text-left" style="width: 5%;">Qty</th>
+					<th class="text-left" style="width: 5%;">UOM</th>
+					<th class="text-left">Rate</th>
+					<th class="text-left" style="width: 5%;">Discount</th>
+					<th class="text-left">Taxable Amount</th>
+					<th class="text-left" style="width: 7%;">Tax Rate</th>
+					<th class="text-left" style="width: 5%;">Other Charges</th>
+					<th class="text-left">Total</th>
+				</tr>
+			</thead>
+			<tbody>
+				{% for item in einvoice.ItemList %}
+					<tr>
+						<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
+						<td class="text-left">{{ item.PrdDesc }}</td>
+						<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
+						<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
+						<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
+						<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
+						<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
+						<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
+					</tr>
+				{% endfor %}
+			</tbody>
+		</table>
+	</div>
+	<div style="overflow-x: auto;">
+		<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th class="text-left">Taxable Amount</th>
+					<th class="text-left">CGST</th>
+					<th class="text-left"">SGST</th>
+					<th class="text-left">IGST</th>
+					<th class="text-left">CESS</th>
+					<th class="text-left" style="width: 10%;">State CESS</th>
+					<th class="text-left">Discount</th>
+					<th class="text-left" style="width: 10%;">Other Charges</th>
+					<th class="text-left" style="width: 10%;">Round Off</th>
+					<th class="text-left">Total Value</th>
+				</tr>
+			</thead>
+			<tbody>
+				{%- set value_details = einvoice.ValDtls -%}
+				<tr>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.OthChrg, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
new file mode 100644
index 0000000..1001199
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
@@ -0,0 +1,24 @@
+{
+ "align_labels_right": 1,
+ "creation": "2020-10-10 18:01:21.032914",
+ "custom_format": 0,
+ "default_print_language": "en-US",
+ "disabled": 1,
+ "doc_type": "Sales Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "",
+ "idx": 0,
+ "line_breaks": 1,
+ "modified": "2020-10-23 19:54:40.634936",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "GST E-Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 1,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json b/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json
deleted file mode 100644
index 1aa1c02..0000000
--- a/erpnext/accounts/print_format/gst_pos_invoice/gst_pos_invoice.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "align_labels_right": 0,
- "creation": "2017-08-08 12:33:04.773099",
- "custom_format": 1,
- "disabled": 0,
- "doc_type": "Sales Invoice",
- "docstatus": 0,
- "doctype": "Print Format",
- "font": "Default",
- "html": "<style>\n\t.print-format table, .print-format tr, \n\t.print-format td, .print-format div, .print-format p {\n\t\tfont-family: Tahoma, sans-serif;\n\t\tline-height: 150%;\n\t\tvertical-align: middle;\n\t}\n\t@media screen {\n\t\t.print-format {\n\t\t\twidth: 4in;\n\t\t\tpadding: 0.25in;\n\t\t\tmin-height: 8in;\n\t\t}\n\t}\n</style>\n\n{% if letter_head %}\n    {{ letter_head }}\n{% endif %}\n<p class=\"text-center\">\n\t{{ doc.company }}<br>\n\t{% if doc.company_address_display %}\n\t\t{% set company_address = doc.company_address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t{% if \"GSTIN\" not in company_address %}\n\t\t\t{{ company_address }}\n\t\t\t<b>{{ _(\"GSTIN\") }}:</b>{{ doc.company_gstin }}\n\t\t{% else %}\n\t\t\t{{ company_address.replace(\"GSTIN\", \"<br>GSTIN\") }}\n\t\t{% endif %}\n\t{% endif %}\n\t<br>\n\t{% if doc.docstatus == 0 %}\n\t\t<b>{{ doc.status + \" \"+ (doc.select_print_heading or _(\"Invoice\")) }}</b><br>\n\t{% else %}\n\t\t<b>{{ doc.select_print_heading or _(\"Invoice\") }}</b><br>\n\t{% endif %}\n</p>\n<p>\n\t<b>{{ _(\"Receipt No\") }}:</b> {{ doc.name }}<br>\n\t<b>{{ _(\"Date\") }}:</b> {{ doc.get_formatted(\"posting_date\") }}<br>\n\t{% if doc.grand_total > 50000 %}\n\t\t{% set customer_address = doc.address_display.replace(\"\\n\", \" \").replace(\"<br>\", \" \") %}\n\t\t<b>{{ _(\"Customer\") }}:</b><br>\n\t\t{{ doc.customer_name }}<br>\n\t\t{{ customer_address }}\n\t{% endif %}\n</p>\n\n<hr>\n<table class=\"table table-condensed cart no-border\">\n\t<thead>\n\t\t<tr>\n\t\t\t<th width=\"50%\">{{ _(\"Item\") }}</b></th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Qty\") }}</th>\n\t\t\t<th width=\"25%\" class=\"text-right\">{{ _(\"Amount\") }}</th>\n\t\t</tr>\n\t</thead>\n\t<tbody>\n\t\t{%- for item in doc.items -%}\n\t\t<tr>\n\t\t\t<td>\n\t\t\t\t{{ item.item_code }}\n\t\t\t\t{%- if item.item_name != item.item_code -%}\n\t\t\t\t\t<br>{{ item.item_name }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.gst_hsn_code -%}\n\t\t\t\t\t<br><b>{{ _(\"HSN/SAC\") }}:</b> {{ item.gst_hsn_code }}\n\t\t\t\t{%- endif -%}\n\t\t\t\t{%- if item.serial_no -%}\n\t\t\t\t\t<br><b>{{ _(\"Serial No\") }}:</b> {{ item.serial_no }}\n\t\t\t\t{%- endif -%}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">{{ item.qty }}<br>@ {{ item.rate }}</td>\n\t\t\t<td class=\"text-right\">{{ item.get_formatted(\"amount\") }}</td>\n\t\t</tr>\n\t\t{%- endfor -%}\n\t</tbody>\n</table>\n<table class=\"table table-condensed no-border\">\n\t<tbody>\n\t\t<tr>\n\t\t\t{% if doc.flags.show_inclusive_tax_in_print %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total Excl. Tax\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"net_total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% else %}\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ _(\"Total\") }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ doc.get_formatted(\"total\", doc) }}\n\t\t\t\t</td>\n\t\t\t{% endif %}\n\t\t</tr>\n\t\t{%- for row in doc.taxes -%}\n\t\t  {%- if (not row.included_in_print_rate or doc.flags.show_inclusive_tax_in_print) and row.tax_amount != 0 -%}\n\t\t\t<tr>\n\t\t\t\t<td class=\"text-right\" style=\"width: 70%\">\n\t\t\t\t\t{{ row.description }}\n\t\t\t\t</td>\n\t\t\t\t<td class=\"text-right\">\n\t\t\t\t\t{{ row.get_formatted(\"tax_amount\", doc) }}\n\t\t\t\t</td>\n\t\t\t<tr>\n\t\t  {%- endif -%}\n\t\t{%- endfor -%}\n\t\t{%- if doc.discount_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t{{ _(\"Discount\") }}\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"discount_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Grand Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"grand_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- if doc.rounded_total -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Rounded Total\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"rounded_total\") }}\n\t\t\t</td>\n\t\t</tr>\n\t\t{%- endif -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Paid Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"paid_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- if doc.change_amount -%}\n\t\t<tr>\n\t\t\t<td class=\"text-right\" style=\"width: 75%\">\n\t\t\t\t<b>{{ _(\"Change Amount\") }}</b>\n\t\t\t</td>\n\t\t\t<td class=\"text-right\">\n\t\t\t\t{{ doc.get_formatted(\"change_amount\") }}\n\t\t\t</td>\n\t\t</tr>\n\t{%- endif -%}\n\t</tbody>\n</table>\n<p>{{ doc.terms or \"\" }}</p>\n<p class=\"text-center\">{{ _(\"Thank you, please visit again.\") }}</p>",
- "idx": 0,
- "line_breaks": 0,
- "modified": "2020-04-29 16:39:12.936215",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "GST POS Invoice",
- "owner": "Administrator",
- "print_format_builder": 0,
- "print_format_type": "Jinja",
- "raw_printing": 0,
- "show_section_headings": 0,
- "standard": "Yes"
-}
\ No newline at end of file
diff --git a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
index 4559fa9..8e3bd8b 100644
--- a/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
+++ b/erpnext/accounts/report/accounts_receivable_summary/accounts_receivable_summary.py
@@ -5,7 +5,6 @@
 import frappe
 from frappe import _, scrub
 from frappe.utils import cint, flt
-from six import iteritems
 
 from erpnext.accounts.party import get_partywise_advanced_payment_amount
 from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
@@ -40,7 +39,7 @@
 		if self.filters.show_gl_balance:
 			gl_balance_map = get_gl_balance(self.filters.report_date)
 
-		for party, party_dict in iteritems(self.party_total):
+		for party, party_dict in self.party_total.items():
 			if party_dict.outstanding == 0:
 				continue
 
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py
index dc1f7aa..f10a5ea 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.py
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py
@@ -120,11 +120,11 @@
 	opening_balance = 0
 	float_precision = cint(frappe.db.get_default("float_precision")) or 2
 	if asset:
-		opening_balance = flt(asset[0].get("opening_balance", 0), float_precision)
+		opening_balance = flt(asset[-1].get("opening_balance", 0), float_precision)
 	if liability:
-		opening_balance -= flt(liability[0].get("opening_balance", 0), float_precision)
+		opening_balance -= flt(liability[-1].get("opening_balance", 0), float_precision)
 	if equity:
-		opening_balance -= flt(equity[0].get("opening_balance", 0), float_precision)
+		opening_balance -= flt(equity[-1].get("opening_balance", 0), float_precision)
 
 	opening_balance = flt(opening_balance, float_precision)
 	if opening_balance:
diff --git a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
index 6c401fb..b72d266 100644
--- a/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
+++ b/erpnext/accounts/report/bank_reconciliation_statement/bank_reconciliation_statement.py
@@ -4,7 +4,12 @@
 
 import frappe
 from frappe import _
-from frappe.utils import flt, getdate, nowdate
+from frappe.query_builder.custom import ConstantColumn
+from frappe.query_builder.functions import Sum
+from frappe.utils import flt, getdate
+from pypika import CustomFunction
+
+from erpnext.accounts.utils import get_balance_on
 
 
 def execute(filters=None):
@@ -18,7 +23,6 @@
 
 	data = get_entries(filters)
 
-	from erpnext.accounts.utils import get_balance_on
 	balance_as_per_system = get_balance_on(filters["account"], filters["report_date"])
 
 	total_debit, total_credit = 0,0
@@ -118,7 +122,21 @@
 	]
 
 def get_entries(filters):
-	journal_entries = frappe.db.sql("""
+	journal_entries = get_journal_entries(filters)
+
+	payment_entries = get_payment_entries(filters)
+
+	loan_entries = get_loan_entries(filters)
+
+	pos_entries = []
+	if filters.include_pos_transactions:
+		pos_entries = get_pos_entries(filters)
+
+	return sorted(list(payment_entries)+list(journal_entries+list(pos_entries) + list(loan_entries)),
+			key=lambda k: getdate(k['posting_date']))
+
+def get_journal_entries(filters):
+	return frappe.db.sql("""
 		select "Journal Entry" as payment_document, jv.posting_date,
 			jv.name as payment_entry, jvd.debit_in_account_currency as debit,
 			jvd.credit_in_account_currency as credit, jvd.against_account,
@@ -130,7 +148,8 @@
 			and ifnull(jv.clearance_date, '4000-01-01') > %(report_date)s
 			and ifnull(jv.is_opening, 'No') = 'No'""", filters, as_dict=1)
 
-	payment_entries = frappe.db.sql("""
+def get_payment_entries(filters):
+	return frappe.db.sql("""
 		select
 			"Payment Entry" as payment_document, name as payment_entry,
 			reference_no, reference_date as ref_date,
@@ -145,9 +164,8 @@
 			and ifnull(clearance_date, '4000-01-01') > %(report_date)s
 	""", filters, as_dict=1)
 
-	pos_entries = []
-	if filters.include_pos_transactions:
-		pos_entries = frappe.db.sql("""
+def get_pos_entries(filters):
+	return frappe.db.sql("""
 			select
 				"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
 				si.posting_date, si.debit_to as against_account, sip.clearance_date,
@@ -161,8 +179,42 @@
 				si.posting_date ASC, si.name DESC
 		""", filters, as_dict=1)
 
-	return sorted(list(payment_entries)+list(journal_entries+list(pos_entries)),
-			key=lambda k: k['posting_date'] or getdate(nowdate()))
+def get_loan_entries(filters):
+	loan_docs = []
+	for doctype in ["Loan Disbursement", "Loan Repayment"]:
+		loan_doc = frappe.qb.DocType(doctype)
+		ifnull = CustomFunction('IFNULL', ['value', 'default'])
+
+		if doctype == "Loan Disbursement":
+			amount_field = (loan_doc.disbursed_amount).as_("credit")
+			posting_date = (loan_doc.disbursement_date).as_("posting_date")
+			account = loan_doc.disbursement_account
+		else:
+			amount_field = (loan_doc.amount_paid).as_("debit")
+			posting_date = (loan_doc.posting_date).as_("posting_date")
+			account = loan_doc.payment_account
+
+		entries = frappe.qb.from_(loan_doc).select(
+			ConstantColumn(doctype).as_("payment_document"),
+			(loan_doc.name).as_("payment_entry"),
+			(loan_doc.reference_number).as_("reference_no"),
+			(loan_doc.reference_date).as_("ref_date"),
+			amount_field,
+			posting_date,
+		).where(
+			loan_doc.docstatus == 1
+		).where(
+			account == filters.get('account')
+		).where(
+			posting_date <= getdate(filters.get('report_date'))
+		).where(
+			ifnull(loan_doc.clearance_date, '4000-01-01') > getdate(filters.get('report_date'))
+		).run(as_dict=1)
+
+		loan_docs.extend(entries)
+
+	return loan_docs
+
 
 def get_amounts_not_reflected_in_system(filters):
 	je_amount = frappe.db.sql("""
@@ -182,7 +234,40 @@
 
 	pe_amount = flt(pe_amount[0][0]) if pe_amount else 0.0
 
-	return je_amount + pe_amount
+	loan_amount = get_loan_amount(filters)
+
+	return je_amount + pe_amount + loan_amount
+
+def get_loan_amount(filters):
+	total_amount = 0
+	for doctype in ["Loan Disbursement", "Loan Repayment"]:
+		loan_doc = frappe.qb.DocType(doctype)
+		ifnull = CustomFunction('IFNULL', ['value', 'default'])
+
+		if doctype == "Loan Disbursement":
+			amount_field = Sum(loan_doc.disbursed_amount)
+			posting_date = (loan_doc.disbursement_date).as_("posting_date")
+			account = loan_doc.disbursement_account
+		else:
+			amount_field = Sum(loan_doc.amount_paid)
+			posting_date = (loan_doc.posting_date).as_("posting_date")
+			account = loan_doc.payment_account
+
+		amount = frappe.qb.from_(loan_doc).select(
+			amount_field
+		).where(
+			loan_doc.docstatus == 1
+		).where(
+			account == filters.get('account')
+		).where(
+			posting_date > getdate(filters.get('report_date'))
+		).where(
+			ifnull(loan_doc.clearance_date, '4000-01-01') <= getdate(filters.get('report_date'))
+		).run()[0][0]
+
+		total_amount += flt(amount)
+
+	return amount
 
 def get_balance_row(label, amount, account_currency):
 	if amount > 0:
diff --git a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
index 3bb590a..56ee500 100644
--- a/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
+++ b/erpnext/accounts/report/budget_variance_report/budget_variance_report.py
@@ -29,18 +29,6 @@
 		dimension_items = cam_map.get(dimension)
 		if dimension_items:
 			data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, 0)
-		else:
-			DCC_allocation = frappe.db.sql('''SELECT parent, sum(percentage_allocation) as percentage_allocation
-				FROM `tabDistributed Cost Center`
-				WHERE cost_center IN %(dimension)s
-				AND parent NOT IN %(dimension)s
-				GROUP BY parent''',{'dimension':[dimension]})
-			if DCC_allocation:
-				filters['budget_against_filter'] = [DCC_allocation[0][0]]
-				ddc_cam_map = get_dimension_account_month_map(filters)
-				dimension_items = ddc_cam_map.get(DCC_allocation[0][0])
-				if dimension_items:
-					data = get_final_data(dimension, dimension_items, filters, period_month_ranges, data, DCC_allocation[0][1])
 
 	chart = get_chart_data(filters, columns, data)
 
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index 758e3e9..1e20f7b 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -354,9 +354,6 @@
 		if d.parent_account:
 			account = d.parent_account_name
 
-			# if not accounts_by_name.get(account):
-			# 	continue
-
 			for company in companies:
 				accounts_by_name[account][company] = \
 					accounts_by_name[account].get(company, 0.0) + d.get(company, 0.0)
@@ -367,7 +364,7 @@
 				accounts_by_name[account].get("opening_balance", 0.0) + d.get("opening_balance", 0.0)
 
 def get_account_heads(root_type, companies, filters):
-	accounts = get_accounts(root_type, filters)
+	accounts = get_accounts(root_type, companies)
 
 	if not accounts:
 		return None, None, None
@@ -396,7 +393,7 @@
 
 	for account in accounts:
 		if account.parent_account:
-			account["parent_account_name"] = name_to_account_map[account.parent_account]
+			account["parent_account_name"] = name_to_account_map.get(account.parent_account)
 
 	return accounts
 
@@ -419,12 +416,19 @@
 	return frappe.db.sql_list("""select name from `tabCompany`
 		where lft >= {0} and rgt <= {1} order by lft, rgt""".format(lft, rgt))
 
-def get_accounts(root_type, filters):
-	return frappe.db.sql(""" select name, is_group, company,
-			parent_account, lft, rgt, root_type, report_type, account_name, account_number
-		from
-			`tabAccount` where company = %s and root_type = %s
-		""" , (filters.get('company'), root_type), as_dict=1)
+def get_accounts(root_type, companies):
+	accounts = []
+	added_accounts = []
+
+	for company in companies:
+		for account in frappe.get_all("Account", fields=["name", "is_group", "company",
+			"parent_account", "lft", "rgt", "root_type", "report_type", "account_name", "account_number"],
+			filters={"company": company, "root_type": root_type}):
+			if account.account_name not in added_accounts:
+				accounts.append(account)
+				added_accounts.append(account.account_name)
+
+	return accounts
 
 def prepare_data(accounts, start_date, end_date, balance_must_be, companies, company_currency, filters):
 	data = []
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
index 1de6fb6..86eb213 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
@@ -17,10 +17,42 @@
 class TestDeferredRevenueAndExpense(unittest.TestCase):
 	@classmethod
 	def setUpClass(self):
-		clear_old_entries()
+		clear_accounts_and_items()
 		create_company()
+		self.maxDiff = None
+
+	def clear_old_entries(self):
+		sinv = qb.DocType("Sales Invoice")
+		sinv_item = qb.DocType("Sales Invoice Item")
+		pinv = qb.DocType("Purchase Invoice")
+		pinv_item = qb.DocType("Purchase Invoice Item")
+
+		# delete existing invoices with deferred items
+		deferred_invoices = (
+			qb.from_(sinv)
+			.join(sinv_item)
+			.on(sinv.name == sinv_item.parent)
+			.select(sinv.name)
+			.where(sinv_item.enable_deferred_revenue == 1)
+			.run()
+		)
+		if deferred_invoices:
+			qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
+
+		deferred_invoices = (
+			qb.from_(pinv)
+			.join(pinv_item)
+			.on(pinv.name == pinv_item.parent)
+			.select(pinv.name)
+			.where(pinv_item.enable_deferred_expense == 1)
+			.run()
+		)
+		if deferred_invoices:
+			qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
 
 	def test_deferred_revenue(self):
+		self.clear_old_entries()
+
 		# created deferred expense accounts, if not found
 		deferred_revenue_account = create_account(
 			account_name="Deferred Revenue",
@@ -108,6 +140,8 @@
 		self.assertEqual(report.period_total, expected)
 
 	def test_deferred_expense(self):
+		self.clear_old_entries()
+
 		# created deferred expense accounts, if not found
 		deferred_expense_account = create_account(
 			account_name="Deferred Expense",
@@ -198,6 +232,91 @@
 		]
 		self.assertEqual(report.period_total, expected)
 
+	def test_zero_months(self):
+		self.clear_old_entries()
+		# created deferred expense accounts, if not found
+		deferred_revenue_account = create_account(
+			account_name="Deferred Revenue",
+			parent_account="Current Liabilities - _CD",
+			company="_Test Company DR",
+		)
+
+		acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
+		acc_settings.book_deferred_entries_based_on = "Months"
+		acc_settings.save()
+
+		customer = frappe.new_doc("Customer")
+		customer.customer_name = "_Test Customer DR"
+		customer.type = "Individual"
+		customer.insert()
+
+		item = create_item(
+			"_Test Internet Subscription",
+			is_stock_item=0,
+			warehouse="All Warehouses - _CD",
+			company="_Test Company DR",
+		)
+		item.enable_deferred_revenue = 1
+		item.deferred_revenue_account = deferred_revenue_account
+		item.no_of_months = 0
+		item.save()
+
+		si = create_sales_invoice(
+			item=item.name,
+			company="_Test Company DR",
+			customer="_Test Customer DR",
+			debit_to="Debtors - _CD",
+			posting_date="2021-05-01",
+			parent_cost_center="Main - _CD",
+			cost_center="Main - _CD",
+			do_not_submit=True,
+			rate=300,
+			price_list_rate=300,
+		)
+		si.items[0].enable_deferred_revenue = 1
+		si.items[0].deferred_revenue_account = deferred_revenue_account
+		si.items[0].income_account = "Sales - _CD"
+		si.save()
+		si.submit()
+
+		pda = frappe.get_doc(
+			dict(
+				doctype="Process Deferred Accounting",
+				posting_date=nowdate(),
+				start_date="2021-05-01",
+				end_date="2021-08-01",
+				type="Income",
+				company="_Test Company DR",
+			)
+		)
+		pda.insert()
+		pda.submit()
+
+		# execute report
+		fiscal_year = frappe.get_doc("Fiscal Year", frappe.defaults.get_user_default("fiscal_year"))
+		self.filters = frappe._dict(
+			{
+				"company": frappe.defaults.get_user_default("Company"),
+				"filter_based_on": "Date Range",
+				"period_start_date": "2021-05-01",
+				"period_end_date": "2021-08-01",
+				"from_fiscal_year": fiscal_year.year,
+				"to_fiscal_year": fiscal_year.year,
+				"periodicity": "Monthly",
+				"type": "Revenue",
+				"with_upcoming_postings": False,
+			}
+		)
+
+		report = Deferred_Revenue_and_Expense_Report(filters=self.filters)
+		report.run()
+		expected = [
+			{"key": "may_2021", "total": 300.0, "actual": 300.0},
+			{"key": "jun_2021", "total": 0, "actual": 0},
+			{"key": "jul_2021", "total": 0, "actual": 0},
+			{"key": "aug_2021", "total": 0, "actual": 0},
+		]
+		self.assertEqual(report.period_total, expected)
 
 def create_company():
 	company = frappe.db.exists("Company", "_Test Company DR")
@@ -209,15 +328,11 @@
 		company.insert()
 
 
-def clear_old_entries():
+def clear_accounts_and_items():
 	item = qb.DocType("Item")
 	account = qb.DocType("Account")
 	customer = qb.DocType("Customer")
 	supplier = qb.DocType("Supplier")
-	sinv = qb.DocType("Sales Invoice")
-	sinv_item = qb.DocType("Sales Invoice Item")
-	pinv = qb.DocType("Purchase Invoice")
-	pinv_item = qb.DocType("Purchase Invoice Item")
 
 	qb.from_(account).delete().where(
 		(account.account_name == "Deferred Revenue")
@@ -228,26 +343,3 @@
 	).run()
 	qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
 	qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
-
-	# delete existing invoices with deferred items
-	deferred_invoices = (
-		qb.from_(sinv)
-		.join(sinv_item)
-		.on(sinv.name == sinv_item.parent)
-		.select(sinv.name)
-		.where(sinv_item.enable_deferred_revenue == 1)
-		.run()
-	)
-	if deferred_invoices:
-		qb.from_(sinv).delete().where(sinv.name.isin(deferred_invoices)).run()
-
-	deferred_invoices = (
-		qb.from_(pinv)
-		.join(pinv_item)
-		.on(pinv.name == pinv_item.parent)
-		.select(pinv.name)
-		.where(pinv_item.enable_deferred_expense == 1)
-		.run()
-	)
-	if deferred_invoices:
-		qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 1e89b65..db28cdf 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -282,7 +282,8 @@
 	total_row = {
 		"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
 		"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
-		"currency": company_currency
+		"currency": company_currency,
+		"opening_balance": 0.0
 	}
 
 	for row in out:
@@ -294,6 +295,7 @@
 
 			total_row.setdefault("total", 0.0)
 			total_row["total"] += flt(row["total"])
+			total_row["opening_balance"] += row["opening_balance"]
 			row["total"] = ""
 
 	if "total" in total_row:
@@ -387,42 +389,15 @@
 					key: value
 				})
 
-		distributed_cost_center_query = ""
-		if filters and filters.get('cost_center'):
-			distributed_cost_center_query = """
-			UNION ALL
-			SELECT posting_date,
-				account,
-				debit*(DCC_allocation.percentage_allocation/100) as debit,
-				credit*(DCC_allocation.percentage_allocation/100) as credit,
-				is_opening,
-				fiscal_year,
-				debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
-				credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency,
-				account_currency
-			FROM `tabGL Entry`,
-			(
-				SELECT parent, sum(percentage_allocation) as percentage_allocation
-				FROM `tabDistributed Cost Center`
-				WHERE cost_center IN %(cost_center)s
-				AND parent NOT IN %(cost_center)s
-				GROUP BY parent
-			) as DCC_allocation
-			WHERE company=%(company)s
-			{additional_conditions}
-			AND posting_date <= %(to_date)s
-			AND is_cancelled = 0
-			AND cost_center = DCC_allocation.parent
-			""".format(additional_conditions=additional_conditions.replace("and cost_center in %(cost_center)s ", ''))
-
-		gl_entries = frappe.db.sql("""select posting_date, account, debit, credit, is_opening, fiscal_year, debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
+		gl_entries = frappe.db.sql("""
+			select posting_date, account, debit, credit, is_opening, fiscal_year,
+				debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
 			where company=%(company)s
 			{additional_conditions}
 			and posting_date <= %(to_date)s
-			and is_cancelled = 0
-			{distributed_cost_center_query}""".format(
-				additional_conditions=additional_conditions,
-				distributed_cost_center_query=distributed_cost_center_query), gl_filters, as_dict=True) #nosec
+			and is_cancelled = 0""".format(
+			additional_conditions=additional_conditions), gl_filters, as_dict=True
+		)
 
 		if filters and filters.get('presentation_currency'):
 			convert_to_presentation_currency(gl_entries, get_currency(filters), filters.get('company'))
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index 7f27920..4ff0297 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -176,44 +176,7 @@
 	if accounting_dimensions:
 		dimension_fields = ', '.join(accounting_dimensions) + ','
 
-	distributed_cost_center_query = ""
-	if filters and filters.get('cost_center'):
-		select_fields_with_percentage = """, debit*(DCC_allocation.percentage_allocation/100) as debit,
-		credit*(DCC_allocation.percentage_allocation/100) as credit,
-		debit_in_account_currency*(DCC_allocation.percentage_allocation/100) as debit_in_account_currency,
-		credit_in_account_currency*(DCC_allocation.percentage_allocation/100) as credit_in_account_currency """
-
-		distributed_cost_center_query = """
-		UNION ALL
-		SELECT name as gl_entry,
-			posting_date,
-			account,
-			party_type,
-			party,
-			voucher_type,
-			voucher_no, {dimension_fields}
-			cost_center, project,
-			against_voucher_type,
-			against_voucher,
-			account_currency,
-			remarks, against,
-			is_opening, `tabGL Entry`.creation {select_fields_with_percentage}
-		FROM `tabGL Entry`,
-		(
-			SELECT parent, sum(percentage_allocation) as percentage_allocation
-			FROM `tabDistributed Cost Center`
-			WHERE cost_center IN %(cost_center)s
-			AND parent NOT IN %(cost_center)s
-			GROUP BY parent
-		) as DCC_allocation
-		WHERE company=%(company)s
-		{conditions}
-		AND posting_date <= %(to_date)s
-		AND cost_center = DCC_allocation.parent
-		""".format(dimension_fields=dimension_fields,select_fields_with_percentage=select_fields_with_percentage, conditions=get_conditions(filters).replace("and cost_center in %(cost_center)s ", ''))
-
-	gl_entries = frappe.db.sql(
-		"""
+	gl_entries = frappe.db.sql("""
 		select
 			name as gl_entry, posting_date, account, party_type, party,
 			voucher_type, voucher_no, {dimension_fields}
@@ -222,13 +185,11 @@
 			remarks, against, is_opening, creation {select_fields}
 		from `tabGL Entry`
 		where company=%(company)s {conditions}
-		{distributed_cost_center_query}
 		{order_by_statement}
-		""".format(
-			dimension_fields=dimension_fields, select_fields=select_fields, conditions=get_conditions(filters), distributed_cost_center_query=distributed_cost_center_query,
-			order_by_statement=order_by_statement
-		),
-		filters, as_dict=1)
+	""".format(
+		dimension_fields=dimension_fields, select_fields=select_fields,
+		conditions=get_conditions(filters), order_by_statement=order_by_statement
+	), filters, as_dict=1)
 
 	if filters.get('presentation_currency'):
 		return convert_to_presentation_currency(gl_entries, currency_map, filters.get('company'))
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.js b/erpnext/accounts/report/gross_profit/gross_profit.js
index 685f2d6..158ff4d 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.js
+++ b/erpnext/accounts/report/gross_profit/gross_profit.js
@@ -8,20 +8,22 @@
 			"label": __("Company"),
 			"fieldtype": "Link",
 			"options": "Company",
-			"reqd": 1,
-			"default": frappe.defaults.get_user_default("Company")
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
 		},
 		{
 			"fieldname":"from_date",
 			"label": __("From Date"),
 			"fieldtype": "Date",
-			"default": frappe.defaults.get_user_default("year_start_date")
+			"default": frappe.defaults.get_user_default("year_start_date"),
+			"reqd": 1
 		},
 		{
 			"fieldname":"to_date",
 			"label": __("To Date"),
 			"fieldtype": "Date",
-			"default": frappe.defaults.get_user_default("year_end_date")
+			"default": frappe.defaults.get_user_default("year_end_date"),
+			"reqd": 1
 		},
 		{
 			"fieldname":"sales_invoice",
@@ -42,6 +44,11 @@
 	"parent_field": "parent_invoice",
 	"initial_depth": 3,
 	"formatter": function(value, row, column, data, default_formatter) {
+		if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
+			column._options = "Sales Invoice";
+		} else {
+			column._options = "Item";
+		}
 		value = default_formatter(value, row, column, data);
 
 		if (data && (data.indent == 0.0 || row[1].content == "Total")) {
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.json b/erpnext/accounts/report/gross_profit/gross_profit.json
index 76c560a..0730ffd 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.json
+++ b/erpnext/accounts/report/gross_profit/gross_profit.json
@@ -1,5 +1,5 @@
 {
- "add_total_row": 0,
+ "add_total_row": 1,
  "columns": [],
  "creation": "2013-02-25 17:03:34",
  "disable_prepared_report": 0,
@@ -9,7 +9,7 @@
  "filters": [],
  "idx": 3,
  "is_standard": "Yes",
- "modified": "2021-11-13 19:14:23.730198",
+ "modified": "2022-02-11 10:18:36.956558",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Gross Profit",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 84effc0..b03bb9b 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -70,43 +70,42 @@
 		data.append(row)
 
 def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_columns, data):
-	for idx, src in enumerate(gross_profit_data.grouped_data):
+	for src in gross_profit_data.grouped_data:
 		row = []
 		for col in group_wise_columns.get(scrub(filters.group_by)):
 			row.append(src.get(col))
 
 		row.append(filters.currency)
-		if idx == len(gross_profit_data.grouped_data)-1:
-			row[0] = "Total"
 
 		data.append(row)
 
 def get_columns(group_wise_columns, filters):
 	columns = []
 	column_map = frappe._dict({
-		"parent": _("Sales Invoice") + ":Link/Sales Invoice:120",
-		"invoice_or_item": _("Sales Invoice") + ":Link/Sales Invoice:120",
-		"posting_date": _("Posting Date") + ":Date:100",
-		"posting_time": _("Posting Time") + ":Data:100",
-		"item_code": _("Item Code") + ":Link/Item:100",
-		"item_name": _("Item Name") + ":Data:100",
-		"item_group": _("Item Group") + ":Link/Item Group:100",
-		"brand": _("Brand") + ":Link/Brand:100",
-		"description": _("Description") +":Data:100",
-		"warehouse": _("Warehouse") + ":Link/Warehouse:100",
-		"qty": _("Qty") + ":Float:80",
-		"base_rate": _("Avg. Selling Rate") + ":Currency/currency:100",
-		"buying_rate": _("Valuation Rate") + ":Currency/currency:100",
-		"base_amount": _("Selling Amount") + ":Currency/currency:100",
-		"buying_amount": _("Buying Amount") + ":Currency/currency:100",
-		"gross_profit": _("Gross Profit") + ":Currency/currency:100",
-		"gross_profit_percent": _("Gross Profit %") + ":Percent:100",
-		"project": _("Project") + ":Link/Project:100",
-		"sales_person": _("Sales person"),
-		"allocated_amount": _("Allocated Amount") + ":Currency/currency:100",
-		"customer": _("Customer") + ":Link/Customer:100",
-		"customer_group": _("Customer Group") + ":Link/Customer Group:100",
-		"territory": _("Territory") + ":Link/Territory:100"
+		"parent": {"label": _('Sales Invoice'), "fieldname": "parent_invoice", "fieldtype": "Link", "options": "Sales Invoice", "width": 120},
+		"invoice_or_item": {"label": _('Sales Invoice'), "fieldtype": "Link", "options": "Sales Invoice", "width": 120},
+		"posting_date": {"label": _('Posting Date'), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
+		"posting_time": {"label": _('Posting Time'), "fieldname": "posting_time", "fieldtype": "Data", "width": 100},
+		"item_code": {"label": _('Item Code'), "fieldname": "item_code", "fieldtype": "Link", "options": "Item", "width": 100},
+		"item_name": {"label": _('Item Name'), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
+		"item_group": {"label": _('Item Group'), "fieldname": "item_group", "fieldtype": "Link", "options": "Item Group", "width": 100},
+		"brand": {"label": _('Brand'), "fieldtype": "Link", "options": "Brand", "width": 100},
+		"description": {"label": _('Description'), "fieldname": "description",  "fieldtype": "Data", "width": 100},
+		"warehouse": {"label": _('Warehouse'), "fieldname": "warehouse", "fieldtype": "Link", "options": "warehouse", "width": 100},
+		"qty": {"label": _('Qty'), "fieldname": "qty", "fieldtype": "Float", "width": 80},
+		"base_rate": {"label": _('Avg. Selling Rate'), "fieldname": "avg._selling_rate",  "fieldtype": "Currency", "options": "currency", "width": 100},
+		"buying_rate": {"label": _('Valuation Rate'), "fieldname": "valuation_rate", "fieldtype": "Currency", "options": "currency", "width": 100},
+		"base_amount": {"label": _('Selling Amount'), "fieldname": "selling_amount", "fieldtype": "Currency", "options": "currency", "width": 100},
+		"buying_amount": {"label": _('Buying Amount'), "fieldname": "buying_amount", "fieldtype": "Currency", "options": "currency", "width": 100},
+		"gross_profit": {"label": _('Gross Profit'), "fieldname": "gross_profit", "fieldtype": "Currency", "options": "currency", "width": 100},
+		"gross_profit_percent": {"label": _('Gross Profit Percent'), "fieldname": "gross_profit_%",
+			"fieldtype": "Percent", "width": 100},
+		"project": {"label": _('Project'), "fieldname": "project", "fieldtype": "Link", "options": "Project", "width": 100},
+		"sales_person": {"label": _('Sales Person'), "fieldname": "sales_person", "fieldtype": "Data","width": 100},
+		"allocated_amount": {"label": _('Allocated Amount'), "fieldname": "allocated_amount", "fieldtype": "Currency", "options": "currency", "width": 100},
+		"customer": {"label": _('Customer'), "fieldname": "customer", "fieldtype": "Link", "options": "Customer", "width": 100},
+		"customer_group": {"label": _('Customer Group'), "fieldname": "customer_group", "fieldtype": "Link", "options": "customer", "width": 100},
+		"territory": {"label": _('Territory'), "fieldname": "territory",  "fieldtype": "Link", "options": "territory", "width": 100},
 	})
 
 	for col in group_wise_columns.get(scrub(filters.group_by)):
@@ -173,7 +172,7 @@
 			buying_amount = 0
 
 		for row in reversed(self.si_list):
-			if self.skip_row(row, self.product_bundles):
+			if self.skip_row(row):
 				continue
 
 			row.base_amount = flt(row.base_net_amount, self.currency_precision)
@@ -223,16 +222,6 @@
 			self.get_average_rate_based_on_group_by()
 
 	def get_average_rate_based_on_group_by(self):
-		# sum buying / selling totals for group
-		self.totals = frappe._dict(
-			qty=0,
-			base_amount=0,
-			buying_amount=0,
-			gross_profit=0,
-			gross_profit_percent=0,
-			base_rate=0,
-			buying_rate=0
-		)
 		for key in list(self.grouped):
 			if self.filters.get("group_by") != "Invoice":
 				for i, row in enumerate(self.grouped[key]):
@@ -244,7 +233,6 @@
 						new_row.base_amount += flt(row.base_amount, self.currency_precision)
 				new_row = self.set_average_rate(new_row)
 				self.grouped_data.append(new_row)
-				self.add_to_totals(new_row)
 			else:
 				for i, row in enumerate(self.grouped[key]):
 					if row.indent == 1.0:
@@ -258,17 +246,6 @@
 						if (flt(row.qty) or row.base_amount):
 							row = self.set_average_rate(row)
 							self.grouped_data.append(row)
-						self.add_to_totals(row)
-
-		self.set_average_gross_profit(self.totals)
-
-		if self.filters.get("group_by") == "Invoice":
-			self.totals.indent = 0.0
-			self.totals.parent_invoice = ""
-			self.totals.invoice_or_item = "Total"
-			self.si_list.append(self.totals)
-		else:
-			self.grouped_data.append(self.totals)
 
 	def is_not_invoice_row(self, row):
 		return (self.filters.get("group_by") == "Invoice" and row.indent != 0.0) or self.filters.get("group_by") != "Invoice"
@@ -284,11 +261,6 @@
 		new_row.gross_profit_percent = flt(((new_row.gross_profit / new_row.base_amount) * 100.0), self.currency_precision) \
 			if new_row.base_amount else 0
 
-	def add_to_totals(self, new_row):
-		for key in self.totals:
-			if new_row.get(key):
-				self.totals[key] += new_row[key]
-
 	def get_returned_invoice_items(self):
 		returned_invoices = frappe.db.sql("""
 			select
@@ -306,12 +278,12 @@
 			self.returned_invoices.setdefault(inv.return_against, frappe._dict())\
 				.setdefault(inv.item_code, []).append(inv)
 
-	def skip_row(self, row, product_bundles):
+	def skip_row(self, row):
 		if self.filters.get("group_by") != "Invoice":
 			if not row.get(scrub(self.filters.get("group_by", ""))):
 				return True
-		elif row.get("is_return") == 1:
-			return True
+
+		return False
 
 	def get_buying_amount_from_product_bundle(self, row, product_bundle):
 		buying_amount = 0.0
@@ -369,20 +341,37 @@
 		return self.average_buying_rate[item_code]
 
 	def get_last_purchase_rate(self, item_code, row):
-		condition = ''
-		if row.project:
-			condition += " AND a.project=%s" % (frappe.db.escape(row.project))
-		elif row.cost_center:
-			condition += " AND a.cost_center=%s" % (frappe.db.escape(row.cost_center))
-		if self.filters.to_date:
-			condition += " AND modified='%s'" % (self.filters.to_date)
+		purchase_invoice = frappe.qb.DocType("Purchase Invoice")
+		purchase_invoice_item = frappe.qb.DocType("Purchase Invoice Item")
 
-		last_purchase_rate = frappe.db.sql("""
-		select (a.base_rate / a.conversion_factor)
-		from `tabPurchase Invoice Item` a
-		where a.item_code = %s and a.docstatus=1
-		{0}
-		order by a.modified desc limit 1""".format(condition), item_code)
+		query = (frappe.qb.from_(purchase_invoice_item)
+			.inner_join(
+				purchase_invoice
+			).on(
+				purchase_invoice.name == purchase_invoice_item.parent
+			).select(
+				purchase_invoice_item.base_rate / purchase_invoice_item.conversion_factor
+			).where(
+				purchase_invoice.docstatus == 1
+			).where(
+				purchase_invoice.posting_date <= self.filters.to_date
+			).where(
+				purchase_invoice_item.item_code == item_code
+			))
+
+		if row.project:
+			query.where(
+				purchase_invoice_item.project == row.project
+			)
+
+		if row.cost_center:
+			query.where(
+				purchase_invoice_item.cost_center == row.cost_center
+			)
+
+		query.orderby(purchase_invoice.posting_date, order=frappe.qb.desc)
+		query.limit(1)
+		last_purchase_rate = query.run()
 
 		return flt(last_purchase_rate[0][0]) if last_purchase_rate else 0
 
diff --git a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
index 3dcb862..f4b8731 100644
--- a/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
+++ b/erpnext/accounts/report/profitability_analysis/profitability_analysis.py
@@ -109,7 +109,6 @@
 
 def prepare_data(accounts, filters, total_row, parent_children_map, based_on):
 	data = []
-	new_accounts = accounts
 	company_currency = frappe.get_cached_value('Company',  filters.get("company"),  "default_currency")
 
 	for d in accounts:
@@ -123,19 +122,6 @@
 			"currency": company_currency,
 			"based_on": based_on
 		}
-		if based_on == 'cost_center':
-			cost_center_doc = frappe.get_doc("Cost Center",d.name)
-			if not cost_center_doc.enable_distributed_cost_center:
-				DCC_allocation = frappe.db.sql("""SELECT parent, sum(percentage_allocation) as percentage_allocation
-					FROM `tabDistributed Cost Center`
-					WHERE cost_center IN %(cost_center)s
-					AND parent NOT IN %(cost_center)s
-					GROUP BY parent""",{'cost_center': [d.name]})
-				if DCC_allocation:
-					for account in new_accounts:
-						if account['name'] == DCC_allocation[0][0]:
-							for value in value_fields:
-								d[value] += account[value]*(DCC_allocation[0][1]/100)
 
 		for key in value_fields:
 			row[key] = flt(d.get(key, 0.0), 3)
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.py b/erpnext/accounts/report/tax_detail/test_tax_detail.py
index bf668ab..621de82 100644
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.py
+++ b/erpnext/accounts/report/tax_detail/test_tax_detail.py
@@ -61,7 +61,7 @@
 					# Create GL Entries:
 					db_doc.submit()
 				else:
-					db_doc.insert()
+					db_doc.insert(ignore_if_duplicate=True)
 			except frappe.exceptions.DuplicateEntryError:
 				pass
 
diff --git a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
index caee1a1..e6cbff5 100644
--- a/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
+++ b/erpnext/accounts/report/tds_payable_monthly/tds_payable_monthly.py
@@ -23,7 +23,7 @@
 def get_result(filters, tds_docs, tds_accounts, tax_category_map):
 	supplier_map = get_supplier_pan_map()
 	tax_rate_map = get_tax_rate_map(filters)
-	gle_map = get_gle_map(filters, tds_docs)
+	gle_map = get_gle_map(tds_docs)
 
 	out = []
 	for name, details in gle_map.items():
@@ -43,7 +43,7 @@
 			if entry.account in tds_accounts:
 				tds_deducted += (entry.credit - entry.debit)
 
-			total_amount_credited += (entry.credit - entry.debit)
+			total_amount_credited += entry.credit
 
 		if tds_deducted:
 			row = {
@@ -78,7 +78,7 @@
 
 	return supplier_map
 
-def get_gle_map(filters, documents):
+def get_gle_map(documents):
 	# create gle_map of the form
 	# {"purchase_invoice": list of dict of all gle created for this invoice}
 	gle_map = {}
@@ -86,7 +86,7 @@
 	gle = frappe.db.get_all('GL Entry',
 		{
 			"voucher_no": ["in", documents],
-			"credit": (">", 0)
+			"is_cancelled": 0
 		},
 		["credit", "debit", "account", "voucher_no", "posting_date", "voucher_type", "against", "party"],
 	)
@@ -184,21 +184,28 @@
 	payment_entries = []
 	journal_entries = []
 	tax_category_map = {}
+	or_filters = {}
+	bank_accounts = frappe.get_all('Account', {'is_group': 0, 'account_type': 'Bank'}, pluck="name")
 
 	tds_accounts = frappe.get_all("Tax Withholding Account", {'company': filters.get('company')},
 		pluck="account")
 
 	query_filters = {
-		"credit": ('>', 0),
 		"account": ("in", tds_accounts),
 		"posting_date": ("between", [filters.get("from_date"), filters.get("to_date")]),
-		"is_cancelled": 0
+		"is_cancelled": 0,
+		"against": ("not in", bank_accounts)
 	}
 
-	if filters.get('supplier'):
-		query_filters.update({'against': filters.get('supplier')})
+	if filters.get("supplier"):
+		del query_filters["account"]
+		del query_filters["against"]
+		or_filters = {
+			"against": filters.get('supplier'),
+			"party": filters.get('supplier')
+		}
 
-	tds_docs = frappe.get_all("GL Entry", query_filters, ["voucher_no", "voucher_type", "against", "party"])
+	tds_docs = frappe.get_all("GL Entry", filters=query_filters, or_filters=or_filters, fields=["voucher_no", "voucher_type", "against", "party"])
 
 	for d in tds_docs:
 		if d.voucher_type == "Purchase Invoice":
diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py
index 78c109a..4ed966d 100644
--- a/erpnext/accounts/test/test_reports.py
+++ b/erpnext/accounts/test/test_reports.py
@@ -39,10 +39,11 @@
 	def test_execute_all_accounts_reports(self):
 		"""Test that all script report in stock modules are executable with supported filters"""
 		for report, filter in REPORT_FILTER_TEST_CASES:
-			execute_script_report(
-				report_name=report,
-				module="Accounts",
-				filters=filter,
-				default_filters=DEFAULT_FILTERS,
-				optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
-			)
+			with self.subTest(report=report):
+				execute_script_report(
+					report_name=report,
+					module="Accounts",
+					filters=filter,
+					default_filters=DEFAULT_FILTERS,
+					optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
+				)
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 39e84e3..b17b90b 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -847,7 +847,7 @@
 			"payment_account": bank_account.name,
 			"currency": bank_account.account_currency,
 			"payment_channel": payment_channel
-		}).insert(ignore_permissions=True)
+		}).insert(ignore_permissions=True, ignore_if_duplicate=True)
 
 	except frappe.DuplicateEntryError:
 		# already exists, due to a reinstall?
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index 203ea20..a456c7f 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -1024,6 +1024,17 @@
    "type": "Link"
   },
   {
+    "dependencies": "Cost Center",
+    "hidden": 0,
+    "is_query_report": 0,
+    "label": "Cost Center Allocation",
+    "link_count": 0,
+    "link_to": "Cost Center Allocation",
+    "link_type": "DocType",
+    "onboard": 0,
+    "type": "Link"
+   },
+  {
    "dependencies": "Cost Center",
    "hidden": 0,
    "is_query_report": 1,
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 153f5c5..f414930 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -108,6 +108,10 @@
 				frm.trigger("create_asset_repair");
 			}, __("Manage"));
 
+			frm.add_custom_button(__("Split Asset"), function() {
+				frm.trigger("split_asset");
+			}, __("Manage"));
+
 			if (frm.doc.status != 'Fully Depreciated') {
 				frm.add_custom_button(__("Adjust Asset Value"), function() {
 					frm.trigger("create_asset_value_adjustment");
@@ -322,6 +326,43 @@
 		});
 	},
 
+	split_asset: function(frm) {
+		const title = __('Split Asset');
+
+		const fields = [
+			{
+				fieldname: 'split_qty',
+				fieldtype: 'Int',
+				label: __('Split Qty'),
+				reqd: 1
+			}
+		];
+
+		let dialog = new frappe.ui.Dialog({
+			title: title,
+			fields: fields
+		});
+
+		dialog.set_primary_action(__('Split'), function() {
+			const dialog_data = dialog.get_values();
+			frappe.call({
+				args: {
+					"asset_name": frm.doc.name,
+					"split_qty": cint(dialog_data.split_qty)
+				},
+				method: "erpnext.assets.doctype.asset.asset.split_asset",
+				callback: function(r) {
+					let doclist = frappe.model.sync(r.message);
+					frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
+				}
+			});
+
+			dialog.hide();
+		});
+
+		dialog.show();
+	},
+
 	create_asset_value_adjustment: function(frm) {
 		frappe.call({
 			args: {
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index de06075..6e6bbf1 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "naming_series:",
- "creation": "2016-03-01 17:01:27.920130",
+ "creation": "2022-01-18 02:26:55.975005",
  "doctype": "DocType",
  "document_type": "Document",
  "engine": "InnoDB",
@@ -23,6 +23,7 @@
   "asset_name",
   "asset_category",
   "location",
+  "split_from",
   "custodian",
   "department",
   "disposal_date",
@@ -35,6 +36,7 @@
   "available_for_use_date",
   "column_break_23",
   "gross_purchase_amount",
+  "asset_quantity",
   "purchase_date",
   "section_break_23",
   "calculate_depreciation",
@@ -141,6 +143,7 @@
   },
   {
    "allow_on_submit": 1,
+   "fetch_from": "item_code.image",
    "fieldname": "image",
    "fieldtype": "Attach Image",
    "hidden": 1,
@@ -480,6 +483,19 @@
    "fieldname": "section_break_36",
    "fieldtype": "Section Break",
    "label": "Finance Books"
+  },
+  {
+   "fieldname": "split_from",
+   "fieldtype": "Link",
+   "label": "Split From",
+   "options": "Asset",
+   "read_only": 1
+  },
+  {
+   "fieldname": "asset_quantity",
+   "fieldtype": "Int",
+   "label": "Asset Quantity",
+   "read_only_depends_on": "eval:!doc.is_existing_asset"
   }
  ],
  "idx": 72,
@@ -502,10 +518,11 @@
    "link_fieldname": "asset"
   }
  ],
- "modified": "2021-06-24 14:58:51.097908",
+ "modified": "2022-01-30 20:19:24.680027",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -542,6 +559,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "asset_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index ee3ec8e..ea473fa 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -36,8 +36,10 @@
 		self.validate_asset_values()
 		self.validate_asset_and_reference()
 		self.validate_item()
+		self.validate_cost_center()
 		self.set_missing_values()
-		self.prepare_depreciation_data()
+		if not self.split_from:
+			self.prepare_depreciation_data()
 		self.validate_gross_and_purchase_amount()
 		if self.get("schedules"):
 			self.validate_expected_value_after_useful_life()
@@ -95,6 +97,19 @@
 		elif item.is_stock_item:
 			frappe.throw(_("Item {0} must be a non-stock item").format(self.item_code))
 
+	def validate_cost_center(self):
+		if not self.cost_center: return
+
+		cost_center_company = frappe.db.get_value('Cost Center', self.cost_center, 'company')
+		if cost_center_company != self.company:
+			frappe.throw(
+				_("Selected Cost Center {} doesn't belongs to {}").format(
+					frappe.bold(self.cost_center),
+					frappe.bold(self.company)
+				),
+				title=_("Invalid Cost Center")
+			)
+
 	def validate_in_use_date(self):
 		if not self.available_for_use_date:
 			frappe.throw(_("Available for use date is required"))
@@ -188,142 +203,143 @@
 		start = self.clear_depreciation_schedule()
 
 		for finance_book in self.get('finance_books'):
-			self.validate_asset_finance_books(finance_book)
+			self._make_depreciation_schedule(finance_book, start, date_of_sale)
 
-			# value_after_depreciation - current Asset value
-			if self.docstatus == 1 and finance_book.value_after_depreciation:
-				value_after_depreciation = flt(finance_book.value_after_depreciation)
-			else:
-				value_after_depreciation = (flt(self.gross_purchase_amount) -
-					flt(self.opening_accumulated_depreciation))
+	def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
+		self.validate_asset_finance_books(finance_book)
 
-			finance_book.value_after_depreciation = value_after_depreciation
+		value_after_depreciation = self._get_value_after_depreciation(finance_book)
+		finance_book.value_after_depreciation = value_after_depreciation
 
-			number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
-				cint(self.number_of_depreciations_booked)
+		number_of_pending_depreciations = cint(finance_book.total_number_of_depreciations) - \
+			cint(self.number_of_depreciations_booked)
 
-			has_pro_rata = self.check_is_pro_rata(finance_book)
+		has_pro_rata = self.check_is_pro_rata(finance_book)
+		if has_pro_rata:
+			number_of_pending_depreciations += 1
 
-			if has_pro_rata:
-				number_of_pending_depreciations += 1
+		skip_row = False
 
-			skip_row = False
+		for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
+			# If depreciation is already completed (for double declining balance)
+			if skip_row: continue
 
-			for n in range(start[finance_book.idx-1], number_of_pending_depreciations):
-				# If depreciation is already completed (for double declining balance)
-				if skip_row: continue
+			depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
 
-				depreciation_amount = get_depreciation_amount(self, value_after_depreciation, finance_book)
+			if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
+				schedule_date = add_months(finance_book.depreciation_start_date,
+					n * cint(finance_book.frequency_of_depreciation))
 
-				if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
-					schedule_date = add_months(finance_book.depreciation_start_date,
-						n * cint(finance_book.frequency_of_depreciation))
+				# schedule date will be a year later from start date
+				# so monthly schedule date is calculated by removing 11 months from it
+				monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
 
-					# schedule date will be a year later from start date
-					# so monthly schedule date is calculated by removing 11 months from it
-					monthly_schedule_date = add_months(schedule_date, - finance_book.frequency_of_depreciation + 1)
-
-				# if asset is being sold
-				if date_of_sale:
-					from_date = self.get_from_date(finance_book.finance_book)
-					depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
-						from_date, date_of_sale)
-
-					if depreciation_amount > 0:
-						self.append("schedules", {
-							"schedule_date": date_of_sale,
-							"depreciation_amount": depreciation_amount,
-							"depreciation_method": finance_book.depreciation_method,
-							"finance_book": finance_book.finance_book,
-							"finance_book_id": finance_book.idx
-						})
-
-					break
-
-				# For first row
-				if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
-					depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
-						self.available_for_use_date, finance_book.depreciation_start_date)
-
-					# For first depr schedule date will be the start date
-					# so monthly schedule date is calculated by removing month difference between use date and start date
-					monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
-
-				# For last row
-				elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
-					if not self.flags.increase_in_asset_life:
-						# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
-						self.to_date = add_months(self.available_for_use_date,
-							(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
-
-					depreciation_amount_without_pro_rata = depreciation_amount
-
-					depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
-						depreciation_amount, schedule_date, self.to_date)
-
-					depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
-						depreciation_amount, finance_book.finance_book)
-
-					monthly_schedule_date = add_months(schedule_date, 1)
-					schedule_date = add_days(schedule_date, days)
-					last_schedule_date = schedule_date
-
-				if not depreciation_amount: continue
-				value_after_depreciation -= flt(depreciation_amount,
-					self.precision("gross_purchase_amount"))
-
-				# Adjust depreciation amount in the last period based on the expected value after useful life
-				if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
-					and value_after_depreciation != finance_book.expected_value_after_useful_life)
-					or value_after_depreciation < finance_book.expected_value_after_useful_life):
-					depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
-					skip_row = True
+			# if asset is being sold
+			if date_of_sale:
+				from_date = self.get_from_date(finance_book.finance_book)
+				depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
+					from_date, date_of_sale)
 
 				if depreciation_amount > 0:
-					# With monthly depreciation, each depreciation is divided by months remaining until next date
-					if self.allow_monthly_depreciation:
-						# month range is 1 to 12
-						# In pro rata case, for first and last depreciation, month range would be different
-						month_range = months \
-							if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
-							else finance_book.frequency_of_depreciation
+					self._add_depreciation_row(date_of_sale, depreciation_amount, finance_book.depreciation_method,
+						finance_book.finance_book, finance_book.idx)
 
-						for r in range(month_range):
-							if (has_pro_rata and n == 0):
-								# For first entry of monthly depr
-								if r == 0:
-									days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
-									per_day_amt = depreciation_amount / days
-									depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
-									depreciation_amount -= depreciation_amount_for_current_month
-									date = monthly_schedule_date
-									amount = depreciation_amount_for_current_month
-								else:
-									date = add_months(monthly_schedule_date, r)
-									amount = depreciation_amount / (month_range - 1)
-							elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
-								# For last entry of monthly depr
-								date = last_schedule_date
-								amount = depreciation_amount / month_range
+				break
+
+			# For first row
+			if has_pro_rata and not self.opening_accumulated_depreciation and n==0:
+				from_date = add_days(self.available_for_use_date, -1) # needed to calc depr amount for available_for_use_date too
+				depreciation_amount, days, months = self.get_pro_rata_amt(finance_book, depreciation_amount,
+					from_date, finance_book.depreciation_start_date)
+
+				# For first depr schedule date will be the start date
+				# so monthly schedule date is calculated by removing month difference between use date and start date
+				monthly_schedule_date = add_months(finance_book.depreciation_start_date, - months + 1)
+
+			# For last row
+			elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
+				if not self.flags.increase_in_asset_life:
+					# In case of increase_in_asset_life, the self.to_date is already set on asset_repair submission
+					self.to_date = add_months(self.available_for_use_date,
+						(n + self.number_of_depreciations_booked) * cint(finance_book.frequency_of_depreciation))
+
+				depreciation_amount_without_pro_rata = depreciation_amount
+
+				depreciation_amount, days, months = self.get_pro_rata_amt(finance_book,
+					depreciation_amount, schedule_date, self.to_date)
+
+				depreciation_amount = self.get_adjusted_depreciation_amount(depreciation_amount_without_pro_rata,
+					depreciation_amount, finance_book.finance_book)
+
+				monthly_schedule_date = add_months(schedule_date, 1)
+				schedule_date = add_days(schedule_date, days)
+				last_schedule_date = schedule_date
+
+			if not depreciation_amount: continue
+			value_after_depreciation -= flt(depreciation_amount,
+				self.precision("gross_purchase_amount"))
+
+			# Adjust depreciation amount in the last period based on the expected value after useful life
+			if finance_book.expected_value_after_useful_life and ((n == cint(number_of_pending_depreciations) - 1
+				and value_after_depreciation != finance_book.expected_value_after_useful_life)
+				or value_after_depreciation < finance_book.expected_value_after_useful_life):
+				depreciation_amount += (value_after_depreciation - finance_book.expected_value_after_useful_life)
+				skip_row = True
+
+			if depreciation_amount > 0:
+				# With monthly depreciation, each depreciation is divided by months remaining until next date
+				if self.allow_monthly_depreciation:
+					# month range is 1 to 12
+					# In pro rata case, for first and last depreciation, month range would be different
+					month_range = months \
+						if (has_pro_rata and n==0) or (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) \
+						else finance_book.frequency_of_depreciation
+
+					for r in range(month_range):
+						if (has_pro_rata and n == 0):
+							# For first entry of monthly depr
+							if r == 0:
+								days_until_first_depr = date_diff(monthly_schedule_date, self.available_for_use_date)
+								per_day_amt = depreciation_amount / days
+								depreciation_amount_for_current_month = per_day_amt * days_until_first_depr
+								depreciation_amount -= depreciation_amount_for_current_month
+								date = monthly_schedule_date
+								amount = depreciation_amount_for_current_month
 							else:
 								date = add_months(monthly_schedule_date, r)
-								amount = depreciation_amount / month_range
+								amount = depreciation_amount / (month_range - 1)
+						elif (has_pro_rata and n == cint(number_of_pending_depreciations) - 1) and r == cint(month_range) - 1:
+							# For last entry of monthly depr
+							date = last_schedule_date
+							amount = depreciation_amount / month_range
+						else:
+							date = add_months(monthly_schedule_date, r)
+							amount = depreciation_amount / month_range
 
-							self.append("schedules", {
-								"schedule_date": date,
-								"depreciation_amount": amount,
-								"depreciation_method": finance_book.depreciation_method,
-								"finance_book": finance_book.finance_book,
-								"finance_book_id": finance_book.idx
-							})
-					else:
-						self.append("schedules", {
-							"schedule_date": schedule_date,
-							"depreciation_amount": depreciation_amount,
-							"depreciation_method": finance_book.depreciation_method,
-							"finance_book": finance_book.finance_book,
-							"finance_book_id": finance_book.idx
-						})
+						self._add_depreciation_row(date, amount, finance_book.depreciation_method,
+							finance_book.finance_book, finance_book.idx)
+				else:
+					self._add_depreciation_row(schedule_date, depreciation_amount, finance_book.depreciation_method,
+						finance_book.finance_book, finance_book.idx)
+
+	def _add_depreciation_row(self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id):
+		self.append("schedules", {
+			"schedule_date": schedule_date,
+			"depreciation_amount": depreciation_amount,
+			"depreciation_method": depreciation_method,
+			"finance_book": finance_book,
+			"finance_book_id": finance_book_id
+		})
+
+	def _get_value_after_depreciation(self, finance_book):
+		# value_after_depreciation - current Asset value
+		if self.docstatus == 1 and finance_book.value_after_depreciation:
+			value_after_depreciation = flt(finance_book.value_after_depreciation)
+		else:
+			value_after_depreciation = (flt(self.gross_purchase_amount) -
+				flt(self.opening_accumulated_depreciation))
+
+		return value_after_depreciation
 
 	# depreciation schedules need to be cleared before modification due to increase in asset life/asset sales
 	# JE: Journal Entry, FB: Finance Book
@@ -333,7 +349,6 @@
 		depr_schedule = []
 
 		for schedule in self.get('schedules'):
-
 			# to update start when there are JEs linked with all the schedule rows corresponding to an FB
 			if len(start) == (int(schedule.finance_book_id) - 2):
 				start.append(num_of_depreciations_completed)
@@ -374,7 +389,9 @@
 
 		if from_date:
 			return from_date
-		return self.available_for_use_date
+
+		# since depr for available_for_use_date is not yet booked
+		return add_days(self.available_for_use_date, -1)
 
 	# if it returns True, depreciation_amount will not be equal for the first and last rows
 	def check_is_pro_rata(self, row):
@@ -400,11 +417,12 @@
 	def validate_asset_finance_books(self, row):
 		if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
 			frappe.throw(_("Row {0}: Expected Value After Useful Life must be less than Gross Purchase Amount")
-				.format(row.idx))
+				.format(row.idx), title=_("Invalid Schedule"))
 
 		if not row.depreciation_start_date:
 			if not self.available_for_use_date:
-				frappe.throw(_("Row {0}: Depreciation Start Date is required").format(row.idx))
+				frappe.throw(_("Row {0}: Depreciation Start Date is required")
+					.format(row.idx), title=_("Invalid Schedule"))
 			row.depreciation_start_date = get_last_day(self.available_for_use_date)
 
 		if not self.is_existing_asset:
@@ -422,8 +440,9 @@
 			else:
 				self.number_of_depreciations_booked = 0
 
-			if cint(self.number_of_depreciations_booked) > cint(row.total_number_of_depreciations):
-				frappe.throw(_("Number of Depreciations Booked cannot be greater than Total Number of Depreciations"))
+			if flt(row.total_number_of_depreciations) <= cint(self.number_of_depreciations_booked):
+				frappe.throw(_("Row {0}: Total Number of Depreciations cannot be less than or equal to Number of Depreciations Booked")
+					.format(row.idx), title=_("Invalid Schedule"))
 
 		if row.depreciation_start_date and getdate(row.depreciation_start_date) < getdate(self.purchase_date):
 			frappe.throw(_("Depreciation Row {0}: Next Depreciation Date cannot be before Purchase Date")
@@ -907,3 +926,113 @@
 		depreciation_amount = flt(depreciable_value * (flt(row.rate_of_depreciation) / 100))
 
 	return depreciation_amount
+
+@frappe.whitelist()
+def split_asset(asset_name, split_qty):
+	asset = frappe.get_doc("Asset", asset_name)
+	split_qty = cint(split_qty)
+
+	if split_qty >= asset.asset_quantity:
+		frappe.throw(_("Split qty cannot be grater than or equal to asset qty"))
+
+	remaining_qty = asset.asset_quantity - split_qty
+
+	new_asset = create_new_asset_after_split(asset, split_qty)
+	update_existing_asset(asset, remaining_qty)
+
+	return new_asset
+
+def update_existing_asset(asset, remaining_qty):
+	remaining_gross_purchase_amount = flt((asset.gross_purchase_amount * remaining_qty) / asset.asset_quantity)
+	opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * remaining_qty) / asset.asset_quantity)
+
+	frappe.db.set_value("Asset", asset.name, {
+		'opening_accumulated_depreciation': opening_accumulated_depreciation,
+		'gross_purchase_amount': remaining_gross_purchase_amount,
+		'asset_quantity': remaining_qty
+	})
+
+	for finance_book in asset.get('finance_books'):
+		value_after_depreciation = flt((finance_book.value_after_depreciation * remaining_qty)/asset.asset_quantity)
+		expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * remaining_qty)/asset.asset_quantity)
+		frappe.db.set_value('Asset Finance Book', finance_book.name, 'value_after_depreciation', value_after_depreciation)
+		frappe.db.set_value('Asset Finance Book', finance_book.name, 'expected_value_after_useful_life', expected_value_after_useful_life)
+
+	accumulated_depreciation = 0
+
+	for term in asset.get('schedules'):
+		depreciation_amount = flt((term.depreciation_amount * remaining_qty)/asset.asset_quantity)
+		frappe.db.set_value('Depreciation Schedule', term.name, 'depreciation_amount', depreciation_amount)
+		accumulated_depreciation += depreciation_amount
+		frappe.db.set_value('Depreciation Schedule', term.name, 'accumulated_depreciation_amount', accumulated_depreciation)
+
+def create_new_asset_after_split(asset, split_qty):
+	new_asset = frappe.copy_doc(asset)
+	new_gross_purchase_amount = flt((asset.gross_purchase_amount * split_qty) / asset.asset_quantity)
+	opening_accumulated_depreciation = flt((asset.opening_accumulated_depreciation * split_qty) / asset.asset_quantity)
+
+	new_asset.gross_purchase_amount = new_gross_purchase_amount
+	new_asset.opening_accumulated_depreciation = opening_accumulated_depreciation
+	new_asset.asset_quantity = split_qty
+	new_asset.split_from = asset.name
+	accumulated_depreciation = 0
+
+	for finance_book in new_asset.get('finance_books'):
+		finance_book.value_after_depreciation = flt((finance_book.value_after_depreciation * split_qty)/asset.asset_quantity)
+		finance_book.expected_value_after_useful_life = flt((finance_book.expected_value_after_useful_life * split_qty)/asset.asset_quantity)
+
+	for term in new_asset.get('schedules'):
+		depreciation_amount = flt((term.depreciation_amount * split_qty)/asset.asset_quantity)
+		term.depreciation_amount = depreciation_amount
+		accumulated_depreciation += depreciation_amount
+		term.accumulated_depreciation_amount = accumulated_depreciation
+
+	new_asset.submit()
+	new_asset.set_status()
+
+	for term in new_asset.get('schedules'):
+		# Update references in JV
+		if term.journal_entry:
+			add_reference_in_jv_on_split(term.journal_entry, new_asset.name, asset.name, term.depreciation_amount)
+
+	return new_asset
+
+def add_reference_in_jv_on_split(entry_name, new_asset_name, old_asset_name, depreciation_amount):
+	journal_entry = frappe.get_doc('Journal Entry', entry_name)
+	entries_to_add = []
+	idx = len(journal_entry.get('accounts')) + 1
+
+	for account in journal_entry.get('accounts'):
+		if account.reference_name == old_asset_name:
+			entries_to_add.append(frappe.copy_doc(account).as_dict())
+			if account.credit:
+				account.credit = account.credit - depreciation_amount
+				account.credit_in_account_currency = account.credit_in_account_currency - \
+					account.exchange_rate * depreciation_amount
+			elif account.debit:
+				account.debit = account.debit - depreciation_amount
+				account.debit_in_account_currency = account.debit_in_account_currency - \
+					account.exchange_rate * depreciation_amount
+
+	for entry in entries_to_add:
+		entry.reference_name = new_asset_name
+		if entry.credit:
+			entry.credit = depreciation_amount
+			entry.credit_in_account_currency = entry.exchange_rate * depreciation_amount
+		elif entry.debit:
+			entry.debit = depreciation_amount
+			entry.debit_in_account_currency = entry.exchange_rate * depreciation_amount
+
+		entry.idx = idx
+		idx += 1
+
+		journal_entry.append('accounts', entry)
+
+	journal_entry.flags.ignore_validate_update_after_submit = True
+	journal_entry.save()
+
+	# Repost GL Entries
+	journal_entry.docstatus = 2
+	journal_entry.make_gl_entries(1)
+	journal_entry.docstatus = 1
+	journal_entry.make_gl_entries()
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 44c4ce5..ffd1065 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -7,7 +7,7 @@
 from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
 
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
-from erpnext.assets.doctype.asset.asset import make_sales_invoice
+from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
 from erpnext.assets.doctype.asset.depreciation import (
 	post_depreciation_entries,
 	restore_asset,
@@ -134,6 +134,29 @@
 		pr.cancel()
 		self.assertEqual(asset.docstatus, 2)
 
+	def test_purchase_of_grouped_asset(self):
+		create_fixed_asset_item("Rack", is_grouped_asset=1)
+		pr = make_purchase_receipt(item_code="Rack", qty=3, 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)
+		self.assertEqual(asset.asset_quantity, 3)
+		asset.calculate_depreciation = 1
+
+		month_end_date = get_last_day(nowdate())
+		purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
+
+		asset.available_for_use_date = purchase_date
+		asset.purchase_date = purchase_date
+		asset.append("finance_books", {
+			"expected_value_after_useful_life": 10000,
+			"depreciation_method": "Straight Line",
+			"total_number_of_depreciations": 3,
+			"frequency_of_depreciation": 10,
+			"depreciation_start_date": month_end_date
+		})
+		asset.submit()
+
 	def test_is_fixed_asset_set(self):
 		asset = create_asset(is_existing_asset = 1)
 		doc = frappe.new_doc('Purchase Invoice')
@@ -207,9 +230,9 @@
 		self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
 
 		expected_gle = (
-			("_Test Accumulated Depreciations - _TC", 20392.16, 0.0),
+			("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
 			("_Test Fixed Asset - _TC", 0.0, 100000.0),
-			("_Test Gain/Loss on Asset Disposal - _TC", 54607.84, 0.0),
+			("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
 			("Debtors - _TC", 25000.0, 0.0)
 		)
 
@@ -222,6 +245,57 @@
 		si.cancel()
 		self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
 
+	def test_asset_splitting(self):
+		asset = create_asset(
+			calculate_depreciation = 1,
+			asset_quantity=10,
+			available_for_use_date = '2020-01-01',
+			purchase_date = '2020-01-01',
+			expected_value_after_useful_life = 0,
+			total_number_of_depreciations = 6,
+			number_of_depreciations_booked = 1,
+			frequency_of_depreciation = 10,
+			depreciation_start_date = '2021-01-01',
+			opening_accumulated_depreciation=20000,
+			gross_purchase_amount=120000,
+			submit = 1
+		)
+
+		post_depreciation_entries(date="2021-01-01")
+
+		self.assertEqual(asset.asset_quantity, 10)
+		self.assertEqual(asset.gross_purchase_amount, 120000)
+		self.assertEqual(asset.opening_accumulated_depreciation, 20000)
+
+		new_asset = split_asset(asset.name, 2)
+		asset.load_from_db()
+
+		self.assertEqual(new_asset.asset_quantity, 2)
+		self.assertEqual(new_asset.gross_purchase_amount, 24000)
+		self.assertEqual(new_asset.opening_accumulated_depreciation, 4000)
+		self.assertEqual(new_asset.split_from, asset.name)
+		self.assertEqual(new_asset.schedules[0].depreciation_amount, 4000)
+		self.assertEqual(new_asset.schedules[1].depreciation_amount, 4000)
+
+		self.assertEqual(asset.asset_quantity, 8)
+		self.assertEqual(asset.gross_purchase_amount, 96000)
+		self.assertEqual(asset.opening_accumulated_depreciation, 16000)
+		self.assertEqual(asset.schedules[0].depreciation_amount, 16000)
+		self.assertEqual(asset.schedules[1].depreciation_amount, 16000)
+
+		journal_entry = asset.schedules[0].journal_entry
+
+		jv = frappe.get_doc('Journal Entry', journal_entry)
+		self.assertEqual(jv.accounts[0].credit_in_account_currency, 16000)
+		self.assertEqual(jv.accounts[1].debit_in_account_currency, 16000)
+		self.assertEqual(jv.accounts[2].credit_in_account_currency, 4000)
+		self.assertEqual(jv.accounts[3].debit_in_account_currency, 4000)
+
+		self.assertEqual(jv.accounts[0].reference_name, asset.name)
+		self.assertEqual(jv.accounts[1].reference_name, asset.name)
+		self.assertEqual(jv.accounts[2].reference_name, new_asset.name)
+		self.assertEqual(jv.accounts[3].reference_name, new_asset.name)
+
 	def test_expense_head(self):
 		pr = make_purchase_receipt(item_code="Macbook Pro",
 			qty=2, rate=200000.0, location="Test Location")
@@ -491,10 +565,10 @@
 		)
 
 		expected_schedules = [
-			["2030-12-31", 27534.25, 27534.25],
-			["2031-12-31", 30000.0, 57534.25],
-			["2032-12-31", 30000.0, 87534.25],
-			["2033-01-30", 2465.75, 90000.0]
+			['2030-12-31', 27616.44, 27616.44],
+			['2031-12-31', 30000.0, 57616.44],
+			['2032-12-31', 30000.0, 87616.44],
+			['2033-01-30', 2383.56, 90000.0]
 		]
 
 		schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -544,10 +618,10 @@
 		self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
 
 		expected_schedules = [
-			["2030-12-31", 28493.15, 28493.15],
-			["2031-12-31", 35753.43, 64246.58],
-			["2032-12-31", 17876.71, 82123.29],
-			["2033-06-06", 5376.71, 87500.0]
+			['2030-12-31', 28630.14, 28630.14],
+			['2031-12-31', 35684.93, 64315.07],
+			['2032-12-31', 17842.47, 82157.54],
+			['2033-06-06', 5342.46, 87500.0]
 		]
 
 		schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -580,10 +654,10 @@
 		self.assertEqual(asset.finance_books[0].rate_of_depreciation, 50.0)
 
 		expected_schedules = [
-			["2030-12-31", 11780.82, 11780.82],
-			["2031-12-31", 44109.59, 55890.41],
-			["2032-12-31", 22054.8, 77945.21],
-			["2033-07-12", 9554.79, 87500.0]
+			["2030-12-31", 11849.32, 11849.32],
+			["2031-12-31", 44075.34, 55924.66],
+			["2032-12-31", 22037.67, 77962.33],
+			["2033-07-12", 9537.67, 87500.0]
 		]
 
 		schedules = [[cstr(d.schedule_date), flt(d.depreciation_amount, 2), flt(d.accumulated_depreciation_amount, 2)]
@@ -621,7 +695,7 @@
 		asset = create_asset(
 			item_code = "Macbook Pro",
 			calculate_depreciation = 1,
-			available_for_use_date = getdate("2019-12-31"),
+			available_for_use_date = getdate("2020-01-01"),
 			total_number_of_depreciations = 3,
 			expected_value_after_useful_life = 10000,
 			depreciation_start_date = getdate("2020-07-01"),
@@ -632,7 +706,7 @@
 			["2020-07-01", 15000, 15000],
 			["2021-07-01", 30000, 45000],
 			["2022-07-01", 30000, 75000],
-			["2022-12-31", 15000, 90000]
+			["2023-01-01", 15000, 90000]
 		]
 
 		for i, schedule in enumerate(asset.schedules):
@@ -799,8 +873,9 @@
 		self.assertRaises(frappe.ValidationError, asset.save)
 
 	def test_number_of_depreciations(self):
-		"""Tests if an error is raised when number_of_depreciations_booked > total_number_of_depreciations."""
+		"""Tests if an error is raised when number_of_depreciations_booked >= total_number_of_depreciations."""
 
+		# number_of_depreciations_booked > total_number_of_depreciations
 		asset = create_asset(
 			item_code = "Macbook Pro",
 			calculate_depreciation = 1,
@@ -815,6 +890,21 @@
 
 		self.assertRaises(frappe.ValidationError, asset.save)
 
+		# number_of_depreciations_booked = total_number_of_depreciations
+		asset_2 = create_asset(
+			item_code = "Macbook Pro",
+			calculate_depreciation = 1,
+			available_for_use_date = "2019-12-31",
+			total_number_of_depreciations = 5,
+			expected_value_after_useful_life = 10000,
+			depreciation_start_date = "2020-07-01",
+			opening_accumulated_depreciation = 10000,
+			number_of_depreciations_booked = 5,
+			do_not_save = 1
+		)
+
+		self.assertRaises(frappe.ValidationError, asset_2.save)
+
 	def test_depreciation_start_date_is_before_purchase_date(self):
 		asset = create_asset(
 			item_code = "Macbook Pro",
@@ -1109,6 +1199,7 @@
 
 		self.assertEqual(gle, expected_gle)
 		self.assertEqual(asset.get("value_after_depreciation"), 0)
+
 	def test_expected_value_change(self):
 		"""
 			tests if changing `expected_value_after_useful_life`
@@ -1130,6 +1221,15 @@
 		asset.reload()
 		self.assertEquals(asset.finance_books[0].value_after_depreciation, 98000.0)
 
+	def test_asset_cost_center(self):
+		asset = create_asset(is_existing_asset = 1, do_not_save=1)
+		asset.cost_center = "Main - WP"
+
+		self.assertRaises(frappe.ValidationError, asset.submit)
+
+		asset.cost_center = "Main - _TC"
+		asset.submit()
+
 def create_asset_data():
 	if not frappe.db.exists("Asset Category", "Computers"):
 		create_asset_category()
@@ -1164,7 +1264,8 @@
 		"available_for_use_date": args.available_for_use_date or "2020-06-06",
 		"location": args.location or "Test Location",
 		"asset_owner": args.asset_owner or "Company",
-		"is_existing_asset": args.is_existing_asset or 1
+		"is_existing_asset": args.is_existing_asset or 1,
+		"asset_quantity": args.get("asset_quantity") or 1
 	})
 
 	if asset.calculate_depreciation:
@@ -1179,7 +1280,7 @@
 
 	if not args.do_not_save:
 		try:
-			asset.save()
+			asset.insert(ignore_if_duplicate=True)
 		except frappe.DuplicateEntryError:
 			pass
 
@@ -1202,13 +1303,13 @@
 	})
 	asset_category.insert()
 
-def create_fixed_asset_item():
+def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
 	meta = frappe.get_meta('Asset')
 	naming_series = meta.get_field("naming_series").options.splitlines()[0] or 'ACC-ASS-.YYYY.-'
 	try:
-		frappe.get_doc({
+		item = frappe.get_doc({
 			"doctype": "Item",
-			"item_code": "Macbook Pro",
+			"item_code": item_code or "Macbook Pro",
 			"item_name": "Macbook Pro",
 			"description": "Macbook Pro Retina Display",
 			"asset_category": "Computers",
@@ -1216,11 +1317,14 @@
 			"stock_uom": "Nos",
 			"is_stock_item": 0,
 			"is_fixed_asset": 1,
-			"auto_create_assets": 1,
+			"auto_create_assets": auto_create_assets,
+			"is_grouped_asset": is_grouped_asset,
 			"asset_naming_series": naming_series
-		}).insert()
+		})
+		item.insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
+	return item
 
 def set_depreciation_settings_in_company():
 	company = frappe.get_doc("Company", "_Test Company")
diff --git a/erpnext/assets/doctype/asset_category/test_asset_category.py b/erpnext/assets/doctype/asset_category/test_asset_category.py
index 3d19fa3..2f52248 100644
--- a/erpnext/assets/doctype/asset_category/test_asset_category.py
+++ b/erpnext/assets/doctype/asset_category/test_asset_category.py
@@ -23,7 +23,7 @@
 		})
 
 		try:
-			asset_category.insert()
+			asset_category.insert(ignore_if_duplicate=True)
 		except frappe.DuplicateEntryError:
 			pass
 
diff --git a/erpnext/demo/__init__.py b/erpnext/bulk_transaction/__init__.py
similarity index 100%
copy from erpnext/demo/__init__.py
copy to erpnext/bulk_transaction/__init__.py
diff --git a/erpnext/hotels/doctype/__init__.py b/erpnext/bulk_transaction/doctype/__init__.py
similarity index 100%
copy from erpnext/hotels/doctype/__init__.py
copy to erpnext/bulk_transaction/doctype/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/__init__.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/hotel_room_pricing/__init__.py
rename to erpnext/bulk_transaction/doctype/bulk_transaction_log/__init__.py
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
new file mode 100644
index 0000000..a739cc3
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.js
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Bulk Transaction Log', {
+
+	before_load: function(frm) {
+		query(frm);
+	},
+
+	refresh: function(frm) {
+		frm.disable_save();
+		frm.add_custom_button(__('Retry Failed Transactions'), ()=>{
+			frappe.confirm(__("Retry Failing Transactions ?"), ()=>{
+				query(frm);
+			}
+			);
+		});
+	}
+});
+
+function query(frm) {
+	frappe.call({
+		method: "erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
+		args: {
+			log_date: frm.doc.log_date
+		}
+	}).then((r) => {
+		if (r.message) {
+			frm.remove_custom_button("Retry Failed Transactions");
+		} else {
+			frappe.show_alert(__("Retrying Failed Transactions"), 5);
+		}
+	});
+}
\ No newline at end of file
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json
new file mode 100644
index 0000000..da42cf1
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.json
@@ -0,0 +1,51 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2021-11-30 13:41:16.343827",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "log_date",
+  "logger_data"
+ ],
+ "fields": [
+  {
+   "fieldname": "log_date",
+   "fieldtype": "Date",
+   "label": "Log Date",
+   "read_only": 1
+  },
+  {
+   "fieldname": "logger_data",
+   "fieldtype": "Table",
+   "label": "Logger Data",
+   "options": "Bulk Transaction Log Detail"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2022-02-03 17:23:02.935325",
+ "modified_by": "Administrator",
+ "module": "Bulk Transaction",
+ "name": "Bulk Transaction Log",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
new file mode 100644
index 0000000..de7cde5
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/bulk_transaction_log.py
@@ -0,0 +1,66 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from datetime import date
+
+import frappe
+from frappe.model.document import Document
+
+from erpnext.utilities.bulk_transaction import task, update_logger
+
+
+class BulkTransactionLog(Document):
+	pass
+
+
+@frappe.whitelist()
+def retry_failing_transaction(log_date=None):
+	btp = frappe.qb.DocType("Bulk Transaction Log Detail")
+	data = (
+		frappe.qb.from_(btp)
+		.select(btp.transaction_name, btp.from_doctype, btp.to_doctype)
+		.distinct()
+		.where(btp.retried != 1)
+		.where(btp.transaction_status == "Failed")
+		.where(btp.date == log_date)
+	).run(as_dict=True)
+
+	if data:
+		if not log_date:
+			log_date = str(date.today())
+		if len(data) > 10:
+			frappe.enqueue(job, queue="long", job_name="bulk_retry", data=data, log_date=log_date)
+		else:
+			job(data, log_date)
+	else:
+		return "No Failed Records"
+
+def job(data, log_date):
+	for d in data:
+		failed = []
+		try:
+			frappe.db.savepoint("before_creation_of_record")
+			task(d.transaction_name, d.from_doctype, d.to_doctype)
+		except Exception as e:
+			frappe.db.rollback(save_point="before_creation_of_record")
+			failed.append(e)
+			update_logger(
+				d.transaction_name,
+				e,
+				d.from_doctype,
+				d.to_doctype,
+				status="Failed",
+				log_date=log_date,
+				restarted=1
+			)
+
+		if not failed:
+			update_logger(
+				d.transaction_name,
+				None,
+				d.from_doctype,
+				d.to_doctype,
+				status="Success",
+				log_date=log_date,
+				restarted=1,
+			)
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
new file mode 100644
index 0000000..a78e697
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log/test_bulk_transaction_log.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+from datetime import date
+
+import frappe
+
+from erpnext.utilities.bulk_transaction import transaction_processing
+
+
+class TestBulkTransactionLog(unittest.TestCase):
+
+	def setUp(self):
+		create_company()
+		create_customer()
+		create_item()
+
+	def test_for_single_record(self):
+		so_name = create_so()
+		transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
+		data = frappe.db.get_list("Sales Invoice", filters = {"posting_date": date.today(), "customer": "Bulk Customer"}, fields=["*"])
+		if not data:
+			self.fail("No Sales Invoice Created !")
+
+	def test_entry_in_log(self):
+		so_name = create_so()
+		transaction_processing([{"name": so_name}], "Sales Order", "Sales Invoice")
+		doc = frappe.get_doc("Bulk Transaction Log", str(date.today()))
+		for d in doc.get("logger_data"):
+			if d.transaction_name == so_name:
+				self.assertEqual(d.transaction_name, so_name)
+				self.assertEqual(d.transaction_status, "Success")
+				self.assertEqual(d.from_doctype, "Sales Order")
+				self.assertEqual(d.to_doctype, "Sales Invoice")
+				self.assertEqual(d.retried, 0)
+
+
+
+def create_company():
+	if not frappe.db.exists('Company', '_Test Company'):
+		frappe.get_doc({
+			'doctype': 'Company',
+			'company_name': '_Test Company',
+			'country': 'India',
+			'default_currency': 'INR'
+		}).insert()
+
+def create_customer():
+	if not frappe.db.exists('Customer', 'Bulk Customer'):
+		frappe.get_doc({
+			'doctype': 'Customer',
+			'customer_name': 'Bulk Customer'
+		}).insert()
+
+def create_item():
+	if not frappe.db.exists("Item", "MK"):
+		frappe.get_doc({
+			"doctype": "Item",
+			"item_code": "MK",
+			"item_name": "Milk",
+			"description": "Milk",
+			"item_group": "Products"
+		}).insert()
+
+def create_so(intent=None):
+	so = frappe.new_doc("Sales Order")
+	so.customer = "Bulk Customer"
+	so.company = "_Test Company"
+	so.transaction_date = date.today()
+
+	so.set_warehouse = "Finished Goods - _TC"
+	so.append("items", {
+		"item_code": "MK",
+		"delivery_date": date.today(),
+		"qty": 10,
+		"rate": 80,
+	})
+	so.insert()
+	so.submit()
+	return so.name
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/__init__.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/__init__.py
similarity index 100%
rename from erpnext/regional/doctype/tax_exemption_80g_certificate_detail/__init__.py
rename to erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/__init__.py
diff --git a/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json
new file mode 100644
index 0000000..8262caa
--- /dev/null
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.json
@@ -0,0 +1,86 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2021-11-30 13:38:30.926047",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "transaction_name",
+  "date",
+  "time",
+  "transaction_status",
+  "error_description",
+  "from_doctype",
+  "to_doctype",
+  "retried"
+ ],
+ "fields": [
+  {
+   "fieldname": "transaction_name",
+   "fieldtype": "Dynamic Link",
+   "in_list_view": 1,
+   "label": "Name",
+   "options": "from_doctype"
+  },
+  {
+   "fieldname": "transaction_status",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Status",
+   "read_only": 1
+  },
+  {
+   "fieldname": "error_description",
+   "fieldtype": "Long Text",
+   "label": "Error Description",
+   "read_only": 1
+  },
+  {
+   "fieldname": "from_doctype",
+   "fieldtype": "Link",
+   "label": "From Doctype",
+   "options": "DocType",
+   "read_only": 1
+  },
+  {
+   "fieldname": "to_doctype",
+   "fieldtype": "Link",
+   "label": "To Doctype",
+   "options": "DocType",
+   "read_only": 1
+  },
+  {
+   "fieldname": "date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Date ",
+   "read_only": 1
+  },
+  {
+   "fieldname": "time",
+   "fieldtype": "Time",
+   "label": "Time",
+   "read_only": 1
+  },
+  {
+   "fieldname": "retried",
+   "fieldtype": "Int",
+   "label": "Retried",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-02-03 19:57:31.650359",
+ "modified_by": "Administrator",
+ "module": "Bulk Transaction",
+ "name": "Bulk Transaction Log Detail",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.py
similarity index 77%
copy from erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
copy to erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.py
index bb7f07f..67795b9 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
+++ b/erpnext/bulk_transaction/doctype/bulk_transaction_log_detail/bulk_transaction_log_detail.py
@@ -1,10 +1,9 @@
 # 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 TaxExemption80GCertificateDetail(Document):
+class BulkTransactionLogDetail(Document):
 	pass
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index b828a43..50321ba 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -6,14 +6,17 @@
  "document_type": "Other",
  "engine": "InnoDB",
  "field_order": [
+  "supplier_and_price_defaults_section",
   "supp_master_name",
   "supplier_group",
+  "column_break_4",
   "buying_price_list",
   "maintain_same_rate_action",
   "role_to_override_stop_action",
-  "column_break_3",
+  "transaction_settings_section",
   "po_required",
   "pr_required",
+  "column_break_12",
   "maintain_same_rate",
   "allow_multiple_items",
   "bill_for_rejected_quantity_in_purchase_invoice",
@@ -43,10 +46,6 @@
    "options": "Price List"
   },
   {
-   "fieldname": "column_break_3",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "po_required",
    "fieldtype": "Select",
    "label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
@@ -73,7 +72,7 @@
   {
    "fieldname": "subcontract",
    "fieldtype": "Section Break",
-   "label": "Subcontract"
+   "label": "Subcontracting Settings"
   },
   {
    "default": "Material Transferred for Subcontract",
@@ -116,6 +115,24 @@
    "fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
    "fieldtype": "Check",
    "label": "Bill for Rejected Quantity in Purchase Invoice"
+  },
+  {
+   "fieldname": "supplier_and_price_defaults_section",
+   "fieldtype": "Section Break",
+   "label": "Supplier and Price Defaults"
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "transaction_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Transaction Settings"
+  },
+  {
+   "fieldname": "column_break_12",
+   "fieldtype": "Column Break"
   }
  ],
  "icon": "fa fa-cog",
@@ -123,7 +140,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-09-08 19:26:23.548837",
+ "modified": "2022-01-27 17:57:58.367048",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
@@ -141,5 +158,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order_list.js b/erpnext/buying/doctype/purchase_order/purchase_order_list.js
index 8413eb6..d7907e4 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order_list.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order_list.js
@@ -29,8 +29,22 @@
 			listview.call_for_selected_items(method, { "status": "Closed" });
 		});
 
-		listview.page.add_menu_item(__("Re-open"), function () {
+		listview.page.add_menu_item(__("Reopen"), function () {
 			listview.call_for_selected_items(method, { "status": "Submitted" });
 		});
+
+
+		listview.page.add_action_item(__("Purchase Invoice"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Invoice");
+		});
+
+		listview.page.add_action_item(__("Purchase Receipt"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Purchase Receipt");
+		});
+
+		listview.page.add_action_item(__("Advance Payment"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Purchase Order", "Advance Payment");
+		});
+
 	}
 };
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 9a63afc..efa2ab1 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -3,9 +3,9 @@
 
 
 import json
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, flt, getdate, nowdate
 
 from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
@@ -27,7 +27,7 @@
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
 
-class TestPurchaseOrder(unittest.TestCase):
+class TestPurchaseOrder(FrappeTestCase):
 	def test_make_purchase_receipt(self):
 		po = create_purchase_order(do_not_submit=True)
 		self.assertRaises(frappe.ValidationError, make_purchase_receipt, po.name)
@@ -682,17 +682,18 @@
 
 		bin1 = frappe.db.get_value("Bin",
 			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
-			fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
+			fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1)
 
 		# Submit PO
 		po = create_purchase_order(item_code="_Test FG Item", is_subcontracted="Yes")
 
 		bin2 = frappe.db.get_value("Bin",
 			filters={"warehouse": "_Test Warehouse - _TC", "item_code": "_Test Item"},
-			fieldname=["reserved_qty_for_sub_contract", "projected_qty"], as_dict=1)
+			fieldname=["reserved_qty_for_sub_contract", "projected_qty", "modified"], as_dict=1)
 
 		self.assertEqual(bin2.reserved_qty_for_sub_contract, bin1.reserved_qty_for_sub_contract + 10)
 		self.assertEqual(bin2.projected_qty, bin1.projected_qty - 10)
+		self.assertNotEqual(bin1.modified, bin2.modified)
 
 		# Create stock transfer
 		rm_item = [{"item_code":"_Test FG Item","rm_item_code":"_Test Item","item_name":"_Test Item",
diff --git a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
index 5190199..5b21124 100644
--- a/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/test_request_for_quotation.py
@@ -1,9 +1,9 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import nowdate
 
 from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
@@ -16,7 +16,7 @@
 from erpnext.templates.pages.rfq import check_supplier_has_docname_access
 
 
-class TestRequestforQuotation(unittest.TestCase):
+class TestRequestforQuotation(FrappeTestCase):
 	def test_quote_status(self):
 		rfq = make_request_for_quotation()
 
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 14d2ccd..4f9ff43 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -131,28 +131,6 @@
 		if frappe.defaults.get_global_default('supp_master_name') == 'Supplier Name':
 			frappe.db.set(self, "supplier_name", newdn)
 
-	def create_onboarding_docs(self, args):
-		company = frappe.defaults.get_defaults().get('company') or \
-			frappe.db.get_single_value('Global Defaults', 'default_company')
-
-		for i in range(1, args.get('max_count')):
-			supplier = args.get('supplier_name_' + str(i))
-			if supplier:
-				try:
-					doc = frappe.get_doc({
-						'doctype': self.doctype,
-						'supplier_name': supplier,
-						'supplier_group': _('Local'),
-						'company': company
-					}).insert()
-
-					if args.get('supplier_email_' + str(i)):
-						from erpnext.selling.doctype.customer.customer import create_contact
-						create_contact(supplier, 'Supplier',
-							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):
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index 13fe9df..7358e2a 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -1,7 +1,6 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-import unittest
 
 import frappe
 from frappe.test_runner import make_test_records
@@ -12,153 +11,154 @@
 test_dependencies = ['Payment Term', 'Payment Terms Template']
 test_records = frappe.get_test_records('Supplier')
 
+from frappe.tests.utils import FrappeTestCase
 
-class TestSupplier(unittest.TestCase):
-    def test_get_supplier_group_details(self):
-        doc = frappe.new_doc("Supplier Group")
-        doc.supplier_group_name = "_Testing Supplier Group"
-        doc.payment_terms = "_Test Payment Term Template 3"
-        doc.accounts = []
-        test_account_details = {
-            "company": "_Test Company",
-            "account": "Creditors - _TC",
-        }
-        doc.append("accounts", test_account_details)
-        doc.save()
-        s_doc = frappe.new_doc("Supplier")
-        s_doc.supplier_name = "Testing Supplier"
-        s_doc.supplier_group = "_Testing Supplier Group"
-        s_doc.payment_terms = ""
-        s_doc.accounts = []
-        s_doc.insert()
-        s_doc.get_supplier_group_details()
-        self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
-        self.assertEqual(s_doc.accounts[0].company, "_Test Company")
-        self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
-        s_doc.delete()
-        doc.delete()
 
-    def test_supplier_default_payment_terms(self):
-        # Payment Term based on Days after invoice date
-        frappe.db.set_value(
-            "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 3")
+class TestSupplier(FrappeTestCase):
+	def test_get_supplier_group_details(self):
+		doc = frappe.new_doc("Supplier Group")
+		doc.supplier_group_name = "_Testing Supplier Group"
+		doc.payment_terms = "_Test Payment Term Template 3"
+		doc.accounts = []
+		test_account_details = {
+			"company": "_Test Company",
+			"account": "Creditors - _TC",
+		}
+		doc.append("accounts", test_account_details)
+		doc.save()
+		s_doc = frappe.new_doc("Supplier")
+		s_doc.supplier_name = "Testing Supplier"
+		s_doc.supplier_group = "_Testing Supplier Group"
+		s_doc.payment_terms = ""
+		s_doc.accounts = []
+		s_doc.insert()
+		s_doc.get_supplier_group_details()
+		self.assertEqual(s_doc.payment_terms, "_Test Payment Term Template 3")
+		self.assertEqual(s_doc.accounts[0].company, "_Test Company")
+		self.assertEqual(s_doc.accounts[0].account, "Creditors - _TC")
+		s_doc.delete()
+		doc.delete()
 
-        due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2016-02-21")
+	def test_supplier_default_payment_terms(self):
+		# Payment Term based on Days after invoice date
+		frappe.db.set_value(
+			"Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 3")
 
-        due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2017-02-21")
+		due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2016-02-21")
 
-        # Payment Term based on last day of month
-        frappe.db.set_value(
-            "Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 1")
+		due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2017-02-21")
 
-        due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2016-02-29")
+		# Payment Term based on last day of month
+		frappe.db.set_value(
+			"Supplier", "_Test Supplier With Template 1", "payment_terms", "_Test Payment Term Template 1")
 
-        due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2017-02-28")
+		due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2016-02-29")
 
-        frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "")
+		due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2017-02-28")
 
-        # Set credit limit for the supplier group instead of supplier and evaluate the due date
-        frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3")
+		frappe.db.set_value("Supplier", "_Test Supplier With Template 1", "payment_terms", "")
 
-        due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2016-02-21")
+		# Set credit limit for the supplier group instead of supplier and evaluate the due date
+		frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 3")
 
-        # Payment terms for Supplier Group instead of supplier and evaluate the due date
-        frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1")
+		due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2016-02-21")
 
-        # Leap year
-        due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2016-02-29")
-        # # Non Leap year
-        due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
-        self.assertEqual(due_date, "2017-02-28")
+		# Payment terms for Supplier Group instead of supplier and evaluate the due date
+		frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "_Test Payment Term Template 1")
 
-        # Supplier with no default Payment Terms Template
-        frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "")
-        frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "")
+		# Leap year
+		due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2016-02-29")
+		# # Non Leap year
+		due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier With Template 1")
+		self.assertEqual(due_date, "2017-02-28")
 
-        due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier")
-        self.assertEqual(due_date, "2016-01-22")
-        # # Non Leap year
-        due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier")
-        self.assertEqual(due_date, "2017-01-22")
+		# Supplier with no default Payment Terms Template
+		frappe.db.set_value("Supplier Group", "_Test Supplier Group", "payment_terms", "")
+		frappe.db.set_value("Supplier", "_Test Supplier", "payment_terms", "")
 
-    def test_supplier_disabled(self):
-        make_test_records("Item")
+		due_date = get_due_date("2016-01-22", "Supplier", "_Test Supplier")
+		self.assertEqual(due_date, "2016-01-22")
+		# # Non Leap year
+		due_date = get_due_date("2017-01-22", "Supplier", "_Test Supplier")
+		self.assertEqual(due_date, "2017-01-22")
 
-        frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1)
+	def test_supplier_disabled(self):
+		make_test_records("Item")
 
-        from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
+		frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 1)
 
-        po = create_purchase_order(do_not_save=True)
+		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
 
-        self.assertRaises(PartyDisabled, po.save)
+		po = create_purchase_order(do_not_save=True)
 
-        frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0)
+		self.assertRaises(PartyDisabled, po.save)
 
-        po.save()
+		frappe.db.set_value("Supplier", "_Test Supplier", "disabled", 0)
 
-    def test_supplier_country(self):
-        # Test that country field exists in Supplier DocType
-        supplier = frappe.get_doc('Supplier', '_Test Supplier with Country')
-        self.assertTrue('country' in supplier.as_dict())
+		po.save()
 
-        # Test if test supplier field record is 'Greece'
-        self.assertEqual(supplier.country, "Greece")
+	def test_supplier_country(self):
+		# Test that country field exists in Supplier DocType
+		supplier = frappe.get_doc('Supplier', '_Test Supplier with Country')
+		self.assertTrue('country' in supplier.as_dict())
 
-        # Test update Supplier instance country value
-        supplier = frappe.get_doc('Supplier', '_Test Supplier')
-        supplier.country = 'Greece'
-        supplier.save()
-        self.assertEqual(supplier.country, "Greece")
+		# Test if test supplier field record is 'Greece'
+		self.assertEqual(supplier.country, "Greece")
 
-    def test_party_details_tax_category(self):
-        from erpnext.accounts.party import get_party_details
+		# Test update Supplier instance country value
+		supplier = frappe.get_doc('Supplier', '_Test Supplier')
+		supplier.country = 'Greece'
+		supplier.save()
+		self.assertEqual(supplier.country, "Greece")
 
-        frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing")
+	def test_party_details_tax_category(self):
+		from erpnext.accounts.party import get_party_details
 
-        # Tax Category without Address
-        details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
-        self.assertEqual(details.tax_category, "_Test Tax Category 1")
+		frappe.delete_doc_if_exists("Address", "_Test Address With Tax Category-Billing")
 
-        address = frappe.get_doc(dict(
-            doctype='Address',
-            address_title='_Test Address With Tax Category',
-            tax_category='_Test Tax Category 2',
-            address_type='Billing',
-            address_line1='Station Road',
-            city='_Test City',
-            country='India',
-            links=[dict(
-                link_doctype='Supplier',
-                link_name='_Test Supplier With Tax Category'
-            )]
-        )).insert()
+		# Tax Category without Address
+		details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
+		self.assertEqual(details.tax_category, "_Test Tax Category 1")
 
-        # Tax Category with Address
-        details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
-        self.assertEqual(details.tax_category, "_Test Tax Category 2")
+		address = frappe.get_doc(dict(
+			doctype='Address',
+			address_title='_Test Address With Tax Category',
+			tax_category='_Test Tax Category 2',
+			address_type='Billing',
+			address_line1='Station Road',
+			city='_Test City',
+			country='India',
+			links=[dict(
+				link_doctype='Supplier',
+				link_name='_Test Supplier With Tax Category'
+			)]
+		)).insert()
 
-        # Rollback
-        address.delete()
+		# Tax Category with Address
+		details = get_party_details("_Test Supplier With Tax Category", party_type="Supplier")
+		self.assertEqual(details.tax_category, "_Test Tax Category 2")
+
+		# Rollback
+		address.delete()
 
 def create_supplier(**args):
-    args = frappe._dict(args)
+	args = frappe._dict(args)
 
-    try:
-        doc = frappe.get_doc({
-            "doctype": "Supplier",
-            "supplier_name": args.supplier_name,
-            "supplier_group": args.supplier_group or "Services",
-            "supplier_type": args.supplier_type or "Company",
-            "tax_withholding_category": args.tax_withholding_category
-        }).insert()
+	if frappe.db.exists("Supplier", args.supplier_name):
+		return frappe.get_doc("Supplier", args.supplier_name)
 
-        return doc
+	doc = frappe.get_doc({
+		"doctype": "Supplier",
+		"supplier_name": args.supplier_name,
+		"supplier_group": args.supplier_group or "Services",
+		"supplier_type": args.supplier_type or "Company",
+		"tax_withholding_category": args.tax_withholding_category
+	}).insert()
 
-    except frappe.DuplicateEntryError:
-        return frappe.get_doc("Supplier", args.supplier_name)
+	return doc
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index d65ab94..171de78 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -143,6 +143,26 @@
 	return doclist
 
 @frappe.whitelist()
+def make_purchase_invoice(source_name, target_doc=None):
+	doc = get_mapped_doc("Supplier Quotation", source_name, {
+		"Supplier Quotation": {
+			"doctype": "Purchase Invoice",
+			"validation": {
+				"docstatus": ["=", 1],
+			}
+		},
+		"Supplier Quotation Item": {
+			"doctype": "Purchase Invoice Item"
+		},
+		"Purchase Taxes and Charges": {
+			"doctype": "Purchase Taxes and Charges"
+		}
+	}, target_doc)
+
+	return doc
+
+
+@frappe.whitelist()
 def make_quotation(source_name, target_doc=None):
 	doclist = get_mapped_doc("Supplier Quotation", source_name, {
 		"Supplier Quotation": {
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js
index 5ab6c98..73685ca 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation_list.js
@@ -8,5 +8,15 @@
 		} else if(doc.status==="Expired") {
 			return [__("Expired"), "gray", "status,=,Expired"];
 		}
+	},
+
+	onload: function(listview) {
+		listview.page.add_action_item(__("Purchase Order"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Order");
+		});
+
+		listview.page.add_action_item(__("Purchase Invoice"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Supplier Quotation", "Purchase Invoice");
+		});
 	}
 };
diff --git a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
index d48ac7e..a4d4597 100644
--- a/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/test_supplier_quotation.py
@@ -3,12 +3,12 @@
 
 
 
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 
-class TestPurchaseOrder(unittest.TestCase):
+class TestPurchaseOrder(FrappeTestCase):
 	def test_make_purchase_order(self):
 		from erpnext.buying.doctype.supplier_quotation.supplier_quotation import make_purchase_order
 
diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
index 49e3351..8ecc2cd 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -1,12 +1,12 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 
-class TestSupplierScorecard(unittest.TestCase):
+class TestSupplierScorecard(FrappeTestCase):
 
 	def test_create_scorecard(self):
 		doc = make_supplier_scorecard().insert()
@@ -49,7 +49,7 @@
 				"min_grade":0.0,"name":"Very Poor",
 				"prevent_rfqs":1,
 				"notify_supplier":0,
-				"doctype":"Supplier Scorecard Standing",
+				"doctype":"Supplier Scorecard Scoring Standing",
 				"max_grade":30.0,
 				"prevent_pos":1,
 				"warn_pos":0,
@@ -65,7 +65,7 @@
 				"name":"Poor",
 				"prevent_rfqs":1,
 				"notify_supplier":0,
-				"doctype":"Supplier Scorecard Standing",
+				"doctype":"Supplier Scorecard Scoring Standing",
 				"max_grade":50.0,
 				"prevent_pos":0,
 				"warn_pos":0,
@@ -81,7 +81,7 @@
 				"name":"Average",
 				"prevent_rfqs":0,
 				"notify_supplier":0,
-				"doctype":"Supplier Scorecard Standing",
+				"doctype":"Supplier Scorecard Scoring Standing",
 				"max_grade":80.0,
 				"prevent_pos":0,
 				"warn_pos":0,
@@ -97,7 +97,7 @@
 				"name":"Excellent",
 				"prevent_rfqs":0,
 				"notify_supplier":0,
-				"doctype":"Supplier Scorecard Standing",
+				"doctype":"Supplier Scorecard Scoring Standing",
 				"max_grade":100.0,
 				"prevent_pos":0,
 				"warn_pos":0,
diff --git a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
index dacc982..7ff84c1 100644
--- a/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
+++ b/erpnext/buying/doctype/supplier_scorecard_criteria/test_supplier_scorecard_criteria.py
@@ -1,12 +1,12 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 
-class TestSupplierScorecardCriteria(unittest.TestCase):
+class TestSupplierScorecardCriteria(FrappeTestCase):
 	def test_variables_exist(self):
 		delete_test_scorecards()
 		for d in test_good_criteria:
diff --git a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
index 4d75981..32005a3 100644
--- a/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
+++ b/erpnext/buying/doctype/supplier_scorecard_variable/test_supplier_scorecard_variable.py
@@ -1,16 +1,16 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.buying.doctype.supplier_scorecard_variable.supplier_scorecard_variable import (
 	VariablePathNotFound,
 )
 
 
-class TestSupplierScorecardVariable(unittest.TestCase):
+class TestSupplierScorecardVariable(FrappeTestCase):
 	def test_variable_exist(self):
 		for d in test_existing_variables:
 			my_doc = frappe.get_doc("Supplier Scorecard Variable", d.get("name"))
diff --git a/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json b/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json
deleted file mode 100644
index ce3d8cf..0000000
--- a/erpnext/buying/onboarding_slide/add_a_few_suppliers/add_a_few_suppliers.json
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "add_more_button": 1,
- "app": "ERPNext",
- "creation": "2019-11-15 14:45:32.626641",
- "docstatus": 0,
- "doctype": "Onboarding Slide",
- "domains": [],
- "help_links": [
-  {
-   "label": "Learn More",
-   "video_id": "zsrrVDk6VBs"
-  }
- ],
- "idx": 0,
- "image_src": "",
- "is_completed": 0,
- "max_count": 3,
- "modified": "2019-12-09 17:54:18.452038",
- "modified_by": "Administrator",
- "name": "Add A Few Suppliers",
- "owner": "Administrator",
- "ref_doctype": "Supplier",
- "slide_desc": "",
- "slide_fields": [
-  {
-   "align": "",
-   "fieldname": "supplier_name",
-   "fieldtype": "Data",
-   "label": "Supplier Name",
-   "placeholder": "",
-   "reqd": 1
-  },
-  {
-   "align": "",
-   "fieldtype": "Column Break",
-   "reqd": 0
-  },
-  {
-   "align": "",
-   "fieldname": "supplier_email",
-   "fieldtype": "Data",
-   "label": "Supplier Email",
-   "reqd": 1
-  }
- ],
- "slide_order": 50,
- "slide_title": "Add A Few Suppliers",
- "slide_type": "Create"
-}
\ No newline at end of file
diff --git a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
index 84de8c6..4452452 100644
--- a/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/test_procurement_tracker.py
@@ -2,10 +2,10 @@
 # For license information, please see license.txt
 
 
-import unittest
 from datetime import datetime
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
 from erpnext.buying.report.procurement_tracker.procurement_tracker import execute
@@ -14,7 +14,7 @@
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 
 
-class TestProcurementTracker(unittest.TestCase):
+class TestProcurementTracker(FrappeTestCase):
 	def test_result_for_procurement_tracker(self):
 		filters = {
 			'company': '_Test Procurement Company',
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
index 144523a..c2b38d3 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/test_subcontracted_item_to_be_received.py
@@ -3,9 +3,9 @@
 # Compiled at: 2019-05-06 09:51:46
 # Decompiled by https://python-decompiler.com
 
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
 from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -15,7 +15,7 @@
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
 
-class TestSubcontractedItemToBeReceived(unittest.TestCase):
+class TestSubcontractedItemToBeReceived(FrappeTestCase):
 
 	def test_pending_and_received_qty(self):
 		po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index 3c203ac..fc9acab 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -4,9 +4,9 @@
 # Decompiled by https://python-decompiler.com
 
 import json
-import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
 from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -16,7 +16,7 @@
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 
 
-class TestSubcontractedItemToBeTransferred(unittest.TestCase):
+class TestSubcontractedItemToBeTransferred(FrappeTestCase):
 
 	def test_pending_and_transferred_qty(self):
 		po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")
diff --git a/erpnext/commands/__init__.py b/erpnext/commands/__init__.py
index 5931119..8e12fad 100644
--- a/erpnext/commands/__init__.py
+++ b/erpnext/commands/__init__.py
@@ -1,49 +1,10 @@
-# Copyright (c) 2015, Web Notes Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# GPL v3 License. See license.txt
 
 import click
-import frappe
-from frappe.commands import get_site, pass_context
 
 
 def call_command(cmd, context):
 	return click.Context(cmd, obj=context).forward(cmd)
 
-@click.command('make-demo')
-@click.option('--site', help='site name')
-@click.option('--domain', default='Manufacturing')
-@click.option('--days', default=100,
-	help='Run the demo for so many days. Default 100')
-@click.option('--resume', default=False, is_flag=True,
-	help='Continue running the demo for given days')
-@click.option('--reinstall', default=False, is_flag=True,
-	help='Reinstall site before demo')
-@pass_context
-def make_demo(context, site, domain='Manufacturing', days=100,
-	resume=False, reinstall=False):
-	"Reinstall site and setup demo"
-	from frappe.commands.site import _reinstall
-	from frappe.installer import install_app
-
-	site = get_site(context)
-
-	if resume:
-		with frappe.init_site(site):
-			frappe.connect()
-			from erpnext.demo import demo
-			demo.simulate(days=days)
-	else:
-		if reinstall:
-			_reinstall(site, yes=True)
-		with frappe.init_site(site=site):
-			frappe.connect()
-			if not 'erpnext' in frappe.get_installed_apps():
-				install_app('erpnext')
-
-			# import needs site
-			from erpnext.demo import demo
-			demo.make(domain, days)
-
-commands = [
-	make_demo
-]
+commands = []
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 4775f56..a94af10 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -167,9 +167,14 @@
 
 		validate_regional(self)
 
+		validate_einvoice_fields(self)
+
 		if self.doctype != 'Material Request':
 			apply_pricing_rule_on_transaction(self)
 
+	def before_cancel(self):
+		validate_einvoice_fields(self)
+
 	def on_trash(self):
 		# delete sl and gl entries on deletion of transaction
 		if frappe.db.get_single_value('Accounts Settings', 'delete_linked_ledger_entries'):
@@ -402,6 +407,22 @@
 								if item_qty != len(get_serial_nos(item.get('serial_no'))):
 									item.set(fieldname, value)
 
+							elif (
+								ret.get("pricing_rule_removed")
+								and value is not None
+								and fieldname
+								in [
+									"discount_percentage",
+									"discount_amount",
+									"rate",
+									"margin_rate_or_amount",
+									"margin_type",
+									"remove_free_item",
+								]
+							):
+								# reset pricing rule fields if pricing_rule_removed
+								item.set(fieldname, value)
+
 					if self.doctype in ["Purchase Invoice", "Sales Invoice"] and item.meta.get_field('is_fixed_asset'):
 						item.set('is_fixed_asset', ret.get('is_fixed_asset', 0))
 
@@ -1313,6 +1334,9 @@
 				payment_schedule['discount_type'] = schedule.discount_type
 				payment_schedule['discount'] = schedule.discount
 
+			if not schedule.invoice_portion:
+				payment_schedule['payment_amount'] = schedule.payment_amount
+
 			self.append("payment_schedule", payment_schedule)
 
 	def set_due_date(self):
@@ -1542,13 +1566,12 @@
 		tax.rate = None
 
 
-def validate_account_head(tax, doc):
-	company = frappe.get_cached_value('Account',
-		tax.account_head, 'company')
+def validate_account_head(idx, account, company):
+	account_company = frappe.get_cached_value('Account', account, 'company')
 
-	if company != doc.company:
+	if account_company != company:
 		frappe.throw(_('Row {0}: Account {1} does not belong to Company {2}')
-			.format(tax.idx, frappe.bold(tax.account_head), frappe.bold(doc.company)), title=_('Invalid Account'))
+			.format(idx, frappe.bold(account), frappe.bold(company)), title=_('Invalid Account'))
 
 
 def validate_cost_center(tax, doc):
@@ -1931,7 +1954,8 @@
 
 		qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
 
-	update_bin_qty(row.item_code, row.warehouse, qty_dict)
+	if row.warehouse:
+		update_bin_qty(row.item_code, row.warehouse, qty_dict)
 
 def validate_and_delete_children(parent, data):
 	deleted_children = []
@@ -2151,3 +2175,7 @@
 @erpnext.allow_regional
 def validate_regional(doc):
 	pass
+
+@erpnext.allow_regional
+def validate_einvoice_fields(doc):
+	pass
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index a3d2502..b740476 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -70,9 +70,18 @@
 
 		# set contact and address details for supplier, if they are not mentioned
 		if getattr(self, "supplier", None):
-			self.update_if_missing(get_party_details(self.supplier, party_type="Supplier", ignore_permissions=self.flags.ignore_permissions,
-			doctype=self.doctype, company=self.company, party_address=self.supplier_address, shipping_address=self.get('shipping_address'),
-			fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template')))
+			self.update_if_missing(
+				get_party_details(
+					self.supplier,
+					party_type="Supplier",
+					doctype=self.doctype,
+					company=self.company,
+					party_address=self.get("supplier_address"),
+					shipping_address=self.get('shipping_address'),
+					fetch_payment_terms_template= not self.get('ignore_default_payment_terms_template'),
+					ignore_permissions=self.flags.ignore_permissions
+				)
+			)
 
 		self.set_missing_item_details(for_validate)
 
@@ -240,6 +249,7 @@
 						"posting_time": self.get('posting_time'),
 						"qty": -1 * flt(d.get('stock_qty')),
 						"serial_no": d.get('serial_no'),
+						"batch_no": d.get("batch_no"),
 						"company": self.company,
 						"voucher_type": self.doctype,
 						"voucher_no": self.name,
@@ -269,7 +279,8 @@
 						"posting_date": self.posting_date,
 						"posting_time": self.posting_time,
 						"qty": -1 * d.consumed_qty,
-						"serial_no": d.serial_no
+						"serial_no": d.serial_no,
+						"batch_no": d.batch_no,
 					})
 
 					if rate > 0:
@@ -554,10 +565,13 @@
 					# Check for asset naming series
 					if item_data.get('asset_naming_series'):
 						created_assets = []
-
-						for qty in range(cint(d.qty)):
-							asset = self.make_asset(d)
+						if item_data.get('is_grouped_asset'):
+							asset = self.make_asset(d, is_grouped_asset=True)
 							created_assets.append(asset)
+						else:
+							for qty in range(cint(d.qty)):
+								asset = self.make_asset(d)
+								created_assets.append(asset)
 
 						if len(created_assets) > 5:
 							# dont show asset form links if more than 5 assets are created
@@ -580,14 +594,18 @@
 		for message in messages:
 			frappe.msgprint(message, title="Success", indicator="green")
 
-	def make_asset(self, row):
+	def make_asset(self, row, is_grouped_asset=False):
 		if not row.asset_location:
 			frappe.throw(_("Row {0}: Enter location for the asset item {1}").format(row.idx, row.item_code))
 
 		item_data = frappe.db.get_value('Item',
 			row.item_code, ['asset_naming_series', 'asset_category'], as_dict=1)
 
-		purchase_amount = flt(row.base_rate + row.item_tax_amount)
+		if is_grouped_asset:
+			purchase_amount = flt(row.base_amount + row.item_tax_amount)
+		else:
+			purchase_amount = flt(row.base_rate + row.item_tax_amount)
+
 		asset = frappe.get_doc({
 			'doctype': 'Asset',
 			'item_code': row.item_code,
@@ -601,6 +619,7 @@
 			'calculate_depreciation': 1,
 			'purchase_receipt_amount': purchase_amount,
 			'gross_purchase_amount': purchase_amount,
+			'asset_quantity': row.qty if is_grouped_asset else 0,
 			'purchase_receipt': self.name if self.doctype == 'Purchase Receipt' else None,
 			'purchase_invoice': self.name if self.doctype == 'Purchase Invoice' else None
 		})
@@ -687,7 +706,7 @@
 
 def get_asset_item_details(asset_items):
 	asset_items_data = {}
-	for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series"],
+	for d in frappe.get_all('Item', fields = ["name", "auto_create_assets", "asset_naming_series", "is_grouped_asset"],
 		filters = {'name': ('in', asset_items)}):
 		asset_items_data.setdefault(d.name, d)
 
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
index b8dc92e..dd02ce1 100644
--- a/erpnext/controllers/employee_boarding_controller.py
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -104,11 +104,11 @@
 	def get_task_dates(self, activity, holiday_list):
 		start_date = end_date = None
 
-		if activity.begin_on:
+		if activity.begin_on is not None:
 			start_date = add_days(self.boarding_begins_on, activity.begin_on)
 			start_date = self.update_if_holiday(start_date, holiday_list)
 
-			if activity.duration:
+			if activity.duration is not None:
 				end_date = add_days(self.boarding_begins_on, activity.begin_on + activity.duration)
 				end_date = self.update_if_holiday(end_date, holiday_list)
 
@@ -132,13 +132,17 @@
 
 	def on_cancel(self):
 		# delete task project
-		for task in frappe.get_all('Task', filters={'project': self.project}):
+		project = self.project
+		for task in frappe.get_all('Task', filters={'project': project}):
 			frappe.delete_doc('Task', task.name, force=1)
-		frappe.delete_doc('Project', self.project, force=1)
+		frappe.delete_doc('Project', project, force=1)
 		self.db_set('project', '')
 		for activity in self.activities:
 			activity.db_set('task', '')
 
+		frappe.msgprint(_('Linked Project {} and Tasks deleted.').format(
+			project), alert=True, indicator='blue')
+
 
 @frappe.whitelist()
 def get_onboarding_details(parent, parenttype):
diff --git a/erpnext/controllers/item_variant.py b/erpnext/controllers/item_variant.py
index 2bad6f8..68ad702 100644
--- a/erpnext/controllers/item_variant.py
+++ b/erpnext/controllers/item_variant.py
@@ -132,7 +132,7 @@
 
 	conditions = " or ".join(conditions)
 
-	from erpnext.portal.product_configurator.utils import get_item_codes_by_attributes
+	from erpnext.e_commerce.variant_selector.utils import get_item_codes_by_attributes
 	possible_variants = [i for i in get_item_codes_by_attributes(args, template) if i != variant_item_code]
 
 	for variant in possible_variants:
@@ -262,9 +262,8 @@
 def copy_attributes_to_variant(item, variant):
 	# copy non no-copy fields
 
-	exclude_fields = ["naming_series", "item_code", "item_name", "show_in_website",
-		"show_variant_in_website", "opening_stock", "variant_of", "valuation_rate",
-		"has_variants", "attributes"]
+	exclude_fields = ["naming_series", "item_code", "item_name", "published_in_website",
+		"opening_stock", "variant_of", "valuation_rate"]
 
 	if item.variant_based_on=='Manufacturer':
 		# don't copy manufacturer values if based on part no
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index dc04dab..dd9b45c 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -249,6 +249,9 @@
 				del filters['customer']
 			else:
 				del filters['supplier']
+		else:
+			filters.pop('customer', None)
+			filters.pop('supplier', None)
 
 
 	description_cond = ''
@@ -707,6 +710,7 @@
 
 	item_doc = frappe.get_cached_doc('Item', filters.get('item_code'))
 	item_group = filters.get('item_group')
+	company = filters.get('company')
 	taxes = item_doc.taxes or []
 
 	while item_group:
@@ -715,7 +719,7 @@
 		item_group = item_group_doc.parent_item_group
 
 	if not taxes:
-		return frappe.db.sql(""" SELECT name FROM `tabItem Tax Template` """)
+		return frappe.get_all('Item Tax Template', filters={'disabled': 0, 'company': company}, as_list=True)
 	else:
 		valid_from = filters.get('valid_from')
 		valid_from = valid_from[1] if isinstance(valid_from, list) else valid_from
@@ -724,7 +728,7 @@
 			'item_code': filters.get('item_code'),
 			'posting_date': valid_from,
 			'tax_category': filters.get('tax_category'),
-			'company': filters.get('company')
+			'company': company
 		}
 
 		taxes = _get_item_tax_template(args, taxes, for_validate=True)
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index df3c5f1..8c3aab4 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -420,6 +420,7 @@
 				"posting_time": sle.get('posting_time'),
 				"qty": sle.actual_qty,
 				"serial_no": sle.get('serial_no'),
+				"batch_no": sle.get("batch_no"),
 				"company": sle.company,
 				"voucher_type": sle.voucher_type,
 				"voucher_no": sle.voucher_no
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 4ff851d..e918cde 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -74,7 +74,8 @@
 				doctype=self.doctype, company=self.company,
 				posting_date=self.get('posting_date'),
 				fetch_payment_terms_template=fetch_payment_terms_template,
-				party_address=self.customer_address, shipping_address=self.shipping_address_name)
+				party_address=self.customer_address, shipping_address=self.shipping_address_name,
+				company_address=self.get('company_address'))
 			if not self.meta.get_field("sales_team"):
 				party_details.pop("sales_team")
 			self.update_if_missing(party_details)
@@ -204,7 +205,7 @@
 		valuation_rate_map = {}
 
 		for item in self.items:
-			if not item.item_code:
+			if not item.item_code or item.is_free_item:
 				continue
 
 			last_purchase_rate, is_stock_item = frappe.get_cached_value(
@@ -251,7 +252,7 @@
 			valuation_rate_map[(rate.item_code, rate.warehouse)] = rate.valuation_rate
 
 		for item in self.items:
-			if not item.item_code:
+			if not item.item_code or item.is_free_item:
 				continue
 
 			last_valuation_rate = valuation_rate_map.get(
@@ -393,6 +394,7 @@
 						"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'),
+						"batch_no": d.get("batch_no"),
 						"company": self.company,
 						"voucher_type": self.doctype,
 						"voucher_no": self.name,
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index 76a7cda..affde4a 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -400,6 +400,16 @@
 			ref_doc = frappe.get_doc(ref_dt, ref_dn)
 
 			ref_doc.db_set("per_billed", per_billed)
+
+			# set billling status
+			if hasattr(ref_doc, 'billing_status'):
+				if ref_doc.per_billed < 0.001:
+					ref_doc.db_set("billing_status", "Not Billed")
+				elif ref_doc.per_billed > 99.999999:
+					ref_doc.db_set("billing_status", "Fully Billed")
+				else:
+					ref_doc.db_set("billing_status", "Partly Billed")
+
 			ref_doc.set_status(update=True)
 
 def get_allowance_for(item_code, item_allowance=None, global_qty_allowance=None, global_amount_allowance=None, qty_or_amount="qty"):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index f22669b..c8e5edd 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -3,6 +3,7 @@
 
 import json
 from collections import defaultdict
+from typing import List, Tuple
 
 import frappe
 from frappe import _
@@ -40,7 +41,10 @@
 		if self.docstatus == 2:
 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
 
-		if cint(erpnext.is_perpetual_inventory_enabled(self.company)):
+		provisional_accounting_for_non_stock_items = \
+			cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+
+		if cint(erpnext.is_perpetual_inventory_enabled(self.company)) or provisional_accounting_for_non_stock_items:
 			warehouse_account = get_warehouse_account_map(self.company)
 
 			if self.docstatus==1:
@@ -77,17 +81,17 @@
 						.format(d.idx, get_link_to_form("Batch", d.get("batch_no"))))
 
 	def clean_serial_nos(self):
+		from erpnext.stock.doctype.serial_no.serial_no import clean_serial_no_string
+
 		for row in self.get("items"):
 			if hasattr(row, "serial_no") and row.serial_no:
-				# replace commas by linefeed
-				row.serial_no = row.serial_no.replace(",", "\n")
+				# remove extra whitespace and store one serial no on each line
+				row.serial_no = clean_serial_no_string(row.serial_no)
 
-				# strip preceeding and succeeding spaces for each SN
-				# (SN could have valid spaces in between e.g. SN - 123 - 2021)
-				serial_no_list = row.serial_no.split("\n")
-				serial_no_list = [sn.strip() for sn in serial_no_list]
-
-				row.serial_no = "\n".join(serial_no_list)
+		for row in self.get('packed_items') or []:
+			if hasattr(row, "serial_no") and row.serial_no:
+				# remove extra whitespace and store one serial no on each line
+				row.serial_no = clean_serial_no_string(row.serial_no)
 
 	def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
 			default_cost_center=None):
@@ -178,33 +182,28 @@
 
 			return details
 
-	def get_items_and_warehouses(self):
-		items, warehouses = [], []
+	def get_items_and_warehouses(self) -> Tuple[List[str], List[str]]:
+		"""Get list of items and warehouses affected by a transaction"""
 
-		if hasattr(self, "items"):
-			item_doclist = self.get("items")
-		elif self.doctype == "Stock Reconciliation":
-			item_doclist = []
-			data = json.loads(self.reconciliation_json)
-			for row in data[data.index(self.head_row)+1:]:
-				d = frappe._dict(zip(["item_code", "warehouse", "qty", "valuation_rate"], row))
-				item_doclist.append(d)
+		if not (hasattr(self, "items") or hasattr(self, "packed_items")):
+			return [], []
 
-		if item_doclist:
-			for d in item_doclist:
-				if d.item_code and d.item_code not in items:
-					items.append(d.item_code)
+		item_rows = (self.get("items") or []) + (self.get("packed_items") or [])
 
-				if d.get("warehouse") and d.warehouse not in warehouses:
-					warehouses.append(d.warehouse)
+		items = {d.item_code for d in item_rows if d.item_code}
 
-				if self.doctype == "Stock Entry":
-					if d.get("s_warehouse") and d.s_warehouse not in warehouses:
-						warehouses.append(d.s_warehouse)
-					if d.get("t_warehouse") and d.t_warehouse not in warehouses:
-						warehouses.append(d.t_warehouse)
+		warehouses = set()
+		for d in item_rows:
+			if d.get("warehouse"):
+				warehouses.add(d.warehouse)
 
-		return items, warehouses
+			if self.doctype == "Stock Entry":
+				if d.get("s_warehouse"):
+					warehouses.add(d.s_warehouse)
+				if d.get("t_warehouse"):
+					warehouses.add(d.t_warehouse)
+
+		return list(items), list(warehouses)
 
 	def get_stock_ledger_details(self):
 		stock_ledger = {}
@@ -216,7 +215,7 @@
 			from
 				`tabStock Ledger Entry`
 			where
-				voucher_type=%s and voucher_no=%s
+				voucher_type=%s and voucher_no=%s and is_cancelled = 0
 		""", (self.doctype, self.name), as_dict=True)
 
 		for sle in stock_ledger_entries:
@@ -256,11 +255,7 @@
 		for d in self.items:
 			if not d.batch_no: continue
 
-			serial_nos = [sr.name for sr in frappe.get_all("Serial No",
-				{'batch_no': d.batch_no, 'status': 'Inactive'})]
-
-			if serial_nos:
-				frappe.db.set_value("Serial No", { 'name': ['in', serial_nos] }, "batch_no", None)
+			frappe.db.set_value("Serial No", {"batch_no": d.batch_no, "status": "Inactive"}, "batch_no", None)
 
 			d.batch_no = None
 			d.db_set("batch_no", None)
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
index 3addb91..c52c688 100644
--- a/erpnext/controllers/subcontracting.py
+++ b/erpnext/controllers/subcontracting.py
@@ -363,8 +363,6 @@
 			return
 
 		for row in self.get(self.raw_material_table):
-			self.__validate_consumed_qty(row)
-
 			key = (row.rm_item_code, row.main_item_code, row.purchase_order)
 			if not self.__transferred_items or not self.__transferred_items.get(key):
 				return
@@ -372,12 +370,6 @@
 			self.__validate_batch_no(row, key)
 			self.__validate_serial_no(row, key)
 
-	def __validate_consumed_qty(self, row):
-		if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
-			msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
-
-			frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
-
 	def __validate_batch_no(self, row, key):
 		if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
 			link = get_link_to_form('Purchase Order', row.purchase_order)
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 075e3e3..2776628 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -106,6 +106,9 @@
 		self.doc.conversion_rate = flt(self.doc.conversion_rate)
 
 	def calculate_item_values(self):
+		if self.doc.get('is_consolidated'):
+			return
+
 		if not self.discount_amount_applied:
 			for item in self.doc.get("items"):
 				self.doc.round_floats_in(item)
@@ -647,12 +650,12 @@
 	def calculate_change_amount(self):
 		self.doc.change_amount = 0.0
 		self.doc.base_change_amount = 0.0
+		grand_total = self.doc.rounded_total or self.doc.grand_total
+		base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
 
 		if self.doc.doctype == "Sales Invoice" \
-			and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
+			and self.doc.paid_amount > grand_total and not self.doc.is_return \
 			and any(d.type == "Cash" for d in self.doc.payments):
-			grand_total = self.doc.rounded_total or self.doc.grand_total
-			base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
 
 			self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
 				self.doc.write_off_amount, self.doc.precision("change_amount"))
diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py
index 908d78c..60d1733 100644
--- a/erpnext/controllers/tests/test_queries.py
+++ b/erpnext/controllers/tests/test_queries.py
@@ -56,6 +56,12 @@
 		bundled_stock_items = query(txt="_test product bundle item 5", filters={"is_stock_item": 1})
 		self.assertEqual(len(bundled_stock_items), 0)
 
+		# empty customer/supplier should be stripped of instead of failure
+		query(txt="", filters={"customer": None})
+		query(txt="", filters={"customer": ""})
+		query(txt="", filters={"supplier": None})
+		query(txt="", filters={"supplier": ""})
+
 	def test_bom_qury(self):
 		query = add_default_params(queries.bom, "BOM")
 
diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js
index 11bfa74..cac45c6 100644
--- a/erpnext/crm/doctype/campaign/campaign.js
+++ b/erpnext/crm/doctype/campaign/campaign.js
@@ -5,7 +5,7 @@
 	refresh: function(frm) {
 		erpnext.toggle_naming_series();
 
-		if (frm.doc.__islocal) {
+		if (frm.is_new()) {
 			frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
 		} else {
 			cur_frm.add_custom_button(__("View Leads"), function() {
diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.py b/erpnext/crm/doctype/crm_settings/crm_settings.py
index bde5254..98cf7d8 100644
--- a/erpnext/crm/doctype/crm_settings/crm_settings.py
+++ b/erpnext/crm/doctype/crm_settings/crm_settings.py
@@ -1,9 +1,10 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-# import frappe
+import frappe
 from frappe.model.document import Document
 
 
 class CRMSettings(Document):
-	pass
+	def validate(self):
+		frappe.db.set_default("campaign_naming_by", self.get("campaign_naming_by", ""))
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index 8fd4978..d2ac10a 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -2,13 +2,14 @@
 # For license information, please see license.txt
 
 
+from urllib.parse import urlencode
+
 import frappe
 import requests
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import get_url_to_form
 from frappe.utils.file_manager import get_file_path
-from six.moves.urllib.parse import urlencode
 
 
 class LinkedInSettings(Document):
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index f8376e6..8e7d67e 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -24,6 +24,14 @@
 			frm.trigger('set_contact_link');
 		}
 	},
+
+	validate: function(frm) {
+		if (frm.doc.status == "Lost" && !frm.doc.lost_reasons.length) {
+			frm.trigger('set_as_lost_dialog');
+			frappe.throw(__("Lost Reasons are required in case opportunity is Lost."));
+		}
+	},
+
 	contact_date: function(frm) {
 		if(frm.doc.contact_date < frappe.datetime.now_datetime()){
 			frm.set_value("contact_date", "");
@@ -82,7 +90,7 @@
 		frm.trigger('setup_opportunity_from');
 		erpnext.toggle_naming_series();
 
-		if(!doc.__islocal && doc.status!=="Lost") {
+		if(!frm.is_new() && doc.status!=="Lost") {
 			if(doc.with_items){
 				frm.add_custom_button(__('Supplier Quotation'),
 					function() {
@@ -187,11 +195,11 @@
 
 	change_form_labels: function(frm) {
 		let company_currency = erpnext.get_currency(frm.doc.company);
-		frm.set_currency_labels(["base_opportunity_amount", "base_total", "base_grand_total"], company_currency);
-		frm.set_currency_labels(["opportunity_amount", "total", "grand_total"], frm.doc.currency);
+		frm.set_currency_labels(["base_opportunity_amount", "base_total"], company_currency);
+		frm.set_currency_labels(["opportunity_amount", "total"], frm.doc.currency);
 
 		// toggle fields
-		frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total", "base_grand_total"],
+		frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total"],
 			frm.doc.currency != company_currency);
 	},
 
@@ -209,20 +217,15 @@
 	},
 
 	calculate_total: function(frm) {
-		let total = 0, base_total = 0, grand_total = 0, base_grand_total = 0;
+		let total = 0, base_total = 0;
 		frm.doc.items.forEach(item => {
 			total += item.amount;
 			base_total += item.base_amount;
 		})
 
-		base_grand_total = base_total + frm.doc.base_opportunity_amount;
-		grand_total = total + frm.doc.opportunity_amount;
-
 		frm.set_value({
 			'total': flt(total),
-			'base_total': flt(base_total),
-			'grand_total': flt(grand_total),
-			'base_grand_total': flt(base_grand_total)
+			'base_total': flt(base_total)
 		});
 	}
 
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index dc32d9a..089f2d2 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -42,10 +42,8 @@
   "items",
   "section_break_32",
   "base_total",
-  "base_grand_total",
   "column_break_33",
   "total",
-  "grand_total",
   "contact_info",
   "customer_address",
   "address_display",
@@ -476,21 +474,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "base_grand_total",
-   "fieldtype": "Currency",
-   "label": "Grand Total (Company Currency)",
-   "options": "Company:company:default_currency",
-   "print_hide": 1,
-   "read_only": 1
-  },
-  {
-   "fieldname": "grand_total",
-   "fieldtype": "Currency",
-   "label": "Grand Total",
-   "options": "currency",
-   "read_only": 1
-  },
-  {
    "fieldname": "lost_detail_section",
    "fieldtype": "Section Break",
    "label": "Lost Reasons"
@@ -510,7 +493,7 @@
  "icon": "fa fa-info-sign",
  "idx": 195,
  "links": [],
- "modified": "2021-10-21 12:04:30.151379",
+ "modified": "2022-01-29 19:32:26.382896",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Opportunity",
@@ -547,6 +530,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "subject_field": "title",
  "timeline_field": "party_name",
  "title_field": "title",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index a4fd765..2d53874 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -69,8 +69,6 @@
 
 		self.total = flt(total)
 		self.base_total = flt(base_total)
-		self.grand_total = flt(self.total) + flt(self.opportunity_amount)
-		self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount)
 
 	def make_new_lead_if_required(self):
 		"""Set lead against new opportunity"""
diff --git a/erpnext/crm/doctype/opportunity_lost_reason/opportunity_lost_reason.json b/erpnext/crm/doctype/opportunity_lost_reason/opportunity_lost_reason.json
index 8a8d425..0cfcf0e 100644
--- a/erpnext/crm/doctype/opportunity_lost_reason/opportunity_lost_reason.json
+++ b/erpnext/crm/doctype/opportunity_lost_reason/opportunity_lost_reason.json
@@ -3,7 +3,7 @@
  "allow_events_in_timeline": 0, 
  "allow_guest_to_view": 0, 
  "allow_import": 0, 
- "allow_rename": 0, 
+ "allow_rename": 1, 
  "autoname": "field:lost_reason", 
  "beta": 0, 
  "creation": "2018-12-28 14:48:51.044975", 
@@ -57,7 +57,7 @@
  "issingle": 0, 
  "istable": 0, 
  "max_attachments": 0, 
- "modified": "2018-12-28 14:49:43.336437", 
+ "modified": "2022-02-16 10:49:43.336437", 
  "modified_by": "Administrator", 
  "module": "CRM", 
  "name": "Opportunity Lost Reason", 
@@ -150,4 +150,4 @@
  "track_changes": 0, 
  "track_seen": 0, 
  "track_views": 0
-}
\ No newline at end of file
+}
diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js
index 67018e1..8721a5b 100644
--- a/erpnext/crm/doctype/prospect/prospect.js
+++ b/erpnext/crm/doctype/prospect/prospect.js
@@ -3,6 +3,8 @@
 
 frappe.ui.form.on('Prospect', {
 	refresh (frm) {
+		frappe.dynamic_link = { doc: frm.doc, fieldname: "name", doctype: frm.doctype };
+
 		if (!frm.is_new() && frappe.boot.user.can_create.includes("Customer")) {
 			frm.add_custom_button(__("Customer"), function() {
 				frappe.model.open_mapped_doc({
diff --git a/erpnext/crm/module_onboarding/crm/crm.json b/erpnext/crm/module_onboarding/crm/crm.json
index 8315218..0faad1d 100644
--- a/erpnext/crm/module_onboarding/crm/crm.json
+++ b/erpnext/crm/module_onboarding/crm/crm.json
@@ -16,7 +16,7 @@
  "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/CRM",
  "idx": 0,
  "is_complete": 0,
- "modified": "2020-07-08 14:05:42.644448",
+ "modified": "2022-01-29 20:14:29.502145",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "CRM",
@@ -33,6 +33,9 @@
   },
   {
    "step": "Create and Send Quotation"
+  },
+  {
+   "step": "CRM Settings"
   }
  ],
  "subtitle": "Lead, Opportunity, Customer, and more.",
diff --git a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
index 78f7e4d..f0f50de 100644
--- a/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_quotation.json
+++ b/erpnext/crm/onboarding_step/create_and_send_quotation/create_and_send_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-05-28 21:07:11.461172",
@@ -13,6 +12,7 @@
  "name": "Create and Send Quotation",
  "owner": "Administrator",
  "reference_document": "Quotation",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create and Send Quotation",
  "validate_action": 1
diff --git a/erpnext/crm/onboarding_step/create_lead/create_lead.json b/erpnext/crm/onboarding_step/create_lead/create_lead.json
index c45e8b0..cb5cce6 100644
--- a/erpnext/crm/onboarding_step/create_lead/create_lead.json
+++ b/erpnext/crm/onboarding_step/create_lead/create_lead.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-28 21:07:01.373403",
@@ -13,6 +12,7 @@
  "name": "Create Lead",
  "owner": "Administrator",
  "reference_document": "Lead",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create Lead",
  "validate_action": 1
diff --git a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
index 0ee9317..96e0256 100644
--- a/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
+++ b/erpnext/crm/onboarding_step/create_opportunity/create_opportunity.json
@@ -5,7 +5,6 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
  "modified": "2021-01-21 15:28:52.483839",
@@ -13,6 +12,7 @@
  "name": "Create Opportunity",
  "owner": "Administrator",
  "reference_document": "Opportunity",
+ "show_form_tour": 0,
  "show_full_form": 1,
  "title": "Create Opportunity",
  "validate_action": 1
diff --git a/erpnext/crm/onboarding_step/crm_settings/crm_settings.json b/erpnext/crm/onboarding_step/crm_settings/crm_settings.json
new file mode 100644
index 0000000..555d795
--- /dev/null
+++ b/erpnext/crm/onboarding_step/crm_settings/crm_settings.json
@@ -0,0 +1,21 @@
+{
+ "action": "Go to Page",
+ "creation": "2022-01-29 20:14:24.803844",
+ "description": "# CRM Settings\n\nCRM module\u2019s features are configurable as per your business needs. CRM Settings is the place where you can set your preferences for:\n- Campaign\n- Lead\n- Opportunity\n- Quotation",
+ "docstatus": 0,
+ "doctype": "Onboarding Step",
+ "idx": 0,
+ "is_complete": 0,
+ "is_single": 1,
+ "is_skipped": 0,
+ "modified": "2022-01-29 20:14:24.803844",
+ "modified_by": "Administrator",
+ "name": "CRM Settings",
+ "owner": "Administrator",
+ "path": "#crm-settings/CRM%20Settings",
+ "reference_document": "CRM Settings",
+ "show_form_tour": 0,
+ "show_full_form": 0,
+ "title": "CRM Settings",
+ "validate_action": 1
+}
\ No newline at end of file
diff --git a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
index fa26921a..8871753 100644
--- a/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
+++ b/erpnext/crm/onboarding_step/introduction_to_crm/introduction_to_crm.json
@@ -5,13 +5,13 @@
  "doctype": "Onboarding Step",
  "idx": 0,
  "is_complete": 0,
- "is_mandatory": 0,
  "is_single": 0,
  "is_skipped": 0,
  "modified": "2020-05-14 17:28:16.448676",
  "modified_by": "Administrator",
  "name": "Introduction to CRM",
  "owner": "Administrator",
+ "show_form_tour": 0,
  "show_full_form": 0,
  "title": "Introduction to CRM",
  "validate_action": 1,
diff --git a/erpnext/demo/data/account.json b/erpnext/demo/data/account.json
deleted file mode 100644
index b50b0c9..0000000
--- a/erpnext/demo/data/account.json
+++ /dev/null
@@ -1,18 +0,0 @@
-[{
-  "account_name": "Debtors EUR",
-  "parent_account": "Accounts Receivable",
-  "account_type": "Receivable",
-  "account_currency": "EUR"
-},
-{
-  "account_name": "Creditors EUR",
-  "parent_account": "Accounts Payable",
-  "account_type": "Payable",
-  "account_currency": "EUR"
-},
-{
-  "account_name": "Paypal",
-  "parent_account": "Bank Accounts",
-  "account_type": "Bank",
-  "account_currency": "EUR"
-}]
\ No newline at end of file
diff --git a/erpnext/demo/data/address.json b/erpnext/demo/data/address.json
deleted file mode 100644
index 7618c2c..0000000
--- a/erpnext/demo/data/address.json
+++ /dev/null
@@ -1,218 +0,0 @@
-[
- {
-  "address_line1": "254 Theotokopoulou Str.",
-  "address_type": "Office",
-  "city": "Larnaka",
-  "country": "Cyprus",
-  "links": [{"link_doctype": "Customer", "link_name": "Adaptas"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "R Patr\u00e3o Caramelho 116",
-  "address_type": "Office",
-  "city": "Fajozes",
-  "country": "Portugal",
-  "links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "30 Fulford Road",
-  "address_type": "Office",
-  "city": "PENTRE-PIOD",
-  "country": "United Kingdom",
-  "links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Schoenebergerstrasse 13",
-  "address_type": "Office",
-  "city": "Raschau",
-  "country": "Germany",
-  "links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Hoheluftchaussee 43",
-  "address_type": "Office",
-  "city": "Kieritzsch",
-  "country": "Germany",
-  "links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "R Cimo Vila 6",
-  "address_type": "Office",
-  "city": "Rebordosa",
-  "country": "Portugal",
-  "links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "R 5 Outubro 9",
-  "address_type": "Office",
-  "city": "Quinta Nova S\u00e3o Domingos",
-  "country": "Portugal",
-  "links": [{"link_doctype": "Customer", "link_name": "Choices"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Avenida Macambira 953",
-  "address_type": "Office",
-  "city": "Goi\u00e2nia",
-  "country": "Brazil",
-  "links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "2342 Goyeau Ave",
-  "address_type": "Office",
-  "city": "Windsor",
-  "country": "Canada",
-  "links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Laukaantie 82",
-  "address_type": "Office",
-  "city": "KOKKOLA",
-  "country": "Finland",
-  "links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "9 Brown Street",
-  "address_type": "Office",
-  "city": "PETERSHAM",
-  "country": "Australia",
-  "links": [{"link_doctype": "Customer", "link_name": "Fayva"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Via Donnalbina 41",
-  "address_type": "Office",
-  "city": "Cala Gonone",
-  "country": "Italy",
-  "links": [{"link_doctype": "Customer", "link_name": "Intelacard"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Liljerum Grenadj\u00e4rtorpet 69",
-  "address_type": "Office",
-  "city": "TOMTEBODA",
-  "country": "Sweden",
-  "links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "72 Bishopgate Street",
-  "address_type": "Office",
-  "city": "SEAHAM",
-  "country": "United Kingdom",
-  "links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "\u03a3\u03ba\u03b1\u03c6\u03af\u03b4\u03b9\u03b1 105",
-  "address_type": "Office",
-  "city": "\u03a0\u0391\u03a1\u0395\u039a\u039a\u039b\u0397\u03a3\u0399\u0391",
-  "country": "Cyprus",
-  "links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Mellemvej 7",
-  "address_type": "Office",
-  "city": "Aabybro",
-  "country": "Denmark",
-  "links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "Plougg\u00e5rdsvej 98",
-  "address_type": "Office",
-  "city": "Karby",
-  "country": "Denmark",
-  "links": [{"link_doctype": "Customer", "link_name": "Netobill"}],
-  "phone": "23566775757"
- },
- {
-  "address_line1": "176 Michalakopoulou Street",
-  "address_type": "Office",
-  "city": "Agio Georgoudi",
-  "country": "Cyprus",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}]
- },
- {
-  "address_line1": "Fibichova 1102",
-  "address_type": "Office",
-  "city": "Kokor\u00edn",
-  "country": "Czech Republic",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}]
- },
- {
-  "address_line1": "Zahradn\u00ed 888",
-  "address_type": "Office",
-  "city": "Cecht\u00edn",
-  "country": "Czech Republic",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}]
- },
- {
-  "address_line1": "ul. Grochowska 94",
-  "address_type": "Office",
-  "city": "Warszawa",
-  "country": "Poland",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}]
- },
- {
-  "address_line1": "Norra Esplanaden 87",
-  "address_type": "Office",
-  "city": "HELSINKI",
-  "country": "Finland",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}]
- },
- {
-  "address_line1": "2038 Fallon Drive",
-  "address_type": "Office",
-  "city": "Dresden",
-  "country": "Canada",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}]
- },
- {
-  "address_line1": "77 cours Franklin Roosevelt",
-  "address_type": "Office",
-  "city": "MARSEILLE",
-  "country": "France",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}]
- },
- {
-  "address_line1": "ul. Tuwima Juliana 85",
-  "address_type": "Office",
-  "city": "\u0141\u00f3d\u017a",
-  "country": "Poland",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}]
- },
- {
-  "address_line1": "Gl. Sygehusvej 41",
-  "address_type": "Office",
-  "city": "Narsaq",
-  "country": "Greenland",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}]
- },
- {
-  "address_line1": "Gosposka ulica 50",
-  "address_type": "Office",
-  "city": "Nova Gorica",
-  "country": "Slovenia",
-  "phone": "23566775757",
-  "links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/assessment_criteria.json b/erpnext/demo/data/assessment_criteria.json
deleted file mode 100644
index 8295682..0000000
--- a/erpnext/demo/data/assessment_criteria.json
+++ /dev/null
@@ -1,18 +0,0 @@
-[
-	{
-		"doctype": "Assessment Criteria",
-		"assessment_criteria": "Aptitude"
-	},
-	{
-		"doctype": "Assessment Criteria",
-		"assessment_criteria": "Application"
-	},
-	{
-		"doctype": "Assessment Criteria",
-		"assessment_criteria": "Understanding"
-	},
-	{
-		"doctype": "Assessment Criteria",
-		"assessment_criteria": "Knowledge"
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/asset.json b/erpnext/demo/data/asset.json
deleted file mode 100644
index 44db2ae..0000000
--- a/erpnext/demo/data/asset.json
+++ /dev/null
@@ -1,58 +0,0 @@
-[
-	{
-		"asset_name": "Macbook Pro - 1",
-		"item_code": "Computer",
-		"gross_purchase_amount": 100000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2017-01-02",
-		"location": "Main Location"
-	},
-	{
-		"asset_name": "Macbook Air - 1",
-		"item_code": "Computer",
-		"gross_purchase_amount": 60000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2017-10-02",
-		"location": "Avg Location"
-	},
-	{
-		"asset_name": "Conferrence Table",
-		"item_code": "Table",
-		"gross_purchase_amount": 30000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2018-10-02",
-		"location": "Zany Location"
-	},
-	{
-		"asset_name": "Lunch Table",
-		"item_code": "Table",
-		"gross_purchase_amount": 20000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2018-06-02",
-		"location": "Fletcher Location"
-	},
-	{
-		"asset_name": "ERPNext",
-		"item_code": "ERP",
-		"gross_purchase_amount": 100000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2018-09-02",
-		"location":"Main Location"
-	},
-	{
-		"asset_name": "Chair 1",
-		"item_code": "Chair",
-		"gross_purchase_amount": 10000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2018-07-02",
-		"location": "Zany Location"
-	},
-	{
-		"asset_name": "Chair 2",
-		"item_code": "Chair",
-		"gross_purchase_amount": 10000,
-		"asset_owner": "Company",
-		"available_for_use_date": "2018-07-02",
-		"location": "Avg Location"
-	}
-]
diff --git a/erpnext/demo/data/asset_category.json b/erpnext/demo/data/asset_category.json
deleted file mode 100644
index 54f779d..0000000
--- a/erpnext/demo/data/asset_category.json
+++ /dev/null
@@ -1,38 +0,0 @@
-[
-	{
-		"asset_category_name": "Furnitures",
-		"depreciation_method": "Straight Line",
-		"total_number_of_depreciations": 5,
-		"frequency_of_depreciation": 12, 
-		"accounts": [{
-			"company_name": "Wind Power LLC",
-			"fixed_asset_account": "Furnitures and Fixtures - WPL",
-			"accumulated_depreciation_account": "Accumulated Depreciation - WPL",
-			"depreciation_expense_account": "Depreciation - WPL"
-		}]
-	},
-	{
-		"asset_category_name": "Electronic Equipments",
-		"depreciation_method": "Double Declining Balance",
-		"total_number_of_depreciations": 10,
-		"frequency_of_depreciation": 6, 
-		"accounts": [{
-			"company_name": "Wind Power LLC",
-			"fixed_asset_account": "Electronic Equipments - WPL",
-			"accumulated_depreciation_account": "Accumulated Depreciation - WPL",
-			"depreciation_expense_account": "Depreciation - WPL"
-		}]
-	},
-	{
-		"asset_category_name": "Softwares",
-		"depreciation_method": "Straight Line",
-		"total_number_of_depreciations": 10,
-		"frequency_of_depreciation": 12, 
-		"accounts": [{
-			"company_name": "Wind Power LLC",
-			"fixed_asset_account": "Softwares - WPL",
-			"accumulated_depreciation_account": "Accumulated Depreciation - WPL",
-			"depreciation_expense_account": "Depreciation - WPL"
-		}]
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/bom.json b/erpnext/demo/data/bom.json
deleted file mode 100644
index 3085435..0000000
--- a/erpnext/demo/data/bom.json
+++ /dev/null
@@ -1,180 +0,0 @@
-[
- {
-  "item": "Bearing Assembly",
-  "items": [
-   {
-    "item_code": "Base Bearing Plate",
-    "qty": 1.0,
-    "rate": 15.0
-   },
-   {
-    "item_code": "Bearing Block",
-    "qty": 1.0,
-    "rate": 10.0
-   },
-   {
-    "item_code": "Bearing Collar",
-    "qty": 2.0,
-    "rate": 20.0
-   },
-   {
-    "item_code": "Bearing Pipe",
-    "qty": 1.0,
-    "rate": 15.0
-   },
-   {
-    "item_code": "Upper Bearing Plate",
-    "qty": 1.0,
-    "rate": 50.0
-   }
-  ]
- },
- {
-  "item": "Wind Mill A Series",
-  "items": [
-   {
-    "item_code": "Base Bearing Plate",
-    "qty": 1.0,
-    "rate": 15.0
-   },
-   {
-    "item_code": "Base Plate",
-    "qty": 1.0,
-    "rate": 20.0
-   },
-   {
-    "item_code": "Bearing Block",
-    "qty": 1.0,
-    "rate": 10.0
-   },
-   {
-    "item_code": "Bearing Pipe",
-    "qty": 1.0,
-    "rate": 15.0
-   },
-   {
-    "item_code": "External Disc",
-    "qty": 1.0,
-    "rate": 45.0
-   },
-   {
-    "item_code": "Shaft",
-    "qty": 1.0,
-    "rate": 30.0
-   },
-   {
-    "item_code": "Wing Sheet",
-    "qty": 4.0,
-    "rate": 22.0
-   }
-  ]
- },
- {
-  "item": "Wind MIll C Series",
-  "items": [
-   {
-    "item_code": "Base Plate",
-    "qty": 2.0,
-    "rate": 20.0
-   },
-   {
-    "item_code": "Internal Disc",
-    "qty": 1.0,
-    "rate": 33.0
-   },
-   {
-    "item_code": "External Disc",
-    "qty": 1.0,
-    "rate": 45.0
-   },
-   {
-    "item_code": "Bearing Assembly",
-    "qty": 1.0,
-    "rate": 130.0
-   },
-   {
-    "item_code": "Wing Sheet",
-    "qty": 3.0,
-    "rate": 22.0
-   }
-  ]
- },
- {
-  "item": "Wind Turbine-S",
-  "with_operations": 1,
-  "operations": [
-   {
-    "operation": "Prepare Frame",
-    "time_in_mins": 30.0,
-    "workstation": "Drilling Machine 1"
-   },
-   {
-    "operation": "Setup Fixtures",
-    "time_in_mins": 15.0,
-    "workstation": "Assembly Station 1"
-   },
-   {
-    "operation": "Assembly Operation",
-    "time_in_mins": 30.0,
-    "workstation": "Assembly Station 1"
-   },
-   {
-    "operation": "Wiring",
-    "time_in_mins": 20.0,
-    "workstation": "Assembly Station 1"
-   },
-   {
-    "operation": "Testing",
-    "time_in_mins": 10.0,
-    "workstation": "Packing and Testing Station"
-   },
-   {
-    "operation": "Packing",
-    "time_in_mins": 25.0,
-    "workstation": "Packing and Testing Station"
-   }
-  ],
-  "items": [
-   {
-    "item_code": "Base Bearing Plate",
-    "qty": 1.0,
-    "rate": 15.0
-   },
-   {
-    "item_code": "Base Plate",
-    "qty": 1.0,
-    "rate": 20.0
-   },
-   {
-    "item_code": "Bearing Collar",
-    "qty": 1.0,
-    "rate": 20.0
-   },
-   {
-    "item_code": "Blade Rib",
-    "qty": 1.0,
-    "rate": 10.0
-   },
-   {
-    "item_code": "Shaft",
-    "qty": 1.0,
-    "rate": 30.0
-   },
-   {
-    "item_code": "Wing Sheet",
-    "qty": 2.0,
-    "rate": 22.0
-   }
-  ]
- },
- {
-  "item": "Base Plate",
-  "items": [
-   {
-    "item_code": "Base Plate Un Painted",
-    "qty": 1.0,
-    "rate": 16.0
-   }
-  ]
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/contact.json b/erpnext/demo/data/contact.json
deleted file mode 100644
index 113b561..0000000
--- a/erpnext/demo/data/contact.json
+++ /dev/null
@@ -1,164 +0,0 @@
-[
- {
-  "email_id": "JanVaclavik@example.com",
-  "first_name": "January",
-  "last_name": "V\u00e1clav\u00edk",
-  "links": [{"link_doctype": "Customer", "link_name": "Adaptas"}]
- },
- {
-  "email_id": "ChidumagaTobeolisa@example.com",
-  "first_name": "Chidumaga",
-  "last_name": "Tobeolisa",
-  "links": [{"link_doctype": "Customer", "link_name": "Asian Fusion"}]
- },
- {
-  "email_id": "JanaKubanova@example.com",
-  "first_name": "Jana",
-  "last_name": "Kub\u00e1\u0148ov\u00e1",
-  "links": [{"link_doctype": "Customer", "link_name": "Asian Junction"}]
- },
- {
-  "email_id": "XuChaoXuan@example.com",
-  "first_name": "\u7d39\u8431",
-  "last_name": "\u4e8e",
-  "links": [{"link_doctype": "Customer", "link_name": "Big D Supermarkets"}]
- },
- {
-  "email_id": "OzlemVerwijmeren@example.com",
-  "first_name": "\u00d6zlem",
-  "last_name": "Verwijmeren",
-  "links": [{"link_doctype": "Customer", "link_name": "Buttrey Food & Drug"}]
- },
- {
-  "email_id": "HansRasmussen@example.com",
-  "first_name": "Hans",
-  "last_name": "Rasmussen",
-  "links": [{"link_doctype": "Customer", "link_name": "Chi-Chis"}]
- },
- {
-  "email_id": "SatomiShigeki@example.com",
-  "first_name": "Satomi",
-  "last_name": "Shigeki",
-  "links": [{"link_doctype": "Customer", "link_name": "Choices"}]
- },
- {
-  "email_id": "SimonVJessen@example.com",
-  "first_name": "Simon",
-  "last_name": "Jessen",
-  "links": [{"link_doctype": "Customer", "link_name": "Consumers and Consumers Express"}]
- },
- {
-  "email_id": "NeguaranShahsaah@example.com",
-  "first_name": "\u0646\u06af\u0627\u0631\u06cc\u0646",
-  "last_name": "\u0634\u0627\u0647 \u0633\u06cc\u0627\u0647",
-  "links": [{"link_doctype": "Customer", "link_name": "Crafts Canada"}]
- },
- {
-  "email_id": "Lom-AliBataev@example.com",
-  "first_name": "Lom-Ali",
-  "last_name": "Bataev",
-  "links": [{"link_doctype": "Customer", "link_name": "Endicott Shoes"}]
- },
- {
-  "email_id": "VanNgocTien@example.com",
-  "first_name": "Ti\u00ean",
-  "last_name": "V\u0103n",
-  "links": [{"link_doctype": "Customer", "link_name": "Fayva"}]
- },
- {
-  "email_id": "QuimeyOsorioRuelas@example.com",
-  "first_name": "Quimey",
-  "last_name": "Osorio",
-  "links": [{"link_doctype": "Customer", "link_name": "Intelacard"}]
- },
- {
-  "email_id": "EdgardaSalcedoRaya@example.com",
-  "first_name": "Edgarda",
-  "last_name": "Salcedo",
-  "links": [{"link_doctype": "Customer", "link_name": "Landskip Yard Care"}]
- },
- {
-  "email_id": "HafsteinnBjarnarsonar@example.com",
-  "first_name": "Hafsteinn",
-  "last_name": "Bjarnarsonar",
-  "links": [{"link_doctype": "Customer", "link_name": "Life Plan Counselling"}]
- },
- {
-  "email_id": "\u0434\u0430\u043d\u0438\u0438\u043b@example.com",
-  "first_name": "\u0414\u0430\u043d\u0438\u0438\u043b",
-  "last_name": "\u041a\u043e\u043d\u043e\u0432\u0430\u043b\u043e\u0432",
-  "links": [{"link_doctype": "Customer", "link_name": "Mr Fables"}]
- },
- {
-  "email_id": "SelmaMAndersen@example.com",
-  "first_name": "Selma",
-  "last_name": "Andersen",
-  "links": [{"link_doctype": "Customer", "link_name": "Nelson Brothers"}]
- },
- {
-  "email_id": "LadislavKolaja@example.com",
-  "first_name": "Ladislav",
-  "last_name": "Kolaja",
-  "links": [{"link_doctype": "Customer", "link_name": "Netobill"}]
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Helios Air"}],
-  "email_id": "TewoldeAbaalom@example.com",
-  "first_name": "Tewolde",
-  "last_name": "Abaalom"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Ks Merchandise"}],
-  "email_id": "LeilaFernandesRodrigues@example.com",
-  "first_name": "Leila",
-  "last_name": "Rodrigues"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "HomeBase"}],
-  "email_id": "DmitryBulgakov@example.com",
-  "first_name": "Dmitry",
-  "last_name": "Bulgakov"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Scott Ties"}],
-  "email_id": "HaiducWhitfoot@example.com",
-  "first_name": "Haiduc",
-  "last_name": "Whitfoot"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Reliable Investments"}],
-  "email_id": "SesseljaPetursdottir@example.com",
-  "first_name": "Sesselja",
-  "last_name": "P\u00e9tursd\u00f3ttir"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Nan Duskin"}],
-  "email_id": "HajdarPignar@example.com",
-  "first_name": "Hajdar",
-  "last_name": "Pignar"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Rainbow Records"}],
-  "email_id": "GustavaLorenzo@example.com",
-  "first_name": "Gustava",
-  "last_name": "Lorenzo"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "New World Realty"}],
-  "email_id": "BethanyWood@example.com",
-  "first_name": "Bethany",
-  "last_name": "Wood"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Asiatic Solutions"}],
-  "email_id": "GlorianaBrownlock@example.com",
-  "first_name": "Gloriana",
-  "last_name": "Brownlock"
- },
- {
-  "links": [{"link_doctype": "Supplier", "link_name": "Eagle Hardware"}],
-  "email_id": "JensonFraser@gustr.com",
-  "first_name": "Jenson",
-  "last_name": "Fraser"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/course.json b/erpnext/demo/data/course.json
deleted file mode 100644
index 15728d5..0000000
--- a/erpnext/demo/data/course.json
+++ /dev/null
@@ -1,134 +0,0 @@
-[
-	{
-		"doctype": "Course",
-		"course_name": "Communication Skiils",
-		"course_code": "BCA2040",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Object Oriented Programing - C++",
-		"course_code": "BCA2030",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Data Structures and Algorithm",
-		"course_code": "BCA2020",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Operating System",
-		"course_code": "BCA2010",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Digital Logic",
-		"course_code": "BCA1040",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Basic Mathematics",
-		"course_code": "BCA1030",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Programing in C",
-		"course_code": "BCA1020",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Fundamentals of IT & Programing",
-		"course_code": "BCA1010",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Microprocessor",
-		"course_code": "MCA4010",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Probability and Statistics",
-		"course_code": "MCA4020",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Programing in Java",
-		"course_code": "MCA4030",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Communication Skills",
-		"course_code": "BBA 101",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Organizational Behavior",
-		"course_code": "BBA 102",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Business Environment",
-		"course_code": "BBA 103",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Legal and Regulatory Framework",
-		"course_code": "BBA 301",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Human Resource Management",
-		"course_code": "BBA 302",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Advertising and Sales",
-		"course_code": "BBA 304",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Entrepreneurship Management",
-		"course_code": "BBA 505",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Visual Merchandising",
-		"course_code": "BBR 504",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Warehouse Management",
-		"course_code": "BBR 505",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Store Operations and Job Knowledge",
-		"course_code": "BBR 501",
-		"department": "Management Studies"
-	},
-	{
-		"doctype": "Course",
-		"course_name": "Management Development and Skills",
-		"course_code": "BBA 602",
-		"department": "Management Studies"
-	}
-]
diff --git a/erpnext/demo/data/department.json b/erpnext/demo/data/department.json
deleted file mode 100644
index f4355ba..0000000
--- a/erpnext/demo/data/department.json
+++ /dev/null
@@ -1,30 +0,0 @@
-[
-	{
-		"doctype": "Department", 
-		"department_name": "Information Technology"
-	},
-	{
-		"doctype": "Department",
-		"department_name": "Physics"
-	},
-	{
-		"doctype": "Department",
-		"department_name": "Chemistry"
-	},
-	{
-		"doctype": "Department",
-		"department_name": "Biology"
-	},
-	{
-		"doctype": "Department",
-		"department_name": "Commerce"
-	},
-	{
-		"doctype": "Department",
-		"department_name": "English"
-	},
-	{
-		"doctype": "Department",
-		"department_name": "Management Studies"
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/drug_list.json b/erpnext/demo/data/drug_list.json
deleted file mode 100644
index 3069042..0000000
--- a/erpnext/demo/data/drug_list.json
+++ /dev/null
@@ -1,5111 +0,0 @@
-[
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Atocopherol",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Atocopherol",
-  "item_group": "Drug",
-  "item_name": "Atocopherol",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-
-
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.577151",
-  "name": "Atocopherol",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Abacavir",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Abacavir",
-  "item_group": "Drug",
-  "item_name": "Abacavir",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-
-
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.678257",
-  "name": "Abacavir",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Abciximab",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Abciximab",
-  "item_group": "Drug",
-  "item_name": "Abciximab",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.695413",
-  "name": "Abciximab",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Acacia",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Acacia",
-  "item_group": "Drug",
-  "item_name": "Acacia",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.797774",
-  "name": "Acacia",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Acamprosate",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Acamprosate",
-  "item_group": "Drug",
-  "item_name": "Acamprosate",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.826952",
-  "name": "Acamprosate",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Acarbose",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Acarbose",
-  "item_group": "Drug",
-  "item_name": "Acarbose",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.843890",
-  "name": "Acarbose",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Acebrofylline",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Acebrofylline",
-  "item_group": "Drug",
-  "item_name": "Acebrofylline",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.969984",
-  "name": "Acebrofylline",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Acebrofylline (SR)",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Acebrofylline (SR)",
-  "item_group": "Drug",
-  "item_name": "Acebrofylline (SR)",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:16.987354",
-  "name": "Acebrofylline (SR)",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Aceclofenac",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Aceclofenac",
-  "item_group": "Drug",
-  "item_name": "Aceclofenac",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.004369",
-  "name": "Aceclofenac",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Ash",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Ash",
-  "item_group": "Drug",
-  "item_name": "Ash",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.021192",
-  "name": "Ash",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Asparaginase",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Asparaginase",
-  "item_group": "Drug",
-  "item_name": "Asparaginase",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.038058",
-  "name": "Asparaginase",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Aspartame",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Aspartame",
-  "item_group": "Drug",
-  "item_name": "Aspartame",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.054463",
-  "name": "Aspartame",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Aspartic Acid",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Aspartic Acid",
-  "item_group": "Drug",
-  "item_name": "Aspartic Acid",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.071001",
-  "name": "Aspartic Acid",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Bleomycin",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Bleomycin",
-  "item_group": "Drug",
-  "item_name": "Bleomycin",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.087170",
-  "name": "Bleomycin",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Bleomycin Sulphate",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Bleomycin Sulphate",
-  "item_group": "Drug",
-  "item_name": "Bleomycin Sulphate",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.103691",
-  "name": "Bleomycin Sulphate",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Blue cap contains",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Blue cap contains",
-  "item_group": "Drug",
-  "item_name": "Blue cap contains",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.120040",
-  "name": "Blue cap contains",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Boran",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Boran",
-  "item_group": "Drug",
-  "item_name": "Boran",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.135964",
-  "name": "Boran",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Borax",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Borax",
-  "item_group": "Drug",
-  "item_name": "Borax",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.152575",
-  "name": "Borax",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorbutanol",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorbutanol",
-  "item_group": "Drug",
-  "item_name": "Chlorbutanol",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.168998",
-  "name": "Chlorbutanol",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorbutol",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorbutol",
-  "item_group": "Drug",
-  "item_name": "Chlorbutol",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.185316",
-  "name": "Chlorbutol",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlordiazepoxide",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlordiazepoxide",
-  "item_group": "Drug",
-  "item_name": "Chlordiazepoxide",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.208361",
-  "name": "Chlordiazepoxide",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlordiazepoxide and Clidinium Bromide",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlordiazepoxide and Clidinium Bromide",
-  "item_group": "Drug",
-  "item_name": "Chlordiazepoxide and Clidinium Bromide",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.224341",
-  "name": "Chlordiazepoxide and Clidinium Bromide",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorhexidine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorhexidine",
-  "item_group": "Drug",
-  "item_name": "Chlorhexidine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.240634",
-  "name": "Chlorhexidine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorhexidine 40%",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorhexidine 40%",
-  "item_group": "Drug",
-  "item_name": "Chlorhexidine 40%",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.256922",
-  "name": "Chlorhexidine 40%",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorhexidine Acetate",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorhexidine Acetate",
-  "item_group": "Drug",
-  "item_name": "Chlorhexidine Acetate",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.274789",
-  "name": "Chlorhexidine Acetate",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorhexidine Gluconate",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorhexidine Gluconate",
-  "item_group": "Drug",
-  "item_name": "Chlorhexidine Gluconate",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.295371",
-  "name": "Chlorhexidine Gluconate",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorhexidine HCL",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorhexidine HCL",
-  "item_group": "Drug",
-  "item_name": "Chlorhexidine HCL",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.312916",
-  "name": "Chlorhexidine HCL",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chlorhexidine Hydrochloride",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chlorhexidine Hydrochloride",
-  "item_group": "Drug",
-  "item_name": "Chlorhexidine Hydrochloride",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.329570",
-  "name": "Chlorhexidine Hydrochloride",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Chloride",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Chloride",
-  "item_group": "Drug",
-  "item_name": "Chloride",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.346088",
-  "name": "Chloride",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Fosfomycin Tromethamine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Fosfomycin Tromethamine",
-  "item_group": "Drug",
-  "item_name": "Fosfomycin Tromethamine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.362777",
-  "name": "Fosfomycin Tromethamine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Fosinopril",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Fosinopril",
-  "item_group": "Drug",
-  "item_name": "Fosinopril",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.379465",
-  "name": "Fosinopril",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Iodochlorhydroxyquinoline",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Iodochlorhydroxyquinoline",
-  "item_group": "Drug",
-  "item_name": "Iodochlorhydroxyquinoline",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.396068",
-  "name": "Iodochlorhydroxyquinoline",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Iodochlorohydroxyquinoline",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Iodochlorohydroxyquinoline",
-  "item_group": "Drug",
-  "item_name": "Iodochlorohydroxyquinoline",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.412734",
-  "name": "Iodochlorohydroxyquinoline",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Ipratropium",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Ipratropium",
-  "item_group": "Drug",
-  "item_name": "Ipratropium",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.429333",
-  "name": "Ipratropium",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Mebeverine hydrochloride",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Mebeverine hydrochloride",
-  "item_group": "Drug",
-  "item_name": "Mebeverine hydrochloride",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.445814",
-  "name": "Mebeverine hydrochloride",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Mecetronium ethylsulphate",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Mecetronium ethylsulphate",
-  "item_group": "Drug",
-  "item_name": "Mecetronium ethylsulphate",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.461696",
-  "name": "Mecetronium ethylsulphate",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Meclizine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Meclizine",
-  "item_group": "Drug",
-  "item_name": "Meclizine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.478020",
-  "name": "Meclizine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Oxaprozin",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Oxaprozin",
-  "item_group": "Drug",
-  "item_name": "Oxaprozin",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-
-
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.496221",
-  "name": "Oxaprozin",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Oxazepam",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Oxazepam",
-  "item_group": "Drug",
-  "item_name": "Oxazepam",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.511933",
-  "name": "Oxazepam",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Oxcarbazepine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Oxcarbazepine",
-  "item_group": "Drug",
-  "item_name": "Oxcarbazepine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.528472",
-  "name": "Oxcarbazepine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Oxetacaine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Oxetacaine",
-  "item_group": "Drug",
-  "item_name": "Oxetacaine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.544177",
-  "name": "Oxetacaine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Oxethazaine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Oxethazaine",
-  "item_group": "Drug",
-  "item_name": "Oxethazaine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.560193",
-  "name": "Oxethazaine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Suxamethonium Chloride",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Suxamethonium Chloride",
-  "item_group": "Drug",
-  "item_name": "Suxamethonium Chloride",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.576447",
-  "name": "Suxamethonium Chloride",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Tacrolimus",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Tacrolimus",
-  "item_group": "Drug",
-  "item_name": "Tacrolimus",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.593481",
-  "name": "Tacrolimus",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Ubiquinol",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Ubiquinol",
-  "item_group": "Drug",
-  "item_name": "Ubiquinol",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.609930",
-  "name": "Ubiquinol",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin B12",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin B12",
-  "item_group": "Drug",
-  "item_name": "Vitamin B12",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.626225",
-  "name": "Vitamin B12",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin B1Hydrochloride",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin B1Hydrochloride",
-  "item_group": "Drug",
-  "item_name": "Vitamin B1Hydrochloride",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.642423",
-  "name": "Vitamin B1Hydrochloride",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin B1Monohydrate",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin B1Monohydrate",
-  "item_group": "Drug",
-  "item_name": "Vitamin B1Monohydrate",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.658946",
-  "name": "Vitamin B1Monohydrate",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin B2",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin B2",
-  "item_group": "Drug",
-  "item_name": "Vitamin B2",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.675234",
-  "name": "Vitamin B2",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin B3",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin B3",
-  "item_group": "Drug",
-  "item_name": "Vitamin B3",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.691598",
-  "name": "Vitamin B3",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin D4",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin D4",
-  "item_group": "Drug",
-  "item_name": "Vitamin D4",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.707840",
-  "name": "Vitamin D4",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Vitamin E",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Vitamin E",
-  "item_group": "Drug",
-  "item_name": "Vitamin E",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.723859",
-  "name": "Vitamin E",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Wheat Germ Oil",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Wheat Germ Oil",
-  "item_group": "Drug",
-  "item_name": "Wheat Germ Oil",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.739829",
-  "name": "Wheat Germ Oil",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Wheatgrass extr",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Wheatgrass extr",
-  "item_group": "Drug",
-  "item_name": "Wheatgrass extr",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.757695",
-  "name": "Wheatgrass extr",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Whey Protein",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Whey Protein",
-  "item_group": "Drug",
-  "item_name": "Whey Protein",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.774098",
-  "name": "Whey Protein",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Xylometazoline",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Xylometazoline",
-  "item_group": "Drug",
-  "item_name": "Xylometazoline",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.790224",
-  "name": "Xylometazoline",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Xylometazoline Hydrochloride",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Xylometazoline Hydrochloride",
-  "item_group": "Drug",
-  "item_name": "Xylometazoline Hydrochloride",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.806359",
-  "name": "Xylometazoline Hydrochloride",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Yeast",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Yeast",
-  "item_group": "Drug",
-  "item_name": "Yeast",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.823305",
-  "name": "Yeast",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Yellow Fever Vaccine",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Yellow Fever Vaccine",
-  "item_group": "Drug",
-  "item_name": "Yellow Fever Vaccine",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.840250",
-  "name": "Yellow Fever Vaccine",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Zafirlukast",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Zafirlukast",
-  "item_group": "Drug",
-  "item_name": "Zafirlukast",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.856856",
-  "name": "Zafirlukast",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Zaleplon",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Zaleplon",
-  "item_group": "Drug",
-  "item_name": "Zaleplon",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.873287",
-  "name": "Zaleplon",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Zaltoprofen",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Zaltoprofen",
-  "item_group": "Drug",
-  "item_name": "Zaltoprofen",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.889263",
-  "name": "Zaltoprofen",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- },
- {
-  "asset_category": null,
-  "attributes": [],
-  "barcode": null,
-  "brand": null,
-  "buying_cost_center": null,
-  "country_of_origin": null,
-  "create_new_batch": 0,
-  "customer_code": "",
-  "customer_items": [],
-  "customs_tariff_number": null,
-  "default_bom": null,
-  "default_material_request_type": null,
-  "default_supplier": null,
-  "default_warehouse": null,
-  "delivered_by_supplier": 0,
-  "description": "Zanamivir",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Item",
-  "end_of_life": null,
-  "expense_account": null,
-  "gst_hsn_code": null,
-  "has_batch_no": 0,
-  "has_serial_no": 0,
-  "has_variants": 0,
-  "image": null,
-  "income_account": null,
-  "inspection_required_before_delivery": 0,
-  "inspection_required_before_purchase": 0,
-  "is_fixed_asset": 0,
-  "is_purchase_item": 1,
-  "is_sales_item": 1,
-  "is_stock_item": 1,
-  "is_sub_contracted_item": 0,
-  "item_code": "Zanamivir",
-  "item_group": "Drug",
-  "item_name": "Zanamivir",
-  "last_purchase_rate": 0.0,
-  "lead_time_days": 0,
-  "max_discount": 0.0,
-  "min_order_qty": 0.0,
-  "modified": "2017-07-06 12:53:17.905022",
-  "name": "Zanamivir",
-  "naming_series": null,
-  "net_weight": 0.0,
-  "opening_stock": 0.0,
-  "quality_parameters": [],
-  "reorder_levels": [],
-  "route": null,
-  "safety_stock": 0.0,
-  "selling_cost_center": null,
-  "serial_no_series": null,
-  "show_in_website": 0,
-  "show_variant_in_website": 0,
-  "slideshow": null,
-  "standard_rate": 0.0,
-  "stock_uom": "Nos",
-  "supplier_items": [],
-  "taxes": [],
-  "thumbnail": null,
-  "tolerance": 0.0,
-  "uoms": [
-   {
-    "conversion_factor": 1.0,
-    "uom": "Nos"
-   }
-  ],
-  "valuation_method": null,
-  "valuation_rate": 0.0,
-  "variant_based_on": null,
-  "variant_of": null,
-  "warranty_period": null,
-  "web_long_description": null,
-  "website_image": null,
-  "website_item_groups": [],
-  "website_specifications": [],
-  "website_warehouse": null,
-  "weight_uom": null,
-  "weightage": 0
- }
-]
diff --git a/erpnext/demo/data/employee.json b/erpnext/demo/data/employee.json
deleted file mode 100644
index 2d2dbe8..0000000
--- a/erpnext/demo/data/employee.json
+++ /dev/null
@@ -1,92 +0,0 @@
-[
-	{
-		"date_of_birth": "1982-01-03",
-		"date_of_joining": "2001-10-10",
-		"employee_name": "Diana Prince",
-		"first_name": "Diana",
-		"last_name": "Prince",
-		"gender": "Female",
-		"user_id": "DianaPrince@example.com"
-	},
-	{
-		"date_of_birth": "1959-02-03",
-		"date_of_joining": "1976-09-16",
-		"employee_name": "Zatanna Zatara",
-		"gender": "Female",
-		"user_id": "ZatannaZatara@example.com",
-		"first_name": "Zatanna",
-		"last_name": "Zatara"
-	},
-	{
-		"date_of_birth": "1982-03-03",
-		"date_of_joining": "2000-06-16",
-		"employee_name": "Holly Granger",
-		"gender": "Female",
-		"user_id": "HollyGranger@example.com",
-		"first_name": "Holly",
-		"last_name": "Granger"
-	},
-	{
-		"date_of_birth": "1945-04-04",
-		"date_of_joining": "1969-07-01",
-		"employee_name": "Neptunia Aquaria",
-		"gender": "Female",
-		"user_id": "NeptuniaAquaria@example.com",
-		"first_name": "Neptunia",
-		"last_name": "Aquaria"
-	},
-	{
-		"date_of_birth": "1978-05-03",
-		"date_of_joining": "1999-12-24",
-		"employee_name": "Arthur Curry",
-		"gender": "Male",
-		"user_id": "ArthurCurry@example.com",
-		"first_name": "Arthur",
-		"last_name": "Curry"
-	},
-	{
-		"date_of_birth": "1964-06-03",
-		"date_of_joining": "1981-08-05",
-		"employee_name": "Thalia Al Ghul",
-		"gender": "Female",
-		"user_id": "ThaliaAlGhul@example.com",
-		"first_name": "Thalia",
-		"last_name": "Al Ghul"
-	},
-	{
-		"date_of_birth": "1982-07-03",
-		"date_of_joining": "2006-06-10",
-		"employee_name": "Maxwell Lord",
-		"gender": "Male",
-		"user_id": "MaxwellLord@example.com",
-		"first_name": "Maxwell",
-		"last_name": "Lord"
-	},
-	{
-		"date_of_birth": "1969-08-03",
-		"date_of_joining": "1993-10-21",
-		"employee_name": "Grace Choi",
-		"gender": "Female",
-		"user_id": "GraceChoi@example.com",
-		"first_name": "Grace",
-		"last_name": "Choi"
-	},
-	{
-		"date_of_birth": "1982-09-03",
-		"date_of_joining": "2005-09-06",
-		"employee_name": "Vandal Savage",
-		"gender": "Male",
-		"user_id": "VandalSavage@example.com",
-		"first_name": "Vandal",
-		"last_name": "Savage"
-	},
-	{
-		"date_of_birth": "1985-10-03",
-		"date_of_joining": "2007-12-25",
-		"employee_name": "Caitlin Snow",
-		"gender": "Female",
-		"user_id": "CaitlinSnow@example.com",
-		"first_name": "Caitlin",
-		"last_name": "Snow"
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/grading_scale.json b/erpnext/demo/data/grading_scale.json
deleted file mode 100644
index 0760919..0000000
--- a/erpnext/demo/data/grading_scale.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
-	{
-		"doctype": "Grading Scale",
-		"grading_scale_name": "Standard Grading",
-		"description": "Standard Grading Scale",
-		"intervals": [
-			{"threshold": 100.0, "grade_code": "A", "grade_description": "Excellent"},
-			{"threshold": 89.9, "grade_code": "B+", "grade_description": "Close to Excellence"},
-			{"threshold": 80.0, "grade_code": "B", "grade_description": "Good"},
-			{"threshold": 69.9, "grade_code": "C+", "grade_description": "Almost Good"},
-			{"threshold": 60.0, "grade_code": "C", "grade_description": "Average"},
-			{"threshold": 50.0, "grade_code": "D+", "grade_description": "Have to Work"},
-			{"threshold": 40.0, "grade_code": "D", "grade_description": "Not met Baseline Expectations"},
-			{"threshold": 0.0, "grade_code": "F", "grade_description": "Have to work a lot"}
-		]
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/instructor.json b/erpnext/demo/data/instructor.json
deleted file mode 100644
index a25d163..0000000
--- a/erpnext/demo/data/instructor.json
+++ /dev/null
@@ -1,128 +0,0 @@
-[
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Eddie Jessup",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "William Dyer",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Alastor Moody",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Charles Xavier",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Cuthbert Calculus",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Reed Richards",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Urban Chronotis",
-		"naming_series": "INS/",
-		"department": "Physics"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "River Song",
-		"naming_series": "INS/",
-		"department": "Physics"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Yana",
-		"naming_series": "INS/",
-		"department": "Physics"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Neil Lasrado",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Deepshi Garg",
-		"naming_series": "INS/",
-		"department": "Chemistry"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Shubham Saxena",
-		"naming_series": "INS/",
-		"department": "Physics"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Rushabh Mehta",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Umari Syed",
-		"naming_series": "INS/",
-		"department": "Chemistry"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Aman Singh",
-		"naming_series": "INS/",
-		"department": "Physics"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Nabin",
-		"naming_series": "INS/",
-		"department": "Chemistry"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Kanchan Chauhan",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Valmik Jangla",
-		"naming_series": "INS/",
-		"department": "Chemistry"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Amit Jain",
-		"naming_series": "INS/",
-		"department": "Physics"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Shreyas P",
-		"naming_series": "INS/",
-		"department": "Chemistry"
-	},
-	{
-		"doctype": "Instructor",
-		"instructor_name": "Rohit",
-		"naming_series": "INS/",
-		"department": "Information Technology"
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/item.json b/erpnext/demo/data/item.json
deleted file mode 100644
index 1d4ed34..0000000
--- a/erpnext/demo/data/item.json
+++ /dev/null
@@ -1,493 +0,0 @@
-[
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Asiatic Solutions",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "For Upper Bearing",
-		"image": "/assets/erpnext_demo/images/disc.png",
-		"item_code": "Disc Collars",
-		"item_group": "Raw Material",
-		"item_name": "Disc Collars"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Nan Duskin",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "CAST IRON, MCMASTER PART NO. 3710T13",
-		"image": "/assets/erpnext_demo/images/bearing.jpg",
-		"item_code": "Bearing Block",
-		"item_group": "Raw Material",
-		"item_name": "Bearing Block"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": null,
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"description": "Wind Mill C Series for Commercial Use 18ft",
-		"image": "/assets/erpnext_demo/images/wind-turbine-2.png",
-		"item_code": "Wind MIll C Series",
-		"item_group": "Products",
-		"item_name": "Wind MIll C Series"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": null,
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"description": "Wind Mill A Series for Home Use 9ft",
-		"image": "/assets/erpnext_demo/images/wind-turbine.png",
-		"item_code": "Wind Mill A Series",
-		"item_group": "Products",
-		"item_name": "Wind Mill A Series"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": null,
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->",
-		"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
-		"item_code": "Wind Turbine",
-		"item_group": "Products",
-		"item_name": "Wind Turbine",
-		"has_variants": 1,
-		"has_serial_no": 1,
-		"attributes": [
-			{
-				"attribute": "Size"
-			}
-		]
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "HomeBase",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "1.5 in. Diameter x 36 in. Mild Steel Tubing",
-		"image": null,
-		"item_code": "Bearing Pipe",
-		"item_group": "Raw Material",
-		"item_name": "Bearing Pipe"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "New World Realty",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "1/32 in. x 24 in. x 47 in. HDPE Opaque Sheet",
-		"image": null,
-		"item_code": "Wing Sheet",
-		"item_group": "Raw Material",
-		"item_name": "Wing Sheet"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Eagle Hardware",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "3/16 in. x 6 in. x 6 in. Low Carbon Steel Plate",
-		"image": null,
-		"item_code": "Upper Bearing Plate",
-		"item_group": "Raw Material",
-		"item_name": "Upper Bearing Plate"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Asiatic Solutions",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "Bearing Assembly",
-		"image": null,
-		"item_code": "Bearing Assembly",
-		"item_group": "Sub Assemblies",
-		"item_name": "Bearing Assembly"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "HomeBase",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood",
-		"image": null,
-		"item_code": "Base Plate",
-		"item_group": "Raw Material",
-		"item_name": "Base Plate",
-		"is_sub_contracted_item": 1
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Scott Ties",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "N/A",
-		"image": null,
-		"item_code": "Stand",
-		"item_group": "Raw Material",
-		"item_name": "Stand"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Eagle Hardware",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "1 in. x 3 in. x 1 ft. Multipurpose Al Alloy Bar",
-		"image": null,
-		"item_code": "Bearing Collar",
-		"item_group": "Raw Material",
-		"item_name": "Bearing Collar"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Eagle Hardware",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "1/4 in. x 6 in. x 6 in. Mild Steel Plate",
-		"image": null,
-		"item_code": "Base Bearing Plate",
-		"item_group": "Raw Material",
-		"item_name": "Base Bearing Plate"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "HomeBase",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "15/32 in. x 4 ft. x 8 ft. 3-Ply Rtd Sheathing",
-		"image": null,
-		"item_code": "External Disc",
-		"item_group": "Raw Material",
-		"item_name": "External Disc"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Eagle Hardware",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "1.25 in. Diameter x 6 ft. Mild Steel Tubing",
-		"image": null,
-		"item_code": "Shaft",
-		"item_group": "Raw Material",
-		"item_name": "Shaft"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "Ks Merchandise",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "1/2 in. x 2 ft. x 4 ft. Pine Plywood",
-		"image": null,
-		"item_code": "Blade Rib",
-		"item_group": "Raw Material",
-		"item_name": "Blade Rib"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "HomeBase",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "For Bearing Collar",
-		"image": null,
-		"item_code": "Internal Disc",
-		"item_group": "Raw Material",
-		"item_name": "Internal Disc"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": null,
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Small</p>",
-		"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
-		"item_code": "Wind Turbine-S",
-		"item_group": "Products",
-		"item_name": "Wind Turbine-S",
-		"variant_of": "Wind Turbine",
-		"valuation_rate": 300,
-		"attributes": [
-			{
-				"attribute": "Size",
-				"attribute_value": "Small"
-			}
-		]
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": null,
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Medium</p>",
-		"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
-		"item_code": "Wind Turbine-M",
-		"item_group": "Products",
-		"item_name": "Wind Turbine-M",
-		"variant_of": "Wind Turbine",
-		"valuation_rate": 300,
-		"attributes": [
-			{
-				"attribute": "Size",
-				"attribute_value": "Medium"
-			}
-		]
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": null,
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"description": "Small Wind Turbine for Home Use\n\n\n<!-- html -->\n<p>Size: Large</p>",
-		"image": "/assets/erpnext_demo/images/wind-turbine-1.jpg",
-		"item_code": "Wind Turbine-L",
-		"item_group": "Products",
-		"item_name": "Wind Turbine-L",
-		"variant_of": "Wind Turbine",
-		"valuation_rate": 300,
-		"attributes": [
-			{
-				"attribute": "Size",
-				"attribute_value": "Large"
-			}
-		]
-	},
-	{
-		"is_stock_item": 0,
-		"description": "Wind Mill A Series with Spare Bearing",
-		"item_code": "Wind Mill A Series with Spare Bearing",
-		"item_group": "Products",
-		"item_name": "Wind Mill A Series with Spare Bearing"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_supplier": "HomeBase",
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "3/4 in. x 2 ft. x 4 ft. Pine Plywood",
-		"image": null,
-		"item_code": "Base Plate Un Painted",
-		"item_group": "Raw Material",
-		"item_name": "Base Plate Un Painted"
-	},
-	{
-		"is_fixed_asset": 1,
-		"asset_category": "Furnitures",
-		"is_stock_item": 0,
-		"description": "Table",
-		"item_code": "Table",
-		"item_name": "Table",
-		"item_group": "Products"
-	},
-	{
-		"is_fixed_asset": 1,
-		"asset_category": "Furnitures",
-		"is_stock_item": 0,
-		"description": "Chair",
-		"item_code": "Chair",
-		"item_name": "Chair",
-		"item_group": "Products"
-	},
-	{
-		"is_fixed_asset": 1,
-		"asset_category": "Electronic Equipments",
-		"is_stock_item": 0,
-		"description": "Computer",
-		"item_code": "Computer",
-		"item_name": "Computer",
-		"item_group": "Products"
-	},
-	{
-		"is_fixed_asset": 1,
-		"asset_category": "Electronic Equipments",
-		"is_stock_item": 0,
-		"description": "Mobile",
-		"item_code": "Mobile",
-		"item_name": "Mobile",
-		"item_group": "Products"
-	},
-	{
-		"is_fixed_asset": 1,
-		"asset_category": "Softwares",
-		"is_stock_item": 0,
-		"description": "ERP",
-		"item_code": "ERP",
-		"item_name": "ERP",
-		"item_group": "All Item Groups"
-	},
-	{
-		"is_fixed_asset": 1,
-		"asset_category": "Softwares",
-		"is_stock_item": 0,
-		"description": "Autocad",
-		"item_code": "Autocad",
-		"item_name": "Autocad",
-		"item_group": "All Item Groups"
-	},
-	{
-		"is_stock_item": 1,
-		"has_batch_no": 1,
-		"create_new_batch": 1,
-		"valuation_rate": 200,
-		"item_defaults": [
-			{
-				"default_warehouse": "Stores"
-			}
-		],
-		"description": "Corrugated Box",
-		"item_code": "Corrugated Box",
-		"item_name": "Corrugated Box",
-		"item_group": "All Item Groups"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"is_stock_item": 1,
-		"description": "OnePlus 6",
-		"item_code": "OnePlus 6",
-		"item_name": "OnePlus 6",
-		"item_group": "Products",
-		"domain": "Retail"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"is_stock_item": 1,
-		"description": "OnePlus 6T",
-		"item_code": "OnePlus 6T",
-		"item_name": "OnePlus 6T",
-		"item_group": "Products",
-		"domain": "Retail"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"is_stock_item": 1,
-		"description": "Xiaomi Poco F1",
-		"item_code": "Xiaomi Poco F1",
-		"item_name": "Xiaomi Poco F1",
-		"item_group": "Products",
-		"domain": "Retail"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"is_stock_item": 1,
-		"description": "Iphone XS",
-		"item_code": "Iphone XS",
-		"item_name": "Iphone XS",
-		"item_group": "Products",
-		"domain": "Retail"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"is_stock_item": 1,
-		"description": "Samsung Galaxy S9",
-		"item_code": "Samsung Galaxy S9",
-		"item_name": "Samsung Galaxy S9",
-		"item_group": "Products",
-		"domain": "Retail"
-	},
-	{
-		"item_defaults": [
-			{
-				"default_warehouse": "Finished Goods"
-			}
-		],
-		"is_stock_item": 1,
-		"description": "Sony Bluetooth Headphone",
-		"item_code": "Sony Bluetooth Headphone",
-		"item_name": "Sony Bluetooth Headphone",
-		"item_group": "Products",
-		"domain": "Retail"
-	},
-	{
-		"is_stock_item": 0,
-		"description": "Samsung Phone Repair",
-		"item_code": "Samsung Phone Repair",
-		"item_name": "Samsung Phone Repair",
-		"item_group": "Services",
-		"domain": "Retail"
-	},
-	{
-		"is_stock_item": 0,
-		"description": "OnePlus Phone Repair",
-		"item_code": "OnePlus Phone Repair",
-		"item_name": "OnePlus Phone Repair",
-		"item_group": "Services",
-		"domain": "Retail"
-	},
-	{
-		"is_stock_item": 0,
-		"description": "Xiaomi Phone Repair",
-		"item_code": "Xiaomi Phone Repair",
-		"item_name": "Xiaomi Phone Repair",
-		"item_group": "Services",
-		"domain": "Retail"
-	},
-	{
-		"is_stock_item": 0,
-		"description": "Apple Phone Repair",
-		"item_code": "Apple Phone Repair",
-		"item_name": "Apple Phone Repair",
-		"item_group": "Services",
-		"domain": "Retail"
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/item_education.json b/erpnext/demo/data/item_education.json
deleted file mode 100644
index 40e4701..0000000
--- a/erpnext/demo/data/item_education.json
+++ /dev/null
@@ -1,137 +0,0 @@
-[
- {
-  "default_supplier": "Asiatic Solutions",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Books",
-  "item_group": "Raw Material",
-  "item_name": "Books"
- },
- {
-  "default_supplier": "HomeBase",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Pencil",
-  "item_group": "Raw Material",
-  "item_name": "Pencil"
- },
- {
-  "default_supplier": "New World Realty",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Tables",
-  "item_group": "Raw Material",
-  "item_name": "Tables"
- },
- {
-  "default_supplier": "Eagle Hardware",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Chair",
-  "item_group": "Raw Material",
-  "item_name": "Chair"
- },
- {
-  "default_supplier": "Asiatic Solutions",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Black Board",
-  "item_group": "Sub Assemblies",
-  "item_name": "Black Board"
- },
- {
-  "default_supplier": "HomeBase",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Chalk",
-  "item_group": "Raw Material",
-  "item_name": "Chalk"
- },
- {
-  "default_supplier": "HomeBase",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Notepad",
-  "item_group": "Raw Material",
-  "item_name": "Notepad"
- },
- {
-  "default_supplier": "Ks Merchandise",
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "item_code": "Uniform",
-  "item_group": "Raw Material",
-  "item_name": "Uniform"
- },
- {
-  "is_stock_item": 0,
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "description": "Computer",
-  "item_code": "Computer",
-  "item_name": "Computer",
-  "item_group": "Products"
- },
- {
-  "is_stock_item": 0,
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "description": "Mobile",
-  "item_code": "Mobile",
-  "item_name": "Mobile",
-  "item_group": "Products"
- },
- {
-  "is_stock_item": 0,
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "description": "ERP",
-  "item_code": "ERP",
-  "item_name": "ERP",
-  "item_group": "All Item Groups"
- },
- {
-  "is_stock_item": 0,
-  "item_defaults": [{
-      "default_warehouse": "Stores",
-      "company": "Whitmore College"
-  }],
-  "description": "Autocad",
-  "item_code": "Autocad",
-  "item_name": "Autocad",
-  "item_group": "All Item Groups"
- },
- {
-  "item_defaults": [{
-        "default_warehouse": "Stores",
-        "company": "Whitmore College"
-  }],
-  "item_code": "Service",
-  "item_group": "Services",
-  "item_name": "Service",
-  "has_variants": 0,
-  "is_stock_item": 0
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/lead.json b/erpnext/demo/data/lead.json
deleted file mode 100644
index ff78877..0000000
--- a/erpnext/demo/data/lead.json
+++ /dev/null
@@ -1,127 +0,0 @@
-[
- {
-  "company_name": "Zany Brainy", 
-  "email_id": "MartLakeman@example.com", 
-  "lead_name": "Mart Lakeman"
- }, 
- {
-  "company_name": "Patterson-Fletcher", 
-  "email_id": "SagaLundqvist@example.com", 
-  "lead_name": "Saga Lundqvist"
- }, 
- {
-  "company_name": "Griff's Hamburgers", 
-  "email_id": "AdnaSjoberg@example.com", 
-  "lead_name": "Adna Sj\u00f6berg"
- }, 
- {
-  "company_name": "Rhodes Furniture", 
-  "email_id": "IdaDSvendsen@example.com", 
-  "lead_name": "Ida Svendsen"
- }, 
- {
-  "company_name": "Burger Chef", 
-  "email_id": "EmppuHameenniemi@example.com", 
-  "lead_name": "Emppu H\u00e4meenniemi"
- }, 
- {
-  "company_name": "Stratabiz", 
-  "email_id": "EugenioPisano@example.com", 
-  "lead_name": "Eugenio Pisano"
- }, 
- {
-  "company_name": "Home Quarters Warehouse", 
-  "email_id": "SemharHagos@example.com", 
-  "lead_name": "Semhar Hagos"
- }, 
- {
-  "company_name": "Enviro Architectural Designs", 
-  "email_id": "BranimiraIvankovic@example.com", 
-  "lead_name": "Branimira Ivankovi\u0107"
- }, 
- {
-  "company_name": "Ideal Garden Management", 
-  "email_id": "ShellyLFields@example.com", 
-  "lead_name": "Shelly Fields"
- }, 
- {
-  "company_name": "Listen Up", 
-  "email_id": "LeoMikulic@example.com", 
-  "lead_name": "Leo Mikuli\u0107"
- }, 
- {
-  "company_name": "I. Magnin", 
-  "email_id": "DenisaJarosova@example.com", 
-  "lead_name": "Denisa Jaro\u0161ov\u00e1"
- }, 
- {
-  "company_name": "First Rate Choice", 
-  "email_id": "JanekRutkowski@example.com", 
-  "lead_name": "Janek Rutkowski"
- }, 
- {
-  "company_name": "Multi Tech Development", 
-  "email_id": "mm@example.com", 
-  "lead_name": "\u7f8e\u6708 \u5b87\u85e4"
- }, 
- {
-  "company_name": "National Auto Parts", 
-  "email_id": "dd@example.com", 
-  "lead_name": "\u0414\u0430\u043d\u0438\u0438\u043b \u0410\u0444\u0430\u043d\u0430\u0441\u044c\u0435\u0432"
- }, 
- {
-  "company_name": "Integra Investment Plan", 
-  "email_id": "ZorislavPetkovic@example.com", 
-  "lead_name": "Zorislav Petkovi\u0107"
- }, 
- {
-  "company_name": "The Lawn Guru", 
-  "email_id": "NanaoNiwa@example.com", 
-  "lead_name": "Nanao Niwa"
- }, 
- {
-  "company_name": "Buena Vista Realty Service", 
-  "email_id": "HreiarJorundsson@example.com", 
-  "lead_name": "Hrei\u00f0ar J\u00f6rundsson"
- }, 
- {
-  "company_name": "Bountiful Harvest Health Food Store", 
-  "email_id": "ChuThiBichLai@example.com", 
-  "lead_name": "Lai Chu"
- }, 
- {
-  "company_name": "P. Samuels Men's Clothiers", 
-  "email_id": "VictorAksakov@example.com", 
-  "lead_name": "Victor Aksakov"
- }, 
- {
-  "company_name": "Vinyl Fever", 
-  "email_id": "SaidalimBisliev@example.com", 
-  "lead_name": "Saidalim Bisliev"
- }, 
- {
-  "company_name": "Garden Master", 
-  "email_id": "TotteJakobsson@example.com", 
-  "lead_name": "Totte Jakobsson"
- }, 
- {
-  "company_name": "Big Apple", 
-  "email_id": "NanaArmasRobles@example.com", 
-  "lead_name": "Nan\u00e1 Armas"
- }, 
- {
-  "company_name": "Monk House Sales", 
-  "email_id": "WalerianDuda@example.com", 
-  "lead_name": "Walerian Duda"
- }, 
- {
-  "company_name": "ManCharm", 
-  "email_id": "Moarimikashi@example.com", 
-  "lead_name": "Moarimikashi"
- }, 
- {
-  "company_name": "Custom Lawn Care", 
-  "email_id": "DobromilDabrowski@example.com", 
-  "lead_name": "Dobromi\u0142 D\u0105browski"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/location.json b/erpnext/demo/data/location.json
deleted file mode 100644
index b521aa0..0000000
--- a/erpnext/demo/data/location.json
+++ /dev/null
@@ -1,22 +0,0 @@
-[
-    {
-        "location_name": "Main Location",
-        "latitude": 40.0,
-        "longitude": 20.0
-    },
-    {
-        "location_name": "Avg Location",
-        "latitude": 63.0,
-        "longitude": 99.3
-    },
-    {
-        "location_name": "Zany Location",
-        "latitude": 47.5,
-        "longitude": 10.0
-    },
-    {
-        "location_name": "Fletcher Location",
-        "latitude": 100.90,
-        "longitude": 80
-    }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/operation.json b/erpnext/demo/data/operation.json
deleted file mode 100644
index 47f26d1..0000000
--- a/erpnext/demo/data/operation.json
+++ /dev/null
@@ -1,32 +0,0 @@
-[
- {
-  "description": "Setup Fixtures for Assembly", 
-  "name": "Setup Fixtures", 
-  "workstation": "Assembly Station 1"
- }, 
- {
-  "description": "Assemble Unit as per Standard Operating Procedures", 
-  "name": "Assembly Operation", 
-  "workstation": "Assembly Station 1"
- }, 
- {
-  "description": "Final Testing Checklist", 
-  "name": "Testing", 
-  "workstation": "Packing and Testing Station"
- }, 
- {
-  "description": "Final Packing and add Instructions", 
-  "name": "Packing", 
-  "workstation": "Packing and Testing Station"
- }, 
- {
-  "description": "Prepare frame for assembly", 
-  "name": "Prepare Frame", 
-  "workstation": "Drilling Machine 1"
- }, 
- {
-  "description": "Connect wires", 
-  "name": "Wiring", 
-  "workstation": "Assembly Station 1"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/patient.json b/erpnext/demo/data/patient.json
deleted file mode 100644
index 6d95a20..0000000
--- a/erpnext/demo/data/patient.json
+++ /dev/null
@@ -1,27 +0,0 @@
-[
-  {
-  "patient_name": "lila",
-  "gender": "Female"
-  },
-  {
-  "patient_name": "charline",
-  "gender": "Female"
-  },
-  {
-  "patient_name": "soren",
-  "last_name": "le gall",
-  "gender": "Male"
-  },
-  {
-  "patient_name": "fanny",
-  "gender": "Female"
-  },
-  {
-  "patient_name": "julie",
-  "gender": "Female"
-  },
-  {
-  "patient_name": "louka",
-  "gender": "Male"
-  }
-]
diff --git a/erpnext/demo/data/practitioner.json b/erpnext/demo/data/practitioner.json
deleted file mode 100644
index 39c960f..0000000
--- a/erpnext/demo/data/practitioner.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
-	{
-		"doctype": "Healthcare Practitioner",
-		"first_name": "Eddie Jessup",
-		"department": "Pathology"
-	},
-	{
-		"doctype": "Healthcare Practitioner",
-		"first_name": "Deepshi Garg",
-		"department": "ENT"
-	},
-	{
-		"doctype": "Healthcare Practitioner",
-		"first_name": "Amit Jain",
-		"department": "Microbiology"
-	}
-]
diff --git a/erpnext/demo/data/program.json b/erpnext/demo/data/program.json
deleted file mode 100644
index 9c2ec77..0000000
--- a/erpnext/demo/data/program.json
+++ /dev/null
@@ -1,46 +0,0 @@
-[
-	{
-		"doctype": "Program",
-		"name": "MCA",
-		"program_name": "Masters of Computer Applications",
-		"program_code": "MCA",
-		"department": "Information Technology",
-		"courses": [
-			{ "course": "MCA4010" },
-			{ "course": "MCA4020" },
-			{ "course": "MCA4030" }
-		]
-	},
-	{
-		"doctype": "Program",
-		"name": "BCA",
-		"program_name": "Bachelor of Computer Applications",
-		"program_code": "BCA",
-		"department": "Information Technology",
-		"courses": [
-			{ "course": "BCA2030" },
-			{ "course": "BCA1030" },
-			{ "course": "BCA2020" },
-			{ "course": "BCA1040" },
-			{ "course": "BCA1010" },
-			{ "course": "BCA2010" },
-			{ "course": "BCA1020" }
-		]
-	},
-	{
-		"doctype": "Program",
-		"name": "BBA",
-		"program_name": "Bachelor of Business Administration",
-		"program_code": "BBA",
-		"department": "Management Studies",
-		"courses": [
-			{ "course": "BBA 101" },
-			{ "course": "BBA 102" },
-			{ "course": "BBA 103" },
-			{ "course": "BBA 301" },
-			{ "course": "BBA 302" },
-			{ "course": "BBA 304" },
-			{ "course": "BBA 505" }
-		]
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/random_student_data.json b/erpnext/demo/data/random_student_data.json
deleted file mode 100644
index babcc71..0000000
--- a/erpnext/demo/data/random_student_data.json
+++ /dev/null
@@ -1,1604 +0,0 @@
-[
-{
-"first_name": "amanda",
-"last_name": "edwards",
-"image": "https://randomuser.me/api/portraits/women/55.jpg",
-"gender": "Female"
-},
-{
-"first_name": "abbie",
-"last_name": "johnston",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "heather",
-"last_name": "nelson",
-"image": "https://randomuser.me/api/portraits/women/13.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maxwell",
-"last_name": "gilbert",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "molly",
-"last_name": "ramirez",
-"image": "https://randomuser.me/api/portraits/women/71.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ian",
-"last_name": "barrett",
-"image": "https://randomuser.me/api/portraits/men/68.jpg",
-"gender": "Male"
-},
-{
-"first_name": "kim",
-"last_name": "hudson",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "bruce",
-"last_name": "murray",
-"image": "https://randomuser.me/api/portraits/men/59.jpg",
-"gender": "Male"
-},
-{
-"first_name": "henry",
-"last_name": "powell",
-"image": "https://randomuser.me/api/portraits/men/88.jpg",
-"gender": "Male"
-},
-{
-"first_name": "chris",
-"last_name": "foster",
-"image": "https://randomuser.me/api/portraits/men/5.jpg",
-"gender": "Male"
-},
-{
-"first_name": "billy",
-"last_name": "kim",
-"image": "https://randomuser.me/api/portraits/men/91.jpg",
-"gender": "Male"
-},
-{
-"first_name": "samuel",
-"last_name": "harper",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jayden",
-"last_name": "kelly",
-"image": "https://randomuser.me/api/portraits/men/31.jpg",
-"gender": "Male"
-},
-{
-"first_name": "grace",
-"last_name": "berry",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ronnie",
-"last_name": "nelson",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "harvey",
-"last_name": "harper",
-"image": "https://randomuser.me/api/portraits/men/68.jpg",
-"gender": "Male"
-},
-{
-"first_name": "maya",
-"last_name": "fernandez",
-"image": "https://randomuser.me/api/portraits/women/79.jpg",
-"gender": "Female"
-},
-{
-"first_name": "faith",
-"last_name": "lewis",
-"image": "https://randomuser.me/api/portraits/women/84.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kirk",
-"last_name": "macrae",
-"image": "https://randomuser.me/api/portraits/men/13.jpg",
-"gender": "Male"
-},
-{
-"first_name": "tracy",
-"last_name": "holt",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mandy",
-"last_name": "dean",
-"image": "https://randomuser.me/api/portraits/women/0.jpg",
-"gender": "Female"
-},
-{
-"first_name": "sam",
-"last_name": "dunn",
-"image": "https://randomuser.me/api/portraits/women/12.jpg",
-"gender": "Female"
-},
-{
-"first_name": "zoe",
-"last_name": "fleming",
-"image": "https://randomuser.me/api/portraits/women/9.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jeffrey",
-"last_name": "stewart",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "dick",
-"last_name": "ryan",
-"image": "https://randomuser.me/api/portraits/men/63.jpg",
-"gender": "Male"
-},
-{
-"first_name": "carl",
-"last_name": "neal",
-"image": "https://randomuser.me/api/portraits/men/41.jpg",
-"gender": "Male"
-},
-{
-"first_name": "scarlett",
-"last_name": "ruiz",
-"image": "https://randomuser.me/api/portraits/women/24.jpg",
-"gender": "Female"
-},
-{
-"first_name": "rene",
-"last_name": "hughes",
-"image": "https://randomuser.me/api/portraits/men/3.jpg",
-"gender": "Male"
-},
-{
-"first_name": "greg",
-"last_name": "montgomery",
-"image": "https://randomuser.me/api/portraits/men/12.jpg",
-"gender": "Male"
-},
-{
-"first_name": "matt",
-"last_name": "lane",
-"image": "https://randomuser.me/api/portraits/men/85.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eleanor",
-"last_name": "pearson",
-"image": "https://randomuser.me/api/portraits/women/61.jpg",
-"gender": "Female"
-},
-{
-"first_name": "theodore",
-"last_name": "burton",
-"image": "https://randomuser.me/api/portraits/men/81.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jesus",
-"last_name": "hunt",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "taylor",
-"last_name": "alvarez",
-"image": "https://randomuser.me/api/portraits/men/0.jpg",
-"gender": "Male"
-},
-{
-"first_name": "barbara",
-"last_name": "lucas",
-"image": "https://randomuser.me/api/portraits/women/21.jpg",
-"gender": "Female"
-},
-{
-"first_name": "nicky",
-"last_name": "simmons",
-"image": "https://randomuser.me/api/portraits/women/29.jpg",
-"gender": "Female"
-},
-{
-"first_name": "arthur",
-"last_name": "obrien",
-"image": "https://randomuser.me/api/portraits/men/11.jpg",
-"gender": "Male"
-},
-{
-"first_name": "donna",
-"last_name": "holmes",
-"image": "https://randomuser.me/api/portraits/women/33.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mitchell",
-"last_name": "castro",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "byron",
-"last_name": "marshall",
-"image": "https://randomuser.me/api/portraits/men/57.jpg",
-"gender": "Male"
-},
-{
-"first_name": "larry",
-"last_name": "king",
-"image": "https://randomuser.me/api/portraits/men/58.jpg",
-"gender": "Male"
-},
-{
-"first_name": "deborah",
-"last_name": "fuller",
-"image": "https://randomuser.me/api/portraits/women/50.jpg",
-"gender": "Female"
-},
-{
-"first_name": "eleanor",
-"last_name": "elliott",
-"image": "https://randomuser.me/api/portraits/women/80.jpg",
-"gender": "Female"
-},
-{
-"first_name": "derrick",
-"last_name": "shaw",
-"image": "https://randomuser.me/api/portraits/men/78.jpg",
-"gender": "Male"
-},
-{
-"first_name": "barbara",
-"last_name": "lynch",
-"image": "https://randomuser.me/api/portraits/women/15.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elijah",
-"last_name": "allen",
-"image": "https://randomuser.me/api/portraits/men/43.jpg",
-"gender": "Male"
-},
-{
-"first_name": "nicholas",
-"last_name": "harper",
-"image": "https://randomuser.me/api/portraits/men/2.jpg",
-"gender": "Male"
-},
-{
-"first_name": "sofia",
-"last_name": "riley",
-"image": "https://randomuser.me/api/portraits/women/96.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jar",
-"last_name": "hunt",
-"image": "https://randomuser.me/api/portraits/men/72.jpg",
-"gender": "Male"
-},
-{
-"first_name": "philip",
-"last_name": "rose",
-"image": "https://randomuser.me/api/portraits/men/16.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ella",
-"last_name": "moore",
-"image": "https://randomuser.me/api/portraits/women/83.jpg",
-"gender": "Female"
-},
-{
-"first_name": "seth",
-"last_name": "tucker",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "abby",
-"last_name": "gonzalez",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "noah",
-"last_name": "williamson",
-"image": "https://randomuser.me/api/portraits/men/54.jpg",
-"gender": "Male"
-},
-{
-"first_name": "cathy",
-"last_name": "gray",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "barb",
-"last_name": "snyder",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "rosalyn",
-"last_name": "hale",
-"image": "https://randomuser.me/api/portraits/women/64.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jessica",
-"last_name": "armstrong",
-"image": "https://randomuser.me/api/portraits/women/95.jpg",
-"gender": "Female"
-},
-{
-"first_name": "vicki",
-"last_name": "wheeler",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "luke",
-"last_name": "fisher",
-"image": "https://randomuser.me/api/portraits/men/77.jpg",
-"gender": "Male"
-},
-{
-"first_name": "joey",
-"last_name": "wheeler",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "victoria",
-"last_name": "jimenez",
-"image": "https://randomuser.me/api/portraits/women/25.jpg",
-"gender": "Female"
-},
-{
-"first_name": "daryl",
-"last_name": "patterson",
-"image": "https://randomuser.me/api/portraits/men/30.jpg",
-"gender": "Male"
-},
-{
-"first_name": "dwayne",
-"last_name": "jensen",
-"image": "https://randomuser.me/api/portraits/men/71.jpg",
-"gender": "Male"
-},
-{
-"first_name": "herbert",
-"last_name": "silva",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "walter",
-"last_name": "walker",
-"image": "https://randomuser.me/api/portraits/men/91.jpg",
-"gender": "Male"
-},
-{
-"first_name": "logan",
-"last_name": "banks",
-"image": "https://randomuser.me/api/portraits/men/67.jpg",
-"gender": "Male"
-},
-{
-"first_name": "shawn",
-"last_name": "harvey",
-"image": "https://randomuser.me/api/portraits/men/87.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lawrence",
-"last_name": "bradley",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jack",
-"last_name": "fleming",
-"image": "https://randomuser.me/api/portraits/men/37.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jackson",
-"last_name": "boyd",
-"image": "https://randomuser.me/api/portraits/men/68.jpg",
-"gender": "Male"
-},
-{
-"first_name": "cecil",
-"last_name": "webb",
-"image": "https://randomuser.me/api/portraits/men/9.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eliza",
-"last_name": "mills",
-"image": "https://randomuser.me/api/portraits/women/20.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jenny",
-"last_name": "frazier",
-"image": "https://randomuser.me/api/portraits/women/61.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kent",
-"last_name": "butler",
-"image": "https://randomuser.me/api/portraits/men/64.jpg",
-"gender": "Male"
-},
-{
-"first_name": "rose",
-"last_name": "perry",
-"image": "https://randomuser.me/api/portraits/women/74.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jack",
-"last_name": "king",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elmer",
-"last_name": "williams",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "vanessa",
-"last_name": "torres",
-"image": "https://randomuser.me/api/portraits/women/41.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tyrone",
-"last_name": "coleman",
-"image": "https://randomuser.me/api/portraits/men/59.jpg",
-"gender": "Male"
-},
-{
-"first_name": "julie",
-"last_name": "bradley",
-"image": "https://randomuser.me/api/portraits/women/50.jpg",
-"gender": "Female"
-},
-{
-"first_name": "fernando",
-"last_name": "castro",
-"image": "https://randomuser.me/api/portraits/men/44.jpg",
-"gender": "Male"
-},
-{
-"first_name": "sara",
-"last_name": "craig",
-"image": "https://randomuser.me/api/portraits/women/8.jpg",
-"gender": "Female"
-},
-{
-"first_name": "steven",
-"last_name": "stone",
-"image": "https://randomuser.me/api/portraits/men/47.jpg",
-"gender": "Male"
-},
-{
-"first_name": "barb",
-"last_name": "rodriquez",
-"image": "https://randomuser.me/api/portraits/women/73.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charlie",
-"last_name": "king",
-"image": "https://randomuser.me/api/portraits/men/79.jpg",
-"gender": "Male"
-},
-{
-"first_name": "jessica",
-"last_name": "davis",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lewis",
-"last_name": "watson",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "charlotte",
-"last_name": "johnson",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "danielle",
-"last_name": "bell",
-"image": "https://randomuser.me/api/portraits/women/54.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kristin",
-"last_name": "dixon",
-"image": "https://randomuser.me/api/portraits/women/23.jpg",
-"gender": "Female"
-},
-{
-"first_name": "andrea",
-"last_name": "thompson",
-"image": "https://randomuser.me/api/portraits/women/54.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ashley",
-"last_name": "andrews",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "sharon",
-"last_name": "martinez",
-"image": "https://randomuser.me/api/portraits/women/6.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tristan",
-"last_name": "cunningham",
-"image": "https://randomuser.me/api/portraits/men/62.jpg",
-"gender": "Male"
-},
-{
-"first_name": "carol",
-"last_name": "chavez",
-"image": "https://randomuser.me/api/portraits/women/85.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lauren",
-"last_name": "hudson",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "guy",
-"last_name": "robertson",
-"image": "https://randomuser.me/api/portraits/men/78.jpg",
-"gender": "Male"
-},
-{
-"first_name": "debra",
-"last_name": "long",
-"image": "https://randomuser.me/api/portraits/women/23.jpg",
-"gender": "Female"
-},
-{
-"first_name": "taylor",
-"last_name": "carpenter",
-"image": "https://randomuser.me/api/portraits/men/0.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eetu",
-"last_name": "annala",
-"image": "https://randomuser.me/api/portraits/men/31.jpg",
-"gender": "Male"
-},
-{
-"first_name": "oliver",
-"last_name": "moilanen",
-"image": "https://randomuser.me/api/portraits/men/14.jpg",
-"gender": "Male"
-},
-{
-"first_name": "leo",
-"last_name": "maunu",
-"image": "https://randomuser.me/api/portraits/men/72.jpg",
-"gender": "Male"
-},
-{
-"first_name": "iiris",
-"last_name": "kalas",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "aada",
-"last_name": "kinnunen",
-"image": "https://randomuser.me/api/portraits/women/64.jpg",
-"gender": "Female"
-},
-{
-"first_name": "topias",
-"last_name": "walli",
-"image": "https://randomuser.me/api/portraits/men/58.jpg",
-"gender": "Male"
-},
-{
-"first_name": "viivi",
-"last_name": "toivonen",
-"image": "https://randomuser.me/api/portraits/women/16.jpg",
-"gender": "Female"
-},
-{
-"first_name": "iina",
-"last_name": "makinen",
-"image": "https://randomuser.me/api/portraits/women/44.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lumi",
-"last_name": "tuominen",
-"image": "https://randomuser.me/api/portraits/women/11.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ellen",
-"last_name": "koski",
-"image": "https://randomuser.me/api/portraits/women/22.jpg",
-"gender": "Female"
-},
-{
-"first_name": "onni",
-"last_name": "laurila",
-"image": "https://randomuser.me/api/portraits/men/74.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eevi",
-"last_name": "niskanen",
-"image": "https://randomuser.me/api/portraits/women/72.jpg",
-"gender": "Female"
-},
-{
-"first_name": "julius",
-"last_name": "maijala",
-"image": "https://randomuser.me/api/portraits/men/8.jpg",
-"gender": "Male"
-},
-{
-"first_name": "sofia",
-"last_name": "tuomi",
-"image": "https://randomuser.me/api/portraits/women/1.jpg",
-"gender": "Female"
-},
-{
-"first_name": "oliver",
-"last_name": "jarvela",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "luukas",
-"last_name": "mikkola",
-"image": "https://randomuser.me/api/portraits/men/90.jpg",
-"gender": "Male"
-},
-{
-"first_name": "amanda",
-"last_name": "anttila",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ella",
-"last_name": "sakala",
-"image": "https://randomuser.me/api/portraits/women/79.jpg",
-"gender": "Female"
-},
-{
-"first_name": "siiri",
-"last_name": "kinnunen",
-"image": "https://randomuser.me/api/portraits/women/37.jpg",
-"gender": "Female"
-},
-{
-"first_name": "joona",
-"last_name": "korhonen",
-"image": "https://randomuser.me/api/portraits/men/87.jpg",
-"gender": "Male"
-},
-{
-"first_name": "topias",
-"last_name": "korpi",
-"image": "https://randomuser.me/api/portraits/men/75.jpg",
-"gender": "Male"
-},
-{
-"first_name": "mikael",
-"last_name": "remes",
-"image": "https://randomuser.me/api/portraits/men/89.jpg",
-"gender": "Male"
-},
-{
-"first_name": "veera",
-"last_name": "peltola",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emil",
-"last_name": "makela",
-"image": "https://randomuser.me/api/portraits/men/98.jpg",
-"gender": "Male"
-},
-{
-"first_name": "luukas",
-"last_name": "kujala",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemil",
-"last_name": "honkala",
-"image": "https://randomuser.me/api/portraits/men/85.jpg",
-"gender": "Male"
-},
-{
-"first_name": "peetu",
-"last_name": "kalm",
-"image": "https://randomuser.me/api/portraits/men/17.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemeli",
-"last_name": "lehtonen",
-"image": "https://randomuser.me/api/portraits/men/55.jpg",
-"gender": "Male"
-},
-{
-"first_name": "viivi",
-"last_name": "koistinen",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elli",
-"last_name": "savela",
-"image": "https://randomuser.me/api/portraits/women/77.jpg",
-"gender": "Female"
-},
-{
-"first_name": "venla",
-"last_name": "walli",
-"image": "https://randomuser.me/api/portraits/women/52.jpg",
-"gender": "Female"
-},
-{
-"first_name": "amanda",
-"last_name": "wuollet",
-"image": "https://randomuser.me/api/portraits/women/11.jpg",
-"gender": "Female"
-},
-{
-"first_name": "valtteri",
-"last_name": "hokkanen",
-"image": "https://randomuser.me/api/portraits/men/30.jpg",
-"gender": "Male"
-},
-{
-"first_name": "veera",
-"last_name": "maki",
-"image": "https://randomuser.me/api/portraits/women/34.jpg",
-"gender": "Female"
-},
-{
-"first_name": "kerttu",
-"last_name": "maunu",
-"image": "https://randomuser.me/api/portraits/women/1.jpg",
-"gender": "Female"
-},
-{
-"first_name": "nella",
-"last_name": "hanka",
-"image": "https://randomuser.me/api/portraits/women/70.jpg",
-"gender": "Female"
-},
-{
-"first_name": "iiris",
-"last_name": "hakala",
-"image": "https://randomuser.me/api/portraits/women/33.jpg",
-"gender": "Female"
-},
-{
-"first_name": "viivi",
-"last_name": "ojala",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "iina",
-"last_name": "peura",
-"image": "https://randomuser.me/api/portraits/women/22.jpg",
-"gender": "Female"
-},
-{
-"first_name": "samuel",
-"last_name": "mattila",
-"image": "https://randomuser.me/api/portraits/men/88.jpg",
-"gender": "Male"
-},
-{
-"first_name": "julius",
-"last_name": "kumpula",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "nooa",
-"last_name": "haapala",
-"image": "https://randomuser.me/api/portraits/men/77.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elias",
-"last_name": "leppo",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "niklas",
-"last_name": "elo",
-"image": "https://randomuser.me/api/portraits/men/64.jpg",
-"gender": "Male"
-},
-{
-"first_name": "olivia",
-"last_name": "nurmi",
-"image": "https://randomuser.me/api/portraits/women/82.jpg",
-"gender": "Female"
-},
-{
-"first_name": "milja",
-"last_name": "lassila",
-"image": "https://randomuser.me/api/portraits/women/47.jpg",
-"gender": "Female"
-},
-{
-"first_name": "daniel",
-"last_name": "kalas",
-"image": "https://randomuser.me/api/portraits/men/53.jpg",
-"gender": "Male"
-},
-{
-"first_name": "enni",
-"last_name": "ramo",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "matilda",
-"last_name": "salmi",
-"image": "https://randomuser.me/api/portraits/women/84.jpg",
-"gender": "Female"
-},
-{
-"first_name": "valtteri",
-"last_name": "wirta",
-"image": "https://randomuser.me/api/portraits/men/26.jpg",
-"gender": "Male"
-},
-{
-"first_name": "julius",
-"last_name": "maijala",
-"image": "https://randomuser.me/api/portraits/men/39.jpg",
-"gender": "Male"
-},
-{
-"first_name": "kerttu",
-"last_name": "peltola",
-"image": "https://randomuser.me/api/portraits/women/39.jpg",
-"gender": "Female"
-},
-{
-"first_name": "aada",
-"last_name": "kokko",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elsa",
-"last_name": "niska",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ella",
-"last_name": "kalm",
-"image": "https://randomuser.me/api/portraits/women/61.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lilja",
-"last_name": "heinonen",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "akseli",
-"last_name": "laakso",
-"image": "https://randomuser.me/api/portraits/men/64.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lotta",
-"last_name": "saarela",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "leo",
-"last_name": "polon",
-"image": "https://randomuser.me/api/portraits/men/5.jpg",
-"gender": "Male"
-},
-{
-"first_name": "aleksi",
-"last_name": "wuollet",
-"image": "https://randomuser.me/api/portraits/men/87.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemil",
-"last_name": "kalas",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "emmi",
-"last_name": "koistinen",
-"image": "https://randomuser.me/api/portraits/women/66.jpg",
-"gender": "Female"
-},
-{
-"first_name": "väinö",
-"last_name": "halla",
-"image": "https://randomuser.me/api/portraits/men/65.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eemil",
-"last_name": "heikkila",
-"image": "https://randomuser.me/api/portraits/men/18.jpg",
-"gender": "Male"
-},
-{
-"first_name": "amanda",
-"last_name": "lakso",
-"image": "https://randomuser.me/api/portraits/women/29.jpg",
-"gender": "Female"
-},
-{
-"first_name": "vilho",
-"last_name": "kivela",
-"image": "https://randomuser.me/api/portraits/men/19.jpg",
-"gender": "Male"
-},
-{
-"first_name": "peppi",
-"last_name": "lehtinen",
-"image": "https://randomuser.me/api/portraits/women/80.jpg",
-"gender": "Female"
-},
-{
-"first_name": "onni",
-"last_name": "lehtinen",
-"image": "https://randomuser.me/api/portraits/men/0.jpg",
-"gender": "Male"
-},
-{
-"first_name": "onni",
-"last_name": "ahonen",
-"image": "https://randomuser.me/api/portraits/men/49.jpg",
-"gender": "Male"
-},
-{
-"first_name": "venla",
-"last_name": "ranta",
-"image": "https://randomuser.me/api/portraits/women/0.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ronja",
-"last_name": "korhonen",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emmi",
-"last_name": "niva",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "oskari",
-"last_name": "leppanen",
-"image": "https://randomuser.me/api/portraits/men/43.jpg",
-"gender": "Male"
-},
-{
-"first_name": "arttu",
-"last_name": "heinonen",
-"image": "https://randomuser.me/api/portraits/men/94.jpg",
-"gender": "Male"
-},
-{
-"first_name": "toivo",
-"last_name": "makela",
-"image": "https://randomuser.me/api/portraits/men/23.jpg",
-"gender": "Male"
-},
-{
-"first_name": "otto",
-"last_name": "leino",
-"image": "https://randomuser.me/api/portraits/men/51.jpg",
-"gender": "Male"
-},
-{
-"first_name": "milla",
-"last_name": "kokko",
-"image": "https://randomuser.me/api/portraits/women/66.jpg",
-"gender": "Female"
-},
-{
-"first_name": "konsta",
-"last_name": "lehto",
-"image": "https://randomuser.me/api/portraits/men/29.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eeli",
-"last_name": "heikkinen",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "matilda",
-"last_name": "tanner",
-"image": "https://randomuser.me/api/portraits/women/2.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elias",
-"last_name": "kivisto",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "akseli",
-"last_name": "wirta",
-"image": "https://randomuser.me/api/portraits/men/90.jpg",
-"gender": "Male"
-},
-{
-"first_name": "leevi",
-"last_name": "kallio",
-"image": "https://randomuser.me/api/portraits/men/89.jpg",
-"gender": "Male"
-},
-{
-"first_name": "emilia",
-"last_name": "pelto",
-"image": "https://randomuser.me/api/portraits/women/0.jpg",
-"gender": "Female"
-},
-{
-"first_name": "niilo",
-"last_name": "keranen",
-"image": "https://randomuser.me/api/portraits/men/29.jpg",
-"gender": "Male"
-},
-{
-"first_name": "mikael",
-"last_name": "wainio",
-"image": "https://randomuser.me/api/portraits/men/85.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elias",
-"last_name": "saksa",
-"image": "https://randomuser.me/api/portraits/men/53.jpg",
-"gender": "Male"
-},
-{
-"first_name": "aatu",
-"last_name": "erkkila",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "arttu",
-"last_name": "jarvela",
-"image": "https://randomuser.me/api/portraits/men/49.jpg",
-"gender": "Male"
-},
-{
-"first_name": "matilda",
-"last_name": "lassila",
-"image": "https://randomuser.me/api/portraits/women/46.jpg",
-"gender": "Female"
-},
-{
-"first_name": "alisa",
-"last_name": "waara",
-"image": "https://randomuser.me/api/portraits/women/67.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emilia",
-"last_name": "saksa",
-"image": "https://randomuser.me/api/portraits/women/66.jpg",
-"gender": "Female"
-},
-{
-"first_name": "valtteri",
-"last_name": "tikkanen",
-"image": "https://randomuser.me/api/portraits/men/88.jpg",
-"gender": "Male"
-},
-{
-"first_name": "konsta",
-"last_name": "rantala",
-"image": "https://randomuser.me/api/portraits/men/50.jpg",
-"gender": "Male"
-},
-{
-"first_name": "minttu",
-"last_name": "murto",
-"image": "https://randomuser.me/api/portraits/women/14.jpg",
-"gender": "Female"
-},
-{
-"first_name": "vilma",
-"last_name": "hatala",
-"image": "https://randomuser.me/api/portraits/women/60.jpg",
-"gender": "Female"
-},
-{
-"first_name": "anni",
-"last_name": "linna",
-"image": "https://randomuser.me/api/portraits/women/59.jpg",
-"gender": "Female"
-},
-{
-"first_name": "niklas",
-"last_name": "hautala",
-"image": "https://randomuser.me/api/portraits/men/7.jpg",
-"gender": "Male"
-},
-{
-"first_name": "niilo",
-"last_name": "lehtinen",
-"image": "https://randomuser.me/api/portraits/men/54.jpg",
-"gender": "Male"
-},
-{
-"first_name": "oona",
-"last_name": "saarinen",
-"image": "https://randomuser.me/api/portraits/women/71.jpg",
-"gender": "Female"
-},
-{
-"first_name": "constance",
-"last_name": "marie",
-"image": "https://randomuser.me/api/portraits/women/40.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charles",
-"last_name": "pierre",
-"image": "https://randomuser.me/api/portraits/men/96.jpg",
-"gender": "Male"
-},
-{
-"first_name": "bérénice",
-"last_name": "leclerc",
-"image": "https://randomuser.me/api/portraits/women/39.jpg",
-"gender": "Female"
-},
-{
-"first_name": "clémence",
-"last_name": "arnaud",
-"image": "https://randomuser.me/api/portraits/women/48.jpg",
-"gender": "Female"
-},
-{
-"first_name": "melvin",
-"last_name": "lemoine",
-"image": "https://randomuser.me/api/portraits/men/47.jpg",
-"gender": "Male"
-},
-{
-"first_name": "marceau",
-"last_name": "joly",
-"image": "https://randomuser.me/api/portraits/men/56.jpg",
-"gender": "Male"
-},
-{
-"first_name": "garance",
-"last_name": "mathieu",
-"image": "https://randomuser.me/api/portraits/women/87.jpg",
-"gender": "Female"
-},
-{
-"first_name": "angèle",
-"last_name": "perrin",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "pauline",
-"last_name": "simon",
-"image": "https://randomuser.me/api/portraits/women/82.jpg",
-"gender": "Female"
-},
-{
-"first_name": "apolline",
-"last_name": "laurent",
-"image": "https://randomuser.me/api/portraits/women/27.jpg",
-"gender": "Female"
-},
-{
-"first_name": "luca",
-"last_name": "lefevre",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "bastien",
-"last_name": "roger",
-"image": "https://randomuser.me/api/portraits/men/73.jpg",
-"gender": "Male"
-},
-{
-"first_name": "marie",
-"last_name": "rodriguez",
-"image": "https://randomuser.me/api/portraits/women/18.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tristan",
-"last_name": "renaud",
-"image": "https://randomuser.me/api/portraits/men/41.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eva",
-"last_name": "philippe",
-"image": "https://randomuser.me/api/portraits/women/26.jpg",
-"gender": "Female"
-},
-{
-"first_name": "coline",
-"last_name": "dufour",
-"image": "https://randomuser.me/api/portraits/women/64.jpg",
-"gender": "Female"
-},
-{
-"first_name": "marilou",
-"last_name": "adam",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lia",
-"last_name": "renard",
-"image": "https://randomuser.me/api/portraits/women/88.jpg",
-"gender": "Female"
-},
-{
-"first_name": "timothee",
-"last_name": "rolland",
-"image": "https://randomuser.me/api/portraits/men/75.jpg",
-"gender": "Male"
-},
-{
-"first_name": "hélèna",
-"last_name": "boyer",
-"image": "https://randomuser.me/api/portraits/women/8.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mélody",
-"last_name": "andre",
-"image": "https://randomuser.me/api/portraits/women/75.jpg",
-"gender": "Female"
-},
-{
-"first_name": "jeanne",
-"last_name": "duval",
-"image": "https://randomuser.me/api/portraits/women/44.jpg",
-"gender": "Female"
-},
-{
-"first_name": "elias",
-"last_name": "dupont",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "estelle",
-"last_name": "bernard",
-"image": "https://randomuser.me/api/portraits/women/23.jpg",
-"gender": "Female"
-},
-{
-"first_name": "roxane",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/women/14.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maëva",
-"last_name": "guerin",
-"image": "https://randomuser.me/api/portraits/women/44.jpg",
-"gender": "Female"
-},
-{
-"first_name": "liam",
-"last_name": "carpentier",
-"image": "https://randomuser.me/api/portraits/men/41.jpg",
-"gender": "Male"
-},
-{
-"first_name": "théo",
-"last_name": "gaillard",
-"image": "https://randomuser.me/api/portraits/men/40.jpg",
-"gender": "Male"
-},
-{
-"first_name": "angelina",
-"last_name": "clement",
-"image": "https://randomuser.me/api/portraits/women/53.jpg",
-"gender": "Female"
-},
-{
-"first_name": "emma",
-"last_name": "bertrand",
-"image": "https://randomuser.me/api/portraits/women/86.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charles",
-"last_name": "rolland",
-"image": "https://randomuser.me/api/portraits/men/14.jpg",
-"gender": "Male"
-},
-{
-"first_name": "nolan",
-"last_name": "gautier",
-"image": "https://randomuser.me/api/portraits/men/6.jpg",
-"gender": "Male"
-},
-{
-"first_name": "agathe",
-"last_name": "menard",
-"image": "https://randomuser.me/api/portraits/women/69.jpg",
-"gender": "Female"
-},
-{
-"first_name": "gaëtan",
-"last_name": "leclerc",
-"image": "https://randomuser.me/api/portraits/men/60.jpg",
-"gender": "Male"
-},
-{
-"first_name": "clarisse",
-"last_name": "lemaire",
-"image": "https://randomuser.me/api/portraits/women/21.jpg",
-"gender": "Female"
-},
-{
-"first_name": "samuel",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/men/16.jpg",
-"gender": "Male"
-},
-{
-"first_name": "eden",
-"last_name": "fontai",
-"image": "https://randomuser.me/api/portraits/women/17.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maëva",
-"last_name": "pierre",
-"image": "https://randomuser.me/api/portraits/women/19.jpg",
-"gender": "Female"
-},
-{
-"first_name": "thomas",
-"last_name": "barbier",
-"image": "https://randomuser.me/api/portraits/men/31.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lily",
-"last_name": "lefebvre",
-"image": "https://randomuser.me/api/portraits/women/76.jpg",
-"gender": "Female"
-},
-{
-"first_name": "lise",
-"last_name": "perez",
-"image": "https://randomuser.me/api/portraits/women/74.jpg",
-"gender": "Female"
-},
-{
-"first_name": "mila",
-"last_name": "moulin",
-"image": "https://randomuser.me/api/portraits/women/43.jpg",
-"gender": "Female"
-},
-{
-"first_name": "dylan",
-"last_name": "picard",
-"image": "https://randomuser.me/api/portraits/men/37.jpg",
-"gender": "Male"
-},
-{
-"first_name": "amandine",
-"last_name": "rodriguez",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "diego",
-"last_name": "girard",
-"image": "https://randomuser.me/api/portraits/men/84.jpg",
-"gender": "Male"
-},
-{
-"first_name": "elouan",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/men/94.jpg",
-"gender": "Male"
-},
-{
-"first_name": "apolline",
-"last_name": "fleury",
-"image": "https://randomuser.me/api/portraits/women/65.jpg",
-"gender": "Female"
-},
-{
-"first_name": "coline",
-"last_name": "menard",
-"image": "https://randomuser.me/api/portraits/women/83.jpg",
-"gender": "Female"
-},
-{
-"first_name": "maëly",
-"last_name": "le gall",
-"image": "https://randomuser.me/api/portraits/women/60.jpg",
-"gender": "Female"
-},
-{
-"first_name": "justin",
-"last_name": "robert",
-"image": "https://randomuser.me/api/portraits/men/20.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ryan",
-"last_name": "faure",
-"image": "https://randomuser.me/api/portraits/men/16.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ninon",
-"last_name": "brunet",
-"image": "https://randomuser.me/api/portraits/women/68.jpg",
-"gender": "Female"
-},
-{
-"first_name": "tessa",
-"last_name": "garnier",
-"image": "https://randomuser.me/api/portraits/women/54.jpg",
-"gender": "Female"
-},
-{
-"first_name": "ryan",
-"last_name": "bonnet",
-"image": "https://randomuser.me/api/portraits/men/28.jpg",
-"gender": "Male"
-},
-{
-"first_name": "aurélien",
-"last_name": "andre",
-"image": "https://randomuser.me/api/portraits/men/29.jpg",
-"gender": "Male"
-},
-{
-"first_name": "clément",
-"last_name": "dumas",
-"image": "https://randomuser.me/api/portraits/men/10.jpg",
-"gender": "Male"
-},
-{
-"first_name": "alexis",
-"last_name": "fournier",
-"image": "https://randomuser.me/api/portraits/men/83.jpg",
-"gender": "Male"
-},
-{
-"first_name": "valentin",
-"last_name": "lecomte",
-"image": "https://randomuser.me/api/portraits/men/44.jpg",
-"gender": "Male"
-},
-{
-"first_name": "florian",
-"last_name": "olivier",
-"image": "https://randomuser.me/api/portraits/men/36.jpg",
-"gender": "Male"
-},
-{
-"first_name": "ewen",
-"last_name": "lefebvre",
-"image": "https://randomuser.me/api/portraits/men/32.jpg",
-"gender": "Male"
-},
-{
-"first_name": "titouan",
-"last_name": "charles",
-"image": "https://randomuser.me/api/portraits/men/59.jpg",
-"gender": "Male"
-},
-{
-"first_name": "lila",
-"last_name": "aubert",
-"image": "https://randomuser.me/api/portraits/women/6.jpg",
-"gender": "Female"
-},
-{
-"first_name": "charline",
-"last_name": "caron",
-"image": "https://randomuser.me/api/portraits/women/49.jpg",
-"gender": "Female"
-},
-{
-"first_name": "soren",
-"last_name": "le gall",
-"image": "https://randomuser.me/api/portraits/men/77.jpg",
-"gender": "Male"
-},
-{
-"first_name": "fanny",
-"last_name": "louis",
-"image": "https://randomuser.me/api/portraits/women/90.jpg",
-"gender": "Female"
-},
-{
-"first_name": "julie",
-"last_name": "adam",
-"image": "https://randomuser.me/api/portraits/women/34.jpg",
-"gender": "Female"
-},
-{
-"first_name": "louka",
-"last_name": "boyer",
-"image": "https://randomuser.me/api/portraits/men/98.jpg",
-"gender": "Male"
-}
-]
diff --git a/erpnext/demo/data/room.json b/erpnext/demo/data/room.json
deleted file mode 100644
index 82f0868..0000000
--- a/erpnext/demo/data/room.json
+++ /dev/null
@@ -1,122 +0,0 @@
-[
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 1",
-		"room_number": "101",
-		"seating_capacity": 80
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 2",
-		"room_number": "102",
-		"seating_capacity": 80
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 3",
-		"room_number": "103",
-		"seating_capacity": 80
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 4",
-		"room_number": "104",
-		"seating_capacity": 80
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 4",
-		"room_number": "104",
-		"seating_capacity": 80
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 5",
-		"room_number": "201",
-		"seating_capacity": 120
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 6",
-		"room_number": "202",
-		"seating_capacity": 120
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Lecture Hall 7",
-		"room_number": "203",
-		"seating_capacity": 120
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Computer Lab 1",
-		"room_number": "301",
-		"seating_capacity": 40
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Computer Lab 2",
-		"room_number": "302",
-		"seating_capacity": 60
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Seminar Hall 1",
-		"room_number": "303",
-		"seating_capacity": 240
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Auditorium",
-		"room_number": "400",
-		"seating_capacity": 450
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 1",
-		"room_number": "560",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 2",
-		"room_number": "561",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 2",
-		"room_number": "562",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 3",
-		"room_number": "563",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 4",
-		"room_number": "564",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 5",
-		"room_number": "565",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 6",
-		"room_number": "566",
-		"seating_capacity": 70
-	},
-	{
-		"doctype": "Room",
-		"room_name": "Exam hall 7",
-		"room_number": "567",
-		"seating_capacity": 70
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/student_batch_name.json b/erpnext/demo/data/student_batch_name.json
deleted file mode 100644
index ef3f18d..0000000
--- a/erpnext/demo/data/student_batch_name.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
-	{
-		"doctype": "Student Batch Name",
-		"batch_name": "Section-A"
-	},
-	{
-		"doctype": "Student Batch Name",
-		"batch_name": "Section-B"
-	}
-]
\ No newline at end of file
diff --git a/erpnext/demo/data/user.json b/erpnext/demo/data/user.json
deleted file mode 100644
index 9ee5e78..0000000
--- a/erpnext/demo/data/user.json
+++ /dev/null
@@ -1,112 +0,0 @@
-[
- {
-  "email": "test_demo@erpnext.com",
-  "first_name": "Test",
-  "last_name": "User"
- },
- {
-  "email": "DianaPrince@example.com",
-  "first_name": "Diana",
-  "last_name": "Prince"
- },
- {
-  "email": "ZatannaZatara@example.com",
-  "first_name": "Zatanna",
-  "last_name": "Zatara"
- },
- {
-  "email": "HollyGranger@example.com",
-  "first_name": "Holly",
-  "last_name": "Granger"
- },
- {
-  "email": "NeptuniaAquaria@example.com",
-  "first_name": "Neptunia",
-  "last_name": "Aquaria"
- },
- {
-  "email": "ArthurCurry@example.com",
-  "first_name": "Arthur",
-  "last_name": "Curry"
- },
- {
-  "email": "ThaliaAlGhul@example.com",
-  "first_name": "Thalia",
-  "last_name": "Al Ghul"
- },
- {
-  "email": "MaxwellLord@example.com",
-  "first_name": "Maxwell",
-  "last_name": "Lord"
- },
- {
-  "email": "GraceChoi@example.com",
-  "first_name": "Grace",
-  "last_name": "Choi"
- },
- {
-  "email": "VandalSavage@example.com",
-  "first_name": "Vandal",
-  "last_name": "Savage"
- },
- {
-  "email": "CaitlinSnow@example.com",
-  "first_name": "Caitlin",
-  "last_name": "Snow"
- },
- {
-  "email": "RipHunter@example.com",
-  "first_name": "Rip",
-  "last_name": "Hunter"
- },
- {
-  "email": "NicholasFury@example.com",
-  "first_name": "Nicholas",
-  "last_name": "Fury"
- },
- {
-  "email": "PeterParker@example.com",
-  "first_name": "Peter",
-  "last_name": "Parker"
- },
- {
-  "email": "JohnConstantine@example.com",
-  "first_name": "John",
-  "last_name": "Constantine"
- },
- {
-  "email": "HalJordan@example.com",
-  "first_name": "Hal",
-  "last_name": "Jordan"
- },
- {
-  "email": "VictorStone@example.com",
-  "first_name": "Victor",
-  "last_name": "Stone"
- },
- {
-  "email": "BruceWayne@example.com",
-  "first_name": "Bruce",
-  "last_name": "Wayne"
- },
- {
-  "email": "ClarkKent@example.com",
-  "first_name": "Clark",
-  "last_name": "Kent"
- },
- {
-  "email": "BarryAllen@example.com",
-  "first_name": "Barry",
-  "last_name": "Allen"
- },
- {
-  "email": "KaraZorEl@example.com",
-  "first_name": "Kara",
-  "last_name": "Zor El"
- },
- {
-  "email": "demo@erpnext.com",
-  "first_name": "Demo",
-  "last_name": "User"
- }
-]
\ No newline at end of file
diff --git a/erpnext/demo/demo.py b/erpnext/demo/demo.py
deleted file mode 100644
index 4a18a99..0000000
--- a/erpnext/demo/demo.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import sys
-
-import frappe
-import frappe.utils
-
-import erpnext
-from erpnext.demo.setup import education, manufacture, retail, setup_data
-from erpnext.demo.user import accounts
-from erpnext.demo.user import education as edu
-from erpnext.demo.user import fixed_asset, hr, manufacturing, projects, purchase, sales, stock
-
-"""
-Make a demo
-
-1. Start with a fresh account
-
-bench --site demo.erpnext.dev reinstall
-
-2. Install Demo
-
-bench --site demo.erpnext.dev execute erpnext.demo.demo.make
-
-3. If Demo breaks, to continue
-
-bench --site demo.erpnext.dev execute erpnext.demo.demo.simulate
-
-"""
-
-def make(domain='Manufacturing', days=100):
-	frappe.flags.domain = domain
-	frappe.flags.mute_emails = True
-	setup_data.setup(domain)
-	if domain== 'Manufacturing':
-		manufacture.setup_data()
-	elif domain == "Retail":
-		retail.setup_data()
-	elif domain== 'Education':
-		education.setup_data()
-
-	site = frappe.local.site
-	frappe.destroy()
-	frappe.init(site)
-	frappe.connect()
-
-	simulate(domain, days)
-
-def simulate(domain='Manufacturing', days=100):
-	runs_for = frappe.flags.runs_for or days
-	frappe.flags.company = erpnext.get_default_company()
-	frappe.flags.mute_emails = True
-
-	if not frappe.flags.start_date:
-		# start date = 100 days back
-		frappe.flags.start_date = frappe.utils.add_days(frappe.utils.nowdate(),
-			-1 * runs_for)
-
-	current_date = frappe.utils.getdate(frappe.flags.start_date)
-
-	# continue?
-	demo_last_date = frappe.db.get_global('demo_last_date')
-	if demo_last_date:
-		current_date = frappe.utils.add_days(frappe.utils.getdate(demo_last_date), 1)
-
-	# run till today
-	if not runs_for:
-		runs_for = frappe.utils.date_diff(frappe.utils.nowdate(), current_date)
-		# runs_for = 100
-
-	fixed_asset.work()
-	for i in range(runs_for):
-		sys.stdout.write("\rSimulating {0}: Day {1}".format(
-			current_date.strftime("%Y-%m-%d"), i))
-		sys.stdout.flush()
-		frappe.flags.current_date = current_date
-		if current_date.weekday() in (5, 6):
-			current_date = frappe.utils.add_days(current_date, 1)
-			continue
-		try:
-			hr.work()
-			purchase.work()
-			stock.work()
-			accounts.work()
-			projects.run_projects(current_date)
-			sales.work(domain)
-			# run_messages()
-
-			if domain=='Manufacturing':
-				manufacturing.work()
-			elif domain=='Education':
-				edu.work()
-
-		except Exception:
-			frappe.db.set_global('demo_last_date', current_date)
-			raise
-		finally:
-			current_date = frappe.utils.add_days(current_date, 1)
-			frappe.db.commit()
diff --git a/erpnext/demo/domains.py b/erpnext/demo/domains.py
deleted file mode 100644
index 346787e..0000000
--- a/erpnext/demo/domains.py
+++ /dev/null
@@ -1,20 +0,0 @@
-data = {
-	'Manufacturing': {
-		'company_name': 'Wind Power LLC'
-	},
-	'Retail': {
-		'company_name': 'Mobile Next',
-	},
-	'Distribution': {
-		'company_name': 'Soltice Hardware',
-	},
-	'Services': {
-		'company_name': 'Acme Consulting'
-	},
-	'Education': {
-		'company_name': 'Whitmore College'
-	},
-	'Non Profit': {
-		'company_name': 'Erpnext Foundation'
-	}
-}
diff --git a/erpnext/demo/setup/__init__.py b/erpnext/demo/setup/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/demo/setup/__init__.py
+++ /dev/null
diff --git a/erpnext/demo/setup/education.py b/erpnext/demo/setup/education.py
deleted file mode 100644
index eb833f4..0000000
--- a/erpnext/demo/setup/education.py
+++ /dev/null
@@ -1,181 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import json
-import random
-from datetime import datetime
-
-import frappe
-from frappe.utils.make_random import get_random
-
-from erpnext.demo.setup.setup_data import import_json
-
-
-def setup_data():
-	frappe.flags.mute_emails = True
-	make_masters()
-	setup_item()
-	make_student_applicants()
-	make_student_group()
-	make_fees_category()
-	make_fees_structure()
-	make_assessment_groups()
-	frappe.db.commit()
-	frappe.clear_cache()
-
-def make_masters():
-	import_json("Room")
-	import_json("Department")
-	import_json("Instructor")
-	import_json("Course")
-	import_json("Program")
-	import_json("Student Batch Name")
-	import_json("Assessment Criteria")
-	import_json("Grading Scale")
-	frappe.db.commit()
-
-def setup_item():
-	items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item_education.json')).read())
-	for i in items:
-		item = frappe.new_doc('Item')
-		item.update(i)
-		item.min_order_qty = random.randint(10, 30)
-		item.item_defaults[0].default_warehouse = frappe.get_all('Warehouse',
-			filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)[0].name
-		item.insert()
-
-def make_student_applicants():
-	blood_group = ["A+", "A-", "B+", "B-", "AB+", "AB-", "O+", "O-"]
-	male_names = []
-	female_names = []
-
-	file_path = get_json_path("Random Student Data")
-	with open(file_path, "r") as open_file:
-		random_student_data = json.loads(open_file.read())
-		count = 1
-
-		for d in random_student_data:
-			if d.get('gender') == "Male":
-				male_names.append(d.get('first_name').title())
-
-			if d.get('gender') == "Female":
-				female_names.append(d.get('first_name').title())
-
-		for idx, d in enumerate(random_student_data):
-			student_applicant = frappe.new_doc("Student Applicant")
-			student_applicant.first_name = d.get('first_name').title()
-			student_applicant.last_name = d.get('last_name').title()
-			student_applicant.image = d.get('image')
-			student_applicant.gender = d.get('gender')
-			student_applicant.program = get_random("Program")
-			student_applicant.blood_group = random.choice(blood_group)
-			year = random.randint(1990, 1998)
-			month = random.randint(1, 12)
-			day = random.randint(1, 28)
-			student_applicant.date_of_birth = datetime(year, month, day)
-			student_applicant.mother_name = random.choice(female_names) + " " + d.get('last_name').title()
-			student_applicant.father_name = random.choice(male_names) + " " + d.get('last_name').title()
-			if student_applicant.gender == "Male":
-				student_applicant.middle_name = random.choice(male_names)
-			else:
-				student_applicant.middle_name = random.choice(female_names)
-			student_applicant.student_email_id = d.get('first_name') + "_" + \
-				student_applicant.middle_name + "_" + d.get('last_name') + "@example.com"
-			if count <5:
-				student_applicant.insert()
-				frappe.db.commit()
-			else:
-				student_applicant.submit()
-				frappe.db.commit()
-			count+=1
-
-def make_student_group():
-	for term in frappe.db.get_list("Academic Term"):
-		for program in frappe.db.get_list("Program"):
-			sg_tool = frappe.new_doc("Student Group Creation Tool")
-			sg_tool.academic_year = "2017-18"
-			sg_tool.academic_term = term.name
-			sg_tool.program = program.name
-			for d in sg_tool.get_courses():
-				d = frappe._dict(d)
-				student_group = frappe.new_doc("Student Group")
-				student_group.student_group_name = d.student_group_name
-				student_group.group_based_on = d.group_based_on
-				student_group.program = program.name
-				student_group.course = d.course
-				student_group.batch = d.batch
-				student_group.academic_term = term.name
-				student_group.academic_year = "2017-18"
-				student_group.save()
-			frappe.db.commit()
-
-def make_fees_category():
-	fee_type = ["Tuition Fee", "Hostel Fee", "Logistics Fee",
-				"Medical Fee", "Mess Fee", "Security Deposit"]
-
-	fee_desc = {"Tuition Fee" : "Curricular activities which includes books, notebooks and faculty charges" ,
-				"Hostel Fee" : "Stay of students in institute premises",
-				"Logistics Fee" : "Lodging boarding of the students" ,
-				"Medical Fee" : "Medical welfare of the students",
-				"Mess Fee" : "Food and beverages for your ward",
-				"Security Deposit" : "In case your child is found to have damaged institutes property"
-				}
-
-	for i in fee_type:
-		fee_category = frappe.new_doc("Fee Category")
-		fee_category.category_name = i
-		fee_category.description = fee_desc[i]
-		fee_category.insert()
-		frappe.db.commit()
-
-def make_fees_structure():
-	for d in frappe.db.get_list("Program"):
-		program = frappe.get_doc("Program", d.name)
-		for academic_term in ["2017-18 (Semester 1)", "2017-18 (Semester 2)", "2017-18 (Semester 3)"]:
-			fee_structure = frappe.new_doc("Fee Structure")
-			fee_structure.program = d.name
-			fee_structure.academic_term = random.choice(frappe.db.get_list("Academic Term")).name
-			for j in range(1,4):
-				temp = {"fees_category": random.choice(frappe.db.get_list("Fee Category")).name , "amount" : random.randint(500,1000)}
-				fee_structure.append("components", temp)
-			fee_structure.insert()
-			program.append("fees", {"academic_term": academic_term, "fee_structure": fee_structure.name, "amount": fee_structure.total_amount})
-		program.save()
-	frappe.db.commit()
-
-def make_assessment_groups():
-	for year in frappe.db.get_list("Academic Year"):
-		ag = frappe.new_doc('Assessment Group')
-		ag.assessment_group_name = year.name
-		ag.parent_assessment_group = "All Assessment Groups"
-		ag.is_group = 1
-		ag.insert()
-		for term in frappe.db.get_list("Academic Term", filters = {"academic_year": year.name}):
-			ag1 = frappe.new_doc('Assessment Group')
-			ag1.assessment_group_name = term.name
-			ag1.parent_assessment_group = ag.name
-			ag1.is_group = 1
-			ag1.insert()
-			for assessment_group in ['Term I', 'Term II']:
-				ag2 = frappe.new_doc('Assessment Group')
-				ag2.assessment_group_name = ag1.name + " " + assessment_group
-				ag2.parent_assessment_group = ag1.name
-				ag2.insert()
-	frappe.db.commit()
-
-
-def get_json_path(doctype):
-		return frappe.get_app_path('erpnext', 'demo', 'data', frappe.scrub(doctype) + '.json')
-
-def weighted_choice(weights):
-	totals = []
-	running_total = 0
-
-	for w in weights:
-		running_total += w
-		totals.append(running_total)
-
-	rnd = random.random() * running_total
-	for i, total in enumerate(totals):
-		if rnd < total:
-			return i
diff --git a/erpnext/demo/setup/manufacture.py b/erpnext/demo/setup/manufacture.py
deleted file mode 100644
index fe1a1fb..0000000
--- a/erpnext/demo/setup/manufacture.py
+++ /dev/null
@@ -1,140 +0,0 @@
-import json
-import random
-
-import frappe
-from frappe.utils import add_days, nowdate
-
-from erpnext.demo.domains import data
-from erpnext.demo.setup.setup_data import import_json
-
-
-def setup_data():
-	import_json("Location")
-	import_json("Asset Category")
-	setup_item()
-	setup_workstation()
-	setup_asset()
-	import_json('Operation')
-	setup_item_price()
-	show_item_groups_in_website()
-	import_json('BOM', submit=True)
-	frappe.db.commit()
-	frappe.clear_cache()
-
-def setup_workstation():
-	workstations = [u'Drilling Machine 1', u'Lathe 1', u'Assembly Station 1', u'Assembly Station 2', u'Packing and Testing Station']
-	for w in workstations:
-		frappe.get_doc({
-			"doctype": "Workstation",
-			"workstation_name": w,
-			"holiday_list": frappe.get_all("Holiday List")[0].name,
-			"hour_rate_consumable": int(random.random() * 20),
-			"hour_rate_electricity": int(random.random() * 10),
-			"hour_rate_labour": int(random.random() * 40),
-			"hour_rate_rent": int(random.random() * 10),
-			"working_hours": [
-				{
-					"enabled": 1,
-				    "start_time": "8:00:00",
-					"end_time": "15:00:00"
-				}
-			]
-		}).insert()
-
-def show_item_groups_in_website():
-	"""set show_in_website=1 for Item Groups"""
-	products = frappe.get_doc("Item Group", "Products")
-	products.show_in_website = 1
-	products.route = 'products'
-	products.save()
-
-def setup_asset():
-	assets = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'asset.json')).read())
-	for d in assets:
-		asset = frappe.new_doc('Asset')
-		asset.update(d)
-		asset.purchase_date = add_days(nowdate(), -random.randint(20, 1500))
-		asset.next_depreciation_date = add_days(asset.purchase_date, 30)
-		asset.warehouse = "Stores - WPL"
-		asset.set_missing_values()
-		asset.make_depreciation_schedule()
-		asset.flags.ignore_validate = True
-		asset.flags.ignore_mandatory = True
-		asset.save()
-		asset.submit()
-
-def setup_item():
-	items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item.json')).read())
-	for i in items:
-		item = frappe.new_doc('Item')
-		item.update(i)
-		if hasattr(item, 'item_defaults') and item.item_defaults[0].default_warehouse:
-			item.item_defaults[0].company = data.get("Manufacturing").get('company_name')
-			warehouse = frappe.get_all('Warehouse', filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)
-			if warehouse:
-				item.item_defaults[0].default_warehouse = warehouse[0].name
-		item.insert()
-
-def setup_product_bundle():
-	frappe.get_doc({
-		'doctype': 'Product Bundle',
-		'new_item_code': 'Wind Mill A Series with Spare Bearing',
-		'items': [
-			{'item_code': 'Wind Mill A Series', 'qty': 1},
-			{'item_code': 'Bearing Collar', 'qty': 1},
-			{'item_code': 'Bearing Assembly', 'qty': 1},
-		]
-	}).insert()
-
-def setup_item_price():
-	frappe.db.sql("delete from `tabItem Price`")
-
-	standard_selling = {
-		"Base Bearing Plate": 28,
-		"Base Plate": 21,
-		"Bearing Assembly": 300,
-		"Bearing Block": 14,
-		"Bearing Collar": 103.6,
-		"Bearing Pipe": 63,
-		"Blade Rib": 46.2,
-		"Disc Collars": 42,
-		"External Disc": 56,
-		"Internal Disc": 70,
-		"Shaft": 340,
-		"Stand": 400,
-		"Upper Bearing Plate": 300,
-		"Wind Mill A Series": 320,
-		"Wind Mill A Series with Spare Bearing": 750,
-		"Wind MIll C Series": 400,
-		"Wind Turbine": 400,
-		"Wing Sheet": 30.8
-	}
-
-	standard_buying = {
-		"Base Bearing Plate": 20,
-		"Base Plate": 28,
-		"Base Plate Un Painted": 16,
-		"Bearing Block": 13,
-		"Bearing Collar": 96.4,
-		"Bearing Pipe": 55,
-		"Blade Rib": 38,
-		"Disc Collars": 34,
-		"External Disc": 50,
-		"Internal Disc": 60,
-		"Shaft": 250,
-		"Stand": 300,
-		"Upper Bearing Plate": 200,
-		"Wing Sheet": 25
-	}
-
-	for price_list in ("standard_buying", "standard_selling"):
-		for item, rate in locals().get(price_list).items():
-			frappe.get_doc({
-				"doctype": "Item Price",
-				"price_list": price_list.replace("_", " ").title(),
-				"item_code": item,
-				"selling": 1 if price_list=="standard_selling" else 0,
-				"buying": 1 if price_list=="standard_buying" else 0,
-				"price_list_rate": rate,
-				"currency": "USD"
-			}).insert()
diff --git a/erpnext/demo/setup/retail.py b/erpnext/demo/setup/retail.py
deleted file mode 100644
index 0469264..0000000
--- a/erpnext/demo/setup/retail.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import json
-
-import frappe
-
-from erpnext.demo.domains import data
-
-
-def setup_data():
-	setup_item()
-	setup_item_price()
-	frappe.db.commit()
-	frappe.clear_cache()
-
-def setup_item():
-	items = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'item.json')).read())
-	for i in items:
-		if not i.get("domain") == "Retail": continue
-		item = frappe.new_doc('Item')
-		item.update(i)
-		if hasattr(item, 'item_defaults') and item.item_defaults[0].default_warehouse:
-			item.item_defaults[0].company = data.get("Retail").get('company_name')
-			warehouse = frappe.get_all('Warehouse', filters={'warehouse_name': item.item_defaults[0].default_warehouse}, limit=1)
-			if warehouse:
-				item.item_defaults[0].default_warehouse = warehouse[0].name
-		item.insert()
-
-def setup_item_price():
-	frappe.db.sql("delete from `tabItem Price`")
-
-	standard_selling = {
-		"OnePlus 6": 579,
-		"OnePlus 6T": 600,
-		"Xiaomi Poco F1": 300,
-		"Iphone XS": 999,
-		"Samsung Galaxy S9": 720,
-		"Sony Bluetooth Headphone": 99,
-		"Xiaomi Phone Repair": 10,
-		"Samsung Phone Repair": 20,
-		"OnePlus Phone Repair": 15,
-		"Apple Phone Repair": 30,
-	}
-
-	standard_buying = {
-		"OnePlus 6": 300,
-		"OnePlus 6T": 350,
-		"Xiaomi Poco F1": 200,
-		"Iphone XS": 600,
-		"Samsung Galaxy S9": 500,
-		"Sony Bluetooth Headphone": 69
-	}
-
-	for price_list in ("standard_buying", "standard_selling"):
-		for item, rate in locals().get(price_list).items():
-			frappe.get_doc({
-				"doctype": "Item Price",
-				"price_list": price_list.replace("_", " ").title(),
-				"item_code": item,
-				"selling": 1 if price_list=="standard_selling" else 0,
-				"buying": 1 if price_list=="standard_buying" else 0,
-				"price_list_rate": rate,
-				"currency": "USD"
-			}).insert()
diff --git a/erpnext/demo/setup/setup_data.py b/erpnext/demo/setup/setup_data.py
deleted file mode 100644
index 7137c6e..0000000
--- a/erpnext/demo/setup/setup_data.py
+++ /dev/null
@@ -1,447 +0,0 @@
-import json
-import random
-
-import frappe
-from frappe import _
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from frappe.utils import cstr, flt, now_datetime, random_string
-from frappe.utils.make_random import add_random_children, get_random
-from frappe.utils.nestedset import get_root_of
-
-import erpnext
-from erpnext.demo.domains import data
-
-
-def setup(domain):
-	frappe.flags.in_demo = 1
-	complete_setup(domain)
-	setup_demo_page()
-	setup_fiscal_year()
-	setup_holiday_list()
-	setup_user()
-	setup_employee()
-	setup_user_roles(domain)
-	setup_role_permissions()
-	setup_custom_field_for_domain()
-
-	employees = frappe.get_all('Employee',  fields=['name', 'date_of_joining'])
-
-	# monthly salary
-	setup_salary_structure(employees[:5], 0)
-
-	# based on timesheet
-	setup_salary_structure(employees[5:], 1)
-
-	setup_leave_allocation()
-	setup_customer()
-	setup_supplier()
-	setup_warehouse()
-	import_json('Address')
-	import_json('Contact')
-	import_json('Lead')
-	setup_currency_exchange()
-	#setup_mode_of_payment()
-	setup_account_to_expense_type()
-	setup_budget()
-	setup_pos_profile()
-
-	frappe.db.commit()
-	frappe.clear_cache()
-
-def complete_setup(domain='Manufacturing'):
-	print("Complete Setup...")
-	from frappe.desk.page.setup_wizard.setup_wizard import setup_complete
-
-	if not frappe.get_all('Company', limit=1):
-		setup_complete({
-			"full_name": "Test User",
-			"email": "test_demo@erpnext.com",
-			"company_tagline": 'Awesome Products and Services',
-			"password": "demo",
-			"fy_start_date": "2015-01-01",
-			"fy_end_date": "2015-12-31",
-			"bank_account": "National Bank",
-			"domains": [domain],
-			"company_name": data.get(domain).get('company_name'),
-			"chart_of_accounts": "Standard",
-			"company_abbr": ''.join([d[0] for d in data.get(domain).get('company_name').split()]).upper(),
-			"currency": 'USD',
-			"timezone": 'America/New_York',
-			"country": 'United States',
-			"language": "english"
-		})
-
-		company = erpnext.get_default_company()
-
-		if company:
-			company_doc = frappe.get_doc("Company", company)
-			company_doc.db_set('default_payroll_payable_account',
-				frappe.db.get_value('Account', dict(account_name='Payroll Payable')))
-
-def setup_demo_page():
-	# home page should always be "start"
-	website_settings = frappe.get_doc("Website Settings", "Website Settings")
-	website_settings.home_page = "demo"
-	website_settings.save()
-
-def setup_fiscal_year():
-	fiscal_year = None
-	for year in range(2010, now_datetime().year + 1, 1):
-		try:
-			fiscal_year = frappe.get_doc({
-				"doctype": "Fiscal Year",
-				"year": cstr(year),
-				"year_start_date": "{0}-01-01".format(year),
-				"year_end_date": "{0}-12-31".format(year)
-			}).insert()
-		except frappe.DuplicateEntryError:
-			pass
-
-	# set the last fiscal year (current year) as default
-	if fiscal_year:
-		fiscal_year.set_as_default()
-
-def setup_holiday_list():
-	"""Setup Holiday List for the current year"""
-	year = now_datetime().year
-	holiday_list = frappe.get_doc({
-		"doctype": "Holiday List",
-		"holiday_list_name": str(year),
-		"from_date": "{0}-01-01".format(year),
-		"to_date": "{0}-12-31".format(year),
-	})
-	holiday_list.insert()
-	holiday_list.weekly_off = "Saturday"
-	holiday_list.get_weekly_off_dates()
-	holiday_list.weekly_off = "Sunday"
-	holiday_list.get_weekly_off_dates()
-	holiday_list.save()
-
-	frappe.set_value("Company", erpnext.get_default_company(), "default_holiday_list", holiday_list.name)
-
-
-def setup_user():
-	frappe.db.sql('delete from tabUser where name not in ("Guest", "Administrator")')
-	for u in json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data', 'user.json')).read()):
-		user = frappe.new_doc("User")
-		user.update(u)
-		user.flags.no_welcome_mail = True
-		user.new_password = 'Demo1234567!!!'
-		user.insert()
-
-def setup_employee():
-	frappe.db.set_value("HR Settings", None, "emp_created_by", "Naming Series")
-	frappe.db.commit()
-
-	for d in frappe.get_all('Salary Component'):
-		salary_component = frappe.get_doc('Salary Component', d.name)
-		salary_component.append('accounts', dict(
-			company=erpnext.get_default_company(),
-			account=frappe.get_value('Account', dict(account_name=('like', 'Salary%')))
-		))
-		salary_component.save()
-
-	import_json('Employee')
-	holiday_list = frappe.db.get_value("Holiday List", {"holiday_list_name": str(now_datetime().year)}, 'name')
-	frappe.db.sql('''update tabEmployee set holiday_list={0}'''.format(holiday_list))
-
-def setup_salary_structure(employees, salary_slip_based_on_timesheet=0):
-	ss = frappe.new_doc('Salary Structure')
-	ss.name = "Sample Salary Structure - " + random_string(5)
-	ss.salary_slip_based_on_timesheet = salary_slip_based_on_timesheet
-
-	if salary_slip_based_on_timesheet:
-		ss.salary_component = 'Basic'
-		ss.hour_rate = flt(random.random() * 10, 2)
-	else:
-		ss.payroll_frequency = 'Monthly'
-
-	ss.payment_account = frappe.get_value('Account',
-		{'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
-
-	ss.append('earnings', {
-		'salary_component': 'Basic',
-		"abbr":'B',
-		'formula': 'base*.2',
-		'amount_based_on_formula': 1,
-		"idx": 1
-	})
-	ss.append('deductions', {
-		'salary_component': 'Income Tax',
-		"abbr":'IT',
-		'condition': 'base > 10000',
-		'formula': 'base*.1',
-		"idx": 1
-	})
-	ss.insert()
-	ss.submit()
-
-	for e in employees:
-		sa  = frappe.new_doc("Salary Structure Assignment")
-		sa.employee = e.name
-		sa.salary_structure = ss.name
-		sa.from_date = "2015-01-01"
-		sa.base = random.random() * 10000
-		sa.insert()
-		sa.submit()
-
-	return ss
-
-def setup_user_roles(domain):
-	user = frappe.get_doc('User', 'demo@erpnext.com')
-	user.add_roles('HR User', 'HR Manager', 'Accounts User', 'Accounts Manager',
-		'Stock User', 'Stock Manager', 'Sales User', 'Sales Manager', 'Purchase User',
-		'Purchase Manager', 'Projects User', 'Manufacturing User', 'Manufacturing Manager',
-		'Support Team')
-
-	if domain == "Education":
-		user.add_roles('Academics User')
-
-	if not frappe.db.get_global('demo_hr_user'):
-		user = frappe.get_doc('User', 'CaitlinSnow@example.com')
-		user.add_roles('HR User', 'HR Manager', 'Accounts User')
-		frappe.db.set_global('demo_hr_user', user.name)
-		update_employee_department(user.name, 'Human Resources')
-		for d in frappe.get_all('User Permission', filters={"user": "CaitlinSnow@example.com"}):
-			frappe.delete_doc('User Permission', d.name)
-
-	if not frappe.db.get_global('demo_sales_user_1'):
-		user = frappe.get_doc('User', 'VandalSavage@example.com')
-		user.add_roles('Sales User')
-		update_employee_department(user.name, 'Sales')
-		frappe.db.set_global('demo_sales_user_1', user.name)
-
-	if not frappe.db.get_global('demo_sales_user_2'):
-		user = frappe.get_doc('User', 'GraceChoi@example.com')
-		user.add_roles('Sales User', 'Sales Manager', 'Accounts User')
-		update_employee_department(user.name, 'Sales')
-		frappe.db.set_global('demo_sales_user_2', user.name)
-
-	if not frappe.db.get_global('demo_purchase_user'):
-		user = frappe.get_doc('User', 'MaxwellLord@example.com')
-		user.add_roles('Purchase User', 'Purchase Manager', 'Accounts User', 'Stock User')
-		update_employee_department(user.name, 'Purchase')
-		frappe.db.set_global('demo_purchase_user', user.name)
-
-	if not frappe.db.get_global('demo_manufacturing_user'):
-		user = frappe.get_doc('User', 'NeptuniaAquaria@example.com')
-		user.add_roles('Manufacturing User', 'Stock Manager', 'Stock User', 'Purchase User', 'Accounts User')
-		update_employee_department(user.name, 'Production')
-		frappe.db.set_global('demo_manufacturing_user', user.name)
-
-	if not frappe.db.get_global('demo_stock_user'):
-		user = frappe.get_doc('User', 'HollyGranger@example.com')
-		user.add_roles('Manufacturing User', 'Stock User', 'Purchase User', 'Accounts User')
-		update_employee_department(user.name, 'Production')
-		frappe.db.set_global('demo_stock_user', user.name)
-
-	if not frappe.db.get_global('demo_accounts_user'):
-		user = frappe.get_doc('User', 'BarryAllen@example.com')
-		user.add_roles('Accounts User', 'Accounts Manager', 'Sales User', 'Purchase User')
-		update_employee_department(user.name, 'Accounts')
-		frappe.db.set_global('demo_accounts_user', user.name)
-
-	if not frappe.db.get_global('demo_projects_user'):
-		user = frappe.get_doc('User', 'PeterParker@example.com')
-		user.add_roles('HR User', 'Projects User')
-		update_employee_department(user.name, 'Management')
-		frappe.db.set_global('demo_projects_user', user.name)
-
-	if domain == "Education":
-		if not frappe.db.get_global('demo_education_user'):
-			user = frappe.get_doc('User', 'ArthurCurry@example.com')
-			user.add_roles('Academics User')
-			update_employee_department(user.name, 'Management')
-			frappe.db.set_global('demo_education_user', user.name)
-
-	#Add Expense Approver
-	user = frappe.get_doc('User', 'ClarkKent@example.com')
-	user.add_roles('Expense Approver')
-
-def setup_leave_allocation():
-	year = now_datetime().year
-	for employee in frappe.get_all('Employee', fields=['name']):
-		leave_types = frappe.get_all("Leave Type", fields=['name', 'max_continuous_days_allowed'])
-		for leave_type in leave_types:
-			if not leave_type.max_continuous_days_allowed:
-				leave_type.max_continuous_days_allowed = 10
-
-		leave_allocation = frappe.get_doc({
-			"doctype": "Leave Allocation",
-			"employee": employee.name,
-			"from_date": "{0}-01-01".format(year),
-			"to_date": "{0}-12-31".format(year),
-			"leave_type": leave_type.name,
-			"new_leaves_allocated": random.randint(1, int(leave_type.max_continuous_days_allowed))
-		})
-		leave_allocation.insert()
-		leave_allocation.submit()
-		frappe.db.commit()
-
-def setup_customer():
-	customers = [u'Asian Junction', u'Life Plan Counselling', u'Two Pesos', u'Mr Fables', u'Intelacard', u'Big D Supermarkets', u'Adaptas', u'Nelson Brothers', u'Landskip Yard Care', u'Buttrey Food & Drug', u'Fayva', u'Asian Fusion', u'Crafts Canada', u'Consumers and Consumers Express', u'Netobill', u'Choices', u'Chi-Chis', u'Red Food', u'Endicott Shoes', u'Hind Enterprises']
-	for c in customers:
-		frappe.get_doc({
-			"doctype": "Customer",
-			"customer_name": c,
-			"customer_group": "Commercial",
-			"customer_type": random.choice(["Company", "Individual"]),
-			"territory": "Rest Of The World"
-		}).insert()
-
-def setup_supplier():
-	suppliers = [u'Helios Air', u'Ks Merchandise', u'HomeBase', u'Scott Ties', u'Reliable Investments', u'Nan Duskin', u'Rainbow Records', u'New World Realty', u'Asiatic Solutions', u'Eagle Hardware', u'Modern Electricals']
-	for s in suppliers:
-		frappe.get_doc({
-			"doctype": "Supplier",
-			"supplier_name": s,
-			"supplier_group": random.choice(["Services", "Raw Material"]),
-		}).insert()
-
-def setup_warehouse():
-	w = frappe.new_doc('Warehouse')
-	w.warehouse_name = 'Supplier'
-	w.insert()
-
-def setup_currency_exchange():
-	frappe.get_doc({
-		'doctype': 'Currency Exchange',
-		'from_currency': 'EUR',
-		'to_currency': 'USD',
-		'exchange_rate': 1.13
-	}).insert()
-
-	frappe.get_doc({
-		'doctype': 'Currency Exchange',
-		'from_currency': 'CNY',
-		'to_currency': 'USD',
-		'exchange_rate': 0.16
-	}).insert()
-
-def setup_mode_of_payment():
-	company_abbr = frappe.get_cached_value('Company',  erpnext.get_default_company(),  "abbr")
-	account_dict = {'Cash': 'Cash - '+ company_abbr , 'Bank': 'National Bank - '+ company_abbr}
-	for payment_mode in frappe.get_all('Mode of Payment', fields = ["name", "type"]):
-		if payment_mode.type:
-			mop = frappe.get_doc('Mode of Payment', payment_mode.name)
-			mop.append('accounts', {
-				'company': erpnext.get_default_company(),
-				'default_account': account_dict.get(payment_mode.type)
-			})
-			mop.save(ignore_permissions=True)
-
-def setup_account():
-	frappe.flags.in_import = True
-	data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',
-		'account.json')).read())
-	for d in data:
-		doc = frappe.new_doc('Account')
-		doc.update(d)
-		doc.parent_account = frappe.db.get_value('Account', {'account_name': doc.parent_account})
-		doc.insert()
-
-	frappe.flags.in_import = False
-
-def setup_account_to_expense_type():
-	company_abbr = frappe.get_cached_value('Company',  erpnext.get_default_company(),  "abbr")
-	expense_types = [{'name': _('Calls'), "account": "Sales Expenses - "+ company_abbr},
-		{'name': _('Food'), "account": "Entertainment Expenses - "+ company_abbr},
-		{'name': _('Medical'), "account": "Utility Expenses - "+ company_abbr},
-		{'name': _('Others'), "account": "Miscellaneous Expenses - "+ company_abbr},
-		{'name': _('Travel'), "account": "Travel Expenses - "+ company_abbr}]
-
-	for expense_type in expense_types:
-		doc = frappe.get_doc("Expense Claim Type", expense_type["name"])
-		doc.append("accounts", {
-			"company" : erpnext.get_default_company(),
-			"default_account" : expense_type["account"]
-		})
-		doc.save(ignore_permissions=True)
-
-def setup_budget():
-	fiscal_years = frappe.get_all("Fiscal Year", order_by="year_start_date")[-2:]
-
-	for fy in fiscal_years:
-		budget = frappe.new_doc("Budget")
-		budget.cost_center = get_random("Cost Center")
-		budget.fiscal_year = fy.name
-		budget.action_if_annual_budget_exceeded = "Warn"
-		expense_ledger_count = frappe.db.count("Account", {"is_group": "0", "root_type": "Expense"})
-
-		add_random_children(budget, "accounts", rows=random.randint(10, expense_ledger_count),
-			randomize = {
-				"account": ("Account", {"is_group": "0", "root_type": "Expense"})
-			}, unique="account")
-
-		for d in budget.accounts:
-			d.budget_amount = random.randint(5, 100) * 10000
-
-		budget.save()
-		budget.submit()
-
-def setup_pos_profile():
-	company_abbr = frappe.get_cached_value('Company',  erpnext.get_default_company(),  "abbr")
-	pos = frappe.new_doc('POS Profile')
-	pos.user = frappe.db.get_global('demo_accounts_user')
-	pos.name = "Demo POS Profile"
-	pos.naming_series = 'SINV-'
-	pos.update_stock = 0
-	pos.write_off_account = 'Cost of Goods Sold - '+ company_abbr
-	pos.write_off_cost_center = 'Main - '+ company_abbr
-	pos.customer_group = get_root_of('Customer Group')
-	pos.territory = get_root_of('Territory')
-
-	pos.append('payments', {
-		'mode_of_payment': frappe.db.get_value('Mode of Payment', {'type': 'Cash'}, 'name'),
-		'amount': 0.0,
-		'default': 1
-	})
-
-	pos.insert()
-
-def setup_role_permissions():
-	role_permissions = {'Batch': ['Accounts User', 'Item Manager']}
-	for doctype, roles in role_permissions.items():
-		for role in roles:
-			if not frappe.db.get_value('Custom DocPerm',
-				{'parent': doctype, 'role': role}):
-				frappe.get_doc({
-					'doctype': 'Custom DocPerm',
-					'role': role,
-					'read': 1,
-					'write': 1,
-					'create': 1,
-					'delete': 1,
-					'parent': doctype
-				}).insert(ignore_permissions=True)
-
-def import_json(doctype, submit=False, values=None):
-	frappe.flags.in_import = True
-	data = json.loads(open(frappe.get_app_path('erpnext', 'demo', 'data',
-		frappe.scrub(doctype) + '.json')).read())
-	for d in data:
-		doc = frappe.new_doc(doctype)
-		doc.update(d)
-		doc.insert()
-		if submit:
-			doc.submit()
-
-	frappe.db.commit()
-
-	frappe.flags.in_import = False
-
-def update_employee_department(user_id, department):
-	employee = frappe.db.get_value('Employee', {"user_id": user_id}, 'name')
-	department = frappe.db.get_value('Department', {'department_name': department}, 'name')
-	frappe.db.set_value('Employee', employee, 'department', department)
-
-def setup_custom_field_for_domain():
-	field = {
-		"Item": [
-			dict(fieldname='domain', label='Domain',
-				fieldtype='Select', hidden=1, default="Manufacturing",
-				options="Manufacturing\nService\nDistribution\nRetail"
-			)
-		]
-	}
-	create_custom_fields(field)
diff --git a/erpnext/demo/user/accounts.py b/erpnext/demo/user/accounts.py
deleted file mode 100644
index 273a3f9..0000000
--- a/erpnext/demo/user/accounts.py
+++ /dev/null
@@ -1,127 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-
-import frappe
-from frappe.desk import query_report
-from frappe.utils import random_string
-from frappe.utils.make_random import get_random
-
-import erpnext
-from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry_against_invoice
-from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-from erpnext.accounts.doctype.payment_request.payment_request import (
-	make_payment_entry,
-	make_payment_request,
-)
-from erpnext.demo.user.sales import make_sales_order
-from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
-from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_invoice
-
-
-def work():
-	frappe.set_user(frappe.db.get_global('demo_accounts_user'))
-
-	if random.random() <= 0.6:
-		report = "Ordered Items to be Billed"
-		for so in list(set([r[0] for r in query_report.run(report)["result"]
-				if r[0]!="Total"]))[:random.randint(1, 5)]:
-			try:
-				si = frappe.get_doc(make_sales_invoice(so))
-				si.posting_date = frappe.flags.current_date
-				for d in si.get("items"):
-					if not d.income_account:
-						d.income_account = "Sales - {}".format(frappe.get_cached_value('Company',  si.company,  'abbr'))
-				si.insert()
-				si.submit()
-				frappe.db.commit()
-			except frappe.ValidationError:
-				pass
-
-	if random.random() <= 0.6:
-		report = "Received Items to be Billed"
-		for pr in list(set([r[0] for r in query_report.run(report)["result"]
-			if r[0]!="Total"]))[:random.randint(1, 5)]:
-			try:
-				pi = frappe.get_doc(make_purchase_invoice(pr))
-				pi.posting_date = frappe.flags.current_date
-				pi.bill_no = random_string(6)
-				pi.insert()
-				pi.submit()
-				frappe.db.commit()
-			except frappe.ValidationError:
-				pass
-
-
-	if random.random() < 0.5:
-		make_payment_entries("Sales Invoice", "Accounts Receivable")
-
-	if random.random() < 0.5:
-		make_payment_entries("Purchase Invoice", "Accounts Payable")
-
-	if random.random() < 0.4:
-		#make payment request against sales invoice
-		sales_invoice_name = get_random("Sales Invoice", filters={"docstatus": 1})
-		if sales_invoice_name:
-			si = frappe.get_doc("Sales Invoice", sales_invoice_name)
-			if si.outstanding_amount > 0:
-				payment_request = make_payment_request(dt="Sales Invoice", dn=si.name, recipient_id=si.contact_email,
-					submit_doc=True, mute_email=True, use_dummy_message=True)
-
-				payment_entry = frappe.get_doc(make_payment_entry(payment_request.name))
-				payment_entry.posting_date = frappe.flags.current_date
-				payment_entry.submit()
-
-	make_pos_invoice()
-
-def make_payment_entries(ref_doctype, report):
-
-	outstanding_invoices = frappe.get_all(ref_doctype, fields=["name"],
-		filters={
-			"company": erpnext.get_default_company(),
-			"outstanding_amount": (">", 0.0)
-		})
-
-	# make Payment Entry
-	for inv in outstanding_invoices[:random.randint(1, 2)]:
-		pe = get_payment_entry(ref_doctype, inv.name)
-		pe.posting_date = frappe.flags.current_date
-		pe.reference_no = random_string(6)
-		pe.reference_date = frappe.flags.current_date
-		pe.insert()
-		pe.submit()
-		frappe.db.commit()
-		outstanding_invoices.remove(inv)
-
-	# make payment via JV
-	for inv in outstanding_invoices[:1]:
-		jv = frappe.get_doc(get_payment_entry_against_invoice(ref_doctype, inv.name))
-		jv.posting_date = frappe.flags.current_date
-		jv.cheque_no = random_string(6)
-		jv.cheque_date = frappe.flags.current_date
-		jv.insert()
-		jv.submit()
-		frappe.db.commit()
-
-def make_pos_invoice():
-	make_sales_order()
-
-	for data in frappe.get_all('Sales Order', fields=["name"],
-		filters = [["per_billed", "<", "100"]]):
-		si = frappe.get_doc(make_sales_invoice(data.name))
-		si.is_pos =1
-		si.posting_date = frappe.flags.current_date
-		for d in si.get("items"):
-			if not d.income_account:
-				d.income_account = "Sales - {}".format(frappe.get_cached_value('Company',  si.company,  'abbr'))
-		si.set_missing_values()
-		make_payment_entries_for_pos_invoice(si)
-		si.insert()
-		si.submit()
-
-def make_payment_entries_for_pos_invoice(si):
-	for data in si.payments:
-		data.amount = si.outstanding_amount
-		return
diff --git a/erpnext/demo/user/education.py b/erpnext/demo/user/education.py
deleted file mode 100644
index 47519c1..0000000
--- a/erpnext/demo/user/education.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-from datetime import timedelta
-
-import frappe
-from frappe.utils import cstr
-from frappe.utils.make_random import get_random
-
-from erpnext.education.api import (
-	collect_fees,
-	enroll_student,
-	get_course,
-	get_fee_schedule,
-	get_student_group_students,
-	make_attendance_records,
-)
-
-
-def work():
-	frappe.set_user(frappe.db.get_global('demo_education_user'))
-	for d in range(20):
-		approve_random_student_applicant()
-		enroll_random_student(frappe.flags.current_date)
-	# if frappe.flags.current_date.weekday()== 0:
-	# 	make_course_schedule(frappe.flags.current_date, frappe.utils.add_days(frappe.flags.current_date, 5))
-	mark_student_attendance(frappe.flags.current_date)
-	# make_assessment_plan()
-	make_fees()
-
-def approve_random_student_applicant():
-	random_student = get_random("Student Applicant", {"application_status": "Applied"})
-	if random_student:
-		status = ["Approved", "Rejected"]
-		frappe.db.set_value("Student Applicant", random_student, "application_status", status[weighted_choice([9,3])])
-
-def enroll_random_student(current_date):
-	batch = ["Section-A", "Section-B"]
-	random_student = get_random("Student Applicant", {"application_status": "Approved"})
-	if random_student:
-		enrollment = enroll_student(random_student)
-		enrollment.academic_year = get_random("Academic Year")
-		enrollment.enrollment_date = current_date
-		enrollment.student_batch_name = batch[weighted_choice([9,3])]
-		fee_schedule = get_fee_schedule(enrollment.program)
-		for fee in fee_schedule:
-			enrollment.append("fees", fee)
-		enrolled_courses = get_course(enrollment.program)
-		for course in enrolled_courses:
-			enrollment.append("courses", course)
-		enrollment.submit()
-		frappe.db.commit()
-		assign_student_group(enrollment.student, enrollment.student_name, enrollment.program,
-			enrolled_courses, enrollment.student_batch_name)
-
-def assign_student_group(student, student_name, program, courses, batch):
-	course_list = [d["course"] for d in courses]
-	for d in frappe.get_list("Student Group", fields=("name"), filters={"program": program, "course":("in", course_list), "disabled": 0}):
-		student_group = frappe.get_doc("Student Group", d.name)
-		student_group.append("students", {"student": student, "student_name": student_name,
-			"group_roll_number":len(student_group.students)+1, "active":1})
-		student_group.save()
-	student_batch = frappe.get_list("Student Group", fields=("name"), filters={"program": program, "group_based_on":"Batch", "batch":batch, "disabled": 0})[0]
-	student_batch_doc = frappe.get_doc("Student Group", student_batch.name)
-	student_batch_doc.append("students", {"student": student, "student_name": student_name,
-		"group_roll_number":len(student_batch_doc.students)+1, "active":1})
-	student_batch_doc.save()
-	frappe.db.commit()
-
-def mark_student_attendance(current_date):
-	status = ["Present", "Absent"]
-	for d in frappe.db.get_list("Student Group", filters={"group_based_on": "Batch", "disabled": 0}):
-		students = get_student_group_students(d.name)
-		for stud in students:
-			make_attendance_records(stud.student, stud.student_name, status[weighted_choice([9,4])], None, d.name, current_date)
-
-def make_fees():
-	for d in range(1,10):
-		random_fee = get_random("Fees", {"paid_amount": 0})
-		collect_fees(random_fee, frappe.db.get_value("Fees", random_fee, "outstanding_amount"))
-
-def make_assessment_plan(date):
-	for d in range(1,4):
-		random_group = get_random("Student Group", {"group_based_on": "Course", "disabled": 0}, True)
-		doc = frappe.new_doc("Assessment Plan")
-		doc.student_group = random_group.name
-		doc.course = random_group.course
-		doc.assessment_group = get_random("Assessment Group", {"is_group": 0, "parent": "2017-18 (Semester 2)"})
-		doc.grading_scale = get_random("Grading Scale")
-		doc.maximum_assessment_score = 100
-
-def make_course_schedule(start_date, end_date):
-	for d in frappe.db.get_list("Student Group"):
-		cs = frappe.new_doc("Scheduling Tool")
-		cs.student_group = d.name
-		cs.room = get_random("Room")
-		cs.instructor = get_random("Instructor")
-		cs.course_start_date = cstr(start_date)
-		cs.course_end_date = cstr(end_date)
-		day = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
-		for x in range(3):
-			random_day = random.choice(day)
-			cs.day = random_day
-			cs.from_time = timedelta(hours=(random.randrange(7, 17,1)))
-			cs.to_time = cs.from_time + timedelta(hours=1)
-			cs.schedule_course()
-			day.remove(random_day)
-
-
-def weighted_choice(weights):
-	totals = []
-	running_total = 0
-
-	for w in weights:
-		running_total += w
-		totals.append(running_total)
-
-	rnd = random.random() * running_total
-	for i, total in enumerate(totals):
-		if rnd < total:
-			return i
diff --git a/erpnext/demo/user/fixed_asset.py b/erpnext/demo/user/fixed_asset.py
deleted file mode 100644
index 72cd420..0000000
--- a/erpnext/demo/user/fixed_asset.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-from frappe.utils.make_random import get_random
-
-from erpnext.assets.doctype.asset.asset import make_sales_invoice
-from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries, scrap_asset
-
-
-def work():
-	frappe.set_user(frappe.db.get_global('demo_accounts_user'))
-
-	# Enable booking asset depreciation entry automatically
-	frappe.db.set_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically", 1)
-
-	# post depreciation entries as on today
-	post_depreciation_entries()
-
-	# scrap a random asset
-	frappe.db.set_value("Company", "Wind Power LLC", "disposal_account", "Gain/Loss on Asset Disposal - WPL")
-
-	asset = get_random_asset()
-	scrap_asset(asset.name)
-
-	# Sell a random asset
-	sell_an_asset()
-
-
-def sell_an_asset():
-	asset = get_random_asset()
-	si = make_sales_invoice(asset.name, asset.item_code, "Wind Power LLC")
-	si.customer = get_random("Customer")
-	si.get("items")[0].rate = asset.value_after_depreciation * 0.8 \
-		if asset.value_after_depreciation else asset.gross_purchase_amount * 0.9
-	si.save()
-	si.submit()
-
-
-def get_random_asset():
-	return frappe.db.sql(""" select name, item_code, value_after_depreciation, gross_purchase_amount
-		from `tabAsset`
-		where docstatus=1 and status not in ("Scrapped", "Sold") order by rand() limit 1""", as_dict=1)[0]
diff --git a/erpnext/demo/user/hr.py b/erpnext/demo/user/hr.py
deleted file mode 100644
index f84a853..0000000
--- a/erpnext/demo/user/hr.py
+++ /dev/null
@@ -1,223 +0,0 @@
-import datetime
-import random
-
-import frappe
-from frappe.utils import add_days, get_last_day, getdate, random_string
-from frappe.utils.make_random import get_random
-
-import erpnext
-from erpnext.hr.doctype.expense_claim.expense_claim import make_bank_entry
-from erpnext.hr.doctype.expense_claim.test_expense_claim import get_payable_account
-from erpnext.hr.doctype.leave_application.leave_application import (
-	AttendanceAlreadyMarkedError,
-	OverlapError,
-	get_leave_balance_on,
-)
-from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
-from erpnext.projects.doctype.timesheet.timesheet import make_salary_slip, make_sales_invoice
-
-
-def work():
-	frappe.set_user(frappe.db.get_global('demo_hr_user'))
-	year, month = frappe.flags.current_date.strftime("%Y-%m").split("-")
-	setup_department_approvers()
-	mark_attendance()
-	make_leave_application()
-
-	# payroll entry
-	if not frappe.db.sql('select name from `tabSalary Slip` where month(adddate(start_date, interval 1 month))=month(curdate())'):
-		# based on frequency
-		payroll_entry = get_payroll_entry()
-		payroll_entry.salary_slip_based_on_timesheet = 0
-		payroll_entry.save()
-		payroll_entry.create_salary_slips()
-		payroll_entry.submit_salary_slips()
-		payroll_entry.make_accrual_jv_entry()
-		payroll_entry.submit()
-		# payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date,
-		# 	reference_number=random_string(10))
-
-		# based on timesheet
-		payroll_entry = get_payroll_entry()
-		payroll_entry.salary_slip_based_on_timesheet = 1
-		payroll_entry.save()
-		payroll_entry.create_salary_slips()
-		payroll_entry.submit_salary_slips()
-		payroll_entry.make_accrual_jv_entry()
-		payroll_entry.submit()
-		# payroll_entry.make_journal_entry(reference_date=frappe.flags.current_date,
-		# 	reference_number=random_string(10))
-
-	if frappe.db.get_global('demo_hr_user'):
-		make_timesheet_records()
-
-		#expense claim
-		expense_claim = frappe.new_doc("Expense Claim")
-		expense_claim.extend('expenses', get_expenses())
-		expense_claim.employee = get_random("Employee")
-		expense_claim.company = frappe.flags.company
-		expense_claim.payable_account = get_payable_account(expense_claim.company)
-		expense_claim.posting_date = frappe.flags.current_date
-		expense_claim.expense_approver = frappe.db.get_global('demo_hr_user')
-		expense_claim.save()
-
-		rand = random.random()
-
-		if rand < 0.4:
-			update_sanctioned_amount(expense_claim)
-			expense_claim.approval_status = 'Approved'
-			expense_claim.submit()
-
-			if random.randint(0, 1):
-				#make journal entry against expense claim
-				je = frappe.get_doc(make_bank_entry("Expense Claim", expense_claim.name))
-				je.posting_date = frappe.flags.current_date
-				je.cheque_no = random_string(10)
-				je.cheque_date = frappe.flags.current_date
-				je.flags.ignore_permissions = 1
-				je.submit()
-
-def get_payroll_entry():
-	# process payroll for previous month
-	payroll_entry = frappe.new_doc("Payroll Entry")
-	payroll_entry.company = frappe.flags.company
-	payroll_entry.payroll_frequency = 'Monthly'
-
-	# select a posting date from the previous month
-	payroll_entry.posting_date = get_last_day(getdate(frappe.flags.current_date) - datetime.timedelta(days=10))
-	payroll_entry.payment_account = frappe.get_value('Account', {'account_type': 'Cash', 'company': erpnext.get_default_company(),'is_group':0}, "name")
-
-	payroll_entry.set_start_end_dates()
-	return payroll_entry
-
-def get_expenses():
-	expenses = []
-	expese_types = frappe.db.sql("""select ect.name, eca.default_account from `tabExpense Claim Type` ect,
-		`tabExpense Claim Account` eca where eca.parent=ect.name
-		and eca.company=%s """, frappe.flags.company,as_dict=1)
-
-	for expense_type in expese_types[:random.randint(1,4)]:
-		claim_amount = random.randint(1,20)*10
-
-		expenses.append({
-			"expense_date": frappe.flags.current_date,
-			"expense_type": expense_type.name,
-			"default_account": expense_type.default_account or "Miscellaneous Expenses - WPL",
-			"amount": claim_amount,
-			"sanctioned_amount": claim_amount
-		})
-
-	return expenses
-
-def update_sanctioned_amount(expense_claim):
-	for expense in expense_claim.expenses:
-		sanctioned_amount = random.randint(1,20)*10
-
-		if sanctioned_amount < expense.amount:
-			expense.sanctioned_amount = sanctioned_amount
-
-def get_timesheet_based_salary_slip_employee():
-	sal_struct = frappe.db.sql("""
-			select name from `tabSalary Structure`
-			where salary_slip_based_on_timesheet = 1
-			and docstatus != 2""")
-	if sal_struct:
-		employees = frappe.db.sql("""
-				select employee from `tabSalary Structure Assignment`
-				where salary_structure IN %(sal_struct)s""", {"sal_struct": sal_struct}, as_dict=True)
-		return employees
-	else:
-		return []
-
-def make_timesheet_records():
-	employees = get_timesheet_based_salary_slip_employee()
-	for e in employees:
-		ts = make_timesheet(e.employee, simulate = True, billable = 1, activity_type=get_random("Activity Type"), company=frappe.flags.company)
-		frappe.db.commit()
-
-		rand = random.random()
-		if rand >= 0.3:
-			make_salary_slip_for_timesheet(ts.name)
-
-		rand = random.random()
-		if rand >= 0.2:
-			make_sales_invoice_for_timesheet(ts.name)
-
-def make_salary_slip_for_timesheet(name):
-	salary_slip = make_salary_slip(name)
-	salary_slip.insert()
-	salary_slip.submit()
-	frappe.db.commit()
-
-def make_sales_invoice_for_timesheet(name):
-	sales_invoice = make_sales_invoice(name)
-	sales_invoice.customer = get_random("Customer")
-	sales_invoice.append('items', {
-		'item_code': get_random("Item", {"has_variants": 0, "is_stock_item": 0,
-			"is_fixed_asset": 0}),
-		'qty': 1,
-		'rate': 1000
-	})
-	sales_invoice.flags.ignore_permissions = 1
-	sales_invoice.set_missing_values()
-	sales_invoice.calculate_taxes_and_totals()
-	sales_invoice.insert()
-	sales_invoice.submit()
-	frappe.db.commit()
-
-def make_leave_application():
-	allocated_leaves = frappe.get_all("Leave Allocation", fields=['employee', 'leave_type'])
-
-	for allocated_leave in allocated_leaves:
-		leave_balance = get_leave_balance_on(allocated_leave.employee, allocated_leave.leave_type, frappe.flags.current_date,
-			consider_all_leaves_in_the_allocation_period=True)
-		if leave_balance != 0:
-			if leave_balance == 1:
-				to_date = frappe.flags.current_date
-			else:
-				to_date = add_days(frappe.flags.current_date, random.randint(0, leave_balance-1))
-
-			leave_application = frappe.get_doc({
-				"doctype": "Leave Application",
-				"employee": allocated_leave.employee,
-				"from_date": frappe.flags.current_date,
-				"to_date": to_date,
-				"leave_type": allocated_leave.leave_type,
-			})
-			try:
-				leave_application.insert()
-				leave_application.submit()
-				frappe.db.commit()
-			except (OverlapError, AttendanceAlreadyMarkedError):
-				frappe.db.rollback()
-
-def mark_attendance():
-	attendance_date = frappe.flags.current_date
-	for employee in frappe.get_all('Employee', fields=['name'], filters = {'status': 'Active'}):
-
-		if not frappe.db.get_value("Attendance", {"employee": employee.name, "attendance_date": attendance_date}):
-			attendance = frappe.get_doc({
-				"doctype": "Attendance",
-				"employee": employee.name,
-				"attendance_date": attendance_date
-			})
-
-			leave = frappe.db.sql("""select name from `tabLeave Application`
-				where employee = %s and %s between from_date and to_date
-				and docstatus = 1""", (employee.name, attendance_date))
-
-			if leave:
-				attendance.status = "Absent"
-			else:
-				attendance.status = "Present"
-			attendance.save()
-			attendance.submit()
-			frappe.db.commit()
-
-def setup_department_approvers():
-	for d in frappe.get_all('Department', filters={'department_name': ['!=', 'All Departments']}):
-		doc = frappe.get_doc('Department', d.name)
-		doc.append("leave_approvers", {'approver': frappe.session.user})
-		doc.append("expense_approvers", {'approver': frappe.session.user})
-		doc.flags.ignore_mandatory = True
-		doc.save()
diff --git a/erpnext/demo/user/manufacturing.py b/erpnext/demo/user/manufacturing.py
deleted file mode 100644
index 6b61776..0000000
--- a/erpnext/demo/user/manufacturing.py
+++ /dev/null
@@ -1,123 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-from datetime import timedelta
-
-import frappe
-from frappe.desk import query_report
-from frappe.utils.make_random import how_many
-
-import erpnext
-from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
-
-
-def work():
-	if random.random() < 0.3: return
-
-	frappe.set_user(frappe.db.get_global('demo_manufacturing_user'))
-	if not frappe.get_all('Sales Order'): return
-
-	ppt = frappe.new_doc("Production Plan")
-	ppt.company = erpnext.get_default_company()
-	# ppt.use_multi_level_bom = 1 #refactored
-	ppt.get_items_from = "Sales Order"
-	# ppt.purchase_request_for_warehouse = "Stores - WPL" # refactored
-	ppt.run_method("get_open_sales_orders")
-	if not ppt.get("sales_orders"): return
-	ppt.run_method("get_items")
-	ppt.run_method("raise_material_requests")
-	ppt.save()
-	ppt.submit()
-	ppt.run_method("raise_work_orders")
-	frappe.db.commit()
-
-	# submit work orders
-	for pro in frappe.db.get_values("Work Order", {"docstatus": 0}, "name"):
-		b = frappe.get_doc("Work Order", pro[0])
-		b.wip_warehouse = "Work in Progress - WPL"
-		b.submit()
-		frappe.db.commit()
-
-	# submit material requests
-	for pro in frappe.db.get_values("Material Request", {"docstatus": 0}, "name"):
-		b = frappe.get_doc("Material Request", pro[0])
-		b.submit()
-		frappe.db.commit()
-
-	# stores -> wip
-	if random.random() < 0.4:
-		for pro in query_report.run("Open Work Orders")["result"][:how_many("Stock Entry for WIP")]:
-			make_stock_entry_from_pro(pro[0], "Material Transfer for Manufacture")
-
-	# wip -> fg
-	if random.random() < 0.4:
-		for pro in query_report.run("Work Orders in Progress")["result"][:how_many("Stock Entry for FG")]:
-			make_stock_entry_from_pro(pro[0], "Manufacture")
-
-	for bom in frappe.get_all('BOM', fields=['item'], filters = {'with_operations': 1}):
-		pro_order = make_wo_order_test_record(item=bom.item, qty=2,
-			source_warehouse="Stores - WPL", wip_warehouse = "Work in Progress - WPL",
-			fg_warehouse = "Stores - WPL", company = erpnext.get_default_company(),
-			stock_uom = frappe.db.get_value('Item', bom.item, 'stock_uom'),
-			planned_start_date = frappe.flags.current_date)
-
-	# submit job card
-	if random.random() < 0.4:
-		submit_job_cards()
-
-def make_stock_entry_from_pro(pro_id, purpose):
-	from erpnext.manufacturing.doctype.work_order.work_order import make_stock_entry
-	from erpnext.stock.doctype.stock_entry.stock_entry import (
-		DuplicateEntryForWorkOrderError,
-		IncorrectValuationRateError,
-		OperationsNotCompleteError,
-	)
-	from erpnext.stock.stock_ledger import NegativeStockError
-
-	try:
-		st = frappe.get_doc(make_stock_entry(pro_id, purpose))
-		st.posting_date = frappe.flags.current_date
-		st.fiscal_year = str(frappe.flags.current_date.year)
-		for d in st.get("items"):
-			d.cost_center = "Main - " + frappe.get_cached_value('Company',  st.company,  'abbr')
-		st.insert()
-		frappe.db.commit()
-		st.submit()
-		frappe.db.commit()
-	except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError,
-		OperationsNotCompleteError):
-		frappe.db.rollback()
-
-def submit_job_cards():
-	work_orders = frappe.get_all("Work Order", ["name", "creation"], {"docstatus": 1, "status": "Not Started"})
-	work_order = random.choice(work_orders)
-	# for work_order in work_orders:
-	start_date = work_order.creation
-	work_order = frappe.get_doc("Work Order", work_order.name)
-	job = frappe.get_all("Job Card", ["name", "operation", "work_order"],
-		{"docstatus": 0, "work_order": work_order.name})
-
-	if not job: return
-	job_map = {}
-	for d in job:
-		job_map[d.operation] = frappe.get_doc("Job Card", d.name)
-
-	for operation in work_order.operations:
-		job = job_map[operation.operation]
-		job_time_log = frappe.new_doc("Job Card Time Log")
-		job_time_log.from_time = start_date
-		minutes = operation.get("time_in_mins")
-		job_time_log.time_in_mins = random.randint(int(minutes/2), minutes)
-		job_time_log.to_time = job_time_log.from_time + \
-					timedelta(minutes=job_time_log.time_in_mins)
-		job_time_log.parent = job.name
-		job_time_log.parenttype = 'Job Card'
-		job_time_log.parentfield = 'time_logs'
-		job_time_log.completed_qty = work_order.qty
-		job_time_log.save(ignore_permissions=True)
-		job.time_logs.append(job_time_log)
-		job.save(ignore_permissions=True)
-		job.submit()
-		start_date = job_time_log.to_time
diff --git a/erpnext/demo/user/projects.py b/erpnext/demo/user/projects.py
deleted file mode 100644
index 1203be4..0000000
--- a/erpnext/demo/user/projects.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-from frappe.utils import flt
-from frappe.utils.make_random import get_random
-
-import erpnext
-from erpnext.demo.user.hr import make_sales_invoice_for_timesheet
-from erpnext.projects.doctype.timesheet.test_timesheet import make_timesheet
-
-
-def run_projects(current_date):
-	frappe.set_user(frappe.db.get_global('demo_projects_user'))
-	if frappe.db.get_global('demo_projects_user'):
-		make_project(current_date)
-		make_timesheet_for_projects(current_date)
-		close_tasks(current_date)
-
-def make_timesheet_for_projects(current_date	):
-	for data in frappe.get_all("Task", ["name", "project"], {"status": "Open", "exp_end_date": ("<", current_date)}):
-		employee = get_random("Employee")
-		ts = make_timesheet(employee, simulate = True, billable = 1, company = erpnext.get_default_company(),
-			activity_type=get_random("Activity Type"), project=data.project, task =data.name)
-
-		if flt(ts.total_billable_amount) > 0.0:
-			make_sales_invoice_for_timesheet(ts.name)
-			frappe.db.commit()
-
-def close_tasks(current_date):
-	for task in frappe.get_all("Task", ["name"], {"status": "Open", "exp_end_date": ("<", current_date)}):
-		task = frappe.get_doc("Task", task.name)
-		task.status = "Completed"
-		task.save()
-
-def make_project(current_date):
-	if not frappe.db.exists('Project',
-		"New Product Development " + current_date.strftime("%Y-%m-%d")):
-		project = frappe.get_doc({
-			"doctype": "Project",
-			"project_name": "New Product Development " + current_date.strftime("%Y-%m-%d"),
-		})
-		project.insert()
diff --git a/erpnext/demo/user/purchase.py b/erpnext/demo/user/purchase.py
deleted file mode 100644
index 61f081c..0000000
--- a/erpnext/demo/user/purchase.py
+++ /dev/null
@@ -1,180 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import json
-import random
-
-import frappe
-from frappe.desk import query_report
-from frappe.utils.make_random import get_random, how_many
-
-import erpnext
-from erpnext.accounts.party import get_party_account_currency
-from erpnext.buying.doctype.request_for_quotation.request_for_quotation import (
-	make_supplier_quotation_from_rfq,
-)
-from erpnext.exceptions import InvalidCurrency
-from erpnext.setup.utils import get_exchange_rate
-from erpnext.stock.doctype.material_request.material_request import make_request_for_quotation
-
-
-def work():
-	frappe.set_user(frappe.db.get_global('demo_purchase_user'))
-
-	if random.random() < 0.6:
-		report = "Items To Be Requested"
-		for row in query_report.run(report)["result"][:random.randint(1, 5)]:
-			item_code, qty = row[0], abs(row[-1])
-
-			mr = make_material_request(item_code, qty)
-
-	if random.random() < 0.6:
-		for mr in frappe.get_all('Material Request',
-			filters={'material_request_type': 'Purchase', 'status': 'Open'},
-			limit=random.randint(1,6)):
-			if not frappe.get_all('Request for Quotation',
-				filters={'material_request': mr.name}, limit=1):
-				rfq = make_request_for_quotation(mr.name)
-				rfq.transaction_date = frappe.flags.current_date
-				add_suppliers(rfq)
-				rfq.save()
-				rfq.submit()
-
-	# Make suppier quotation from RFQ against each supplier.
-	if random.random() < 0.6:
-		for rfq in frappe.get_all('Request for Quotation',
-			filters={'status': 'Open'}, limit=random.randint(1, 6)):
-			if not frappe.get_all('Supplier Quotation',
-				filters={'request_for_quotation': rfq.name}, limit=1):
-				rfq = frappe.get_doc('Request for Quotation', rfq.name)
-
-				for supplier in rfq.suppliers:
-					supplier_quotation = make_supplier_quotation_from_rfq(rfq.name, for_supplier=supplier.supplier)
-					supplier_quotation.save()
-					supplier_quotation.submit()
-
-	# get supplier details
-	supplier = get_random("Supplier")
-
-	company_currency = frappe.get_cached_value('Company', erpnext.get_default_company(), "default_currency")
-	party_account_currency = get_party_account_currency("Supplier", supplier, erpnext.get_default_company())
-	if company_currency == party_account_currency:
-		exchange_rate = 1
-	else:
-		exchange_rate = get_exchange_rate(party_account_currency, company_currency, args="for_buying")
-
-	# make supplier quotations
-	if random.random() < 0.5:
-		from erpnext.stock.doctype.material_request.material_request import make_supplier_quotation
-
-		report = "Material Requests for which Supplier Quotations are not created"
-		for row in query_report.run(report)["result"][:random.randint(1, 3)]:
-			if row[0] != "Total":
-				sq = frappe.get_doc(make_supplier_quotation(row[0]))
-				sq.transaction_date = frappe.flags.current_date
-				sq.supplier = supplier
-				sq.currency = party_account_currency or company_currency
-				sq.conversion_rate = exchange_rate
-				sq.insert()
-				sq.submit()
-				frappe.db.commit()
-
-	# make purchase orders
-	if random.random() < 0.5:
-		from erpnext.stock.doctype.material_request.material_request import make_purchase_order
-		report = "Requested Items To Be Ordered"
-		for row in query_report.run(report)["result"][:how_many("Purchase Order")]:
-			if row[0] != "Total":
-				try:
-					po = frappe.get_doc(make_purchase_order(row[0]))
-					po.supplier = supplier
-					po.currency = party_account_currency or company_currency
-					po.conversion_rate = exchange_rate
-					po.transaction_date = frappe.flags.current_date
-					po.insert()
-					po.submit()
-				except Exception:
-					pass
-				else:
-					frappe.db.commit()
-
-	if random.random() < 0.5:
-		make_subcontract()
-
-def make_material_request(item_code, qty):
-	mr = frappe.new_doc("Material Request")
-
-	variant_of = frappe.db.get_value('Item', item_code, 'variant_of') or item_code
-
-	if frappe.db.get_value('BOM', {'item': variant_of, 'is_default': 1, 'is_active': 1}):
-		mr.material_request_type = 'Manufacture'
-	else:
-		mr.material_request_type = "Purchase"
-
-	mr.transaction_date = frappe.flags.current_date
-	mr.schedule_date = frappe.utils.add_days(mr.transaction_date, 7)
-
-	mr.append("items", {
-		"doctype": "Material Request Item",
-		"schedule_date": frappe.utils.add_days(mr.transaction_date, 7),
-		"item_code": item_code,
-		"qty": qty
-	})
-	mr.insert()
-	mr.submit()
-	return mr
-
-def add_suppliers(rfq):
-	for i in range(2):
-		supplier = get_random("Supplier")
-		if supplier not in [d.supplier for d in rfq.get('suppliers')]:
-			rfq.append("suppliers", { "supplier": supplier })
-
-def make_subcontract():
-	from erpnext.buying.doctype.purchase_order.purchase_order import make_rm_stock_entry
-	item_code = get_random("Item", {"is_sub_contracted_item": 1})
-	if item_code:
-		# make sub-contract PO
-		po = frappe.new_doc("Purchase Order")
-		po.is_subcontracted = "Yes"
-		po.supplier = get_random("Supplier")
-		po.transaction_date = frappe.flags.current_date # added
-		po.schedule_date = frappe.utils.add_days(frappe.flags.current_date, 7)
-
-		item_code = get_random("Item", {"is_sub_contracted_item": 1})
-
-		po.append("items", {
-			"item_code": item_code,
-			"schedule_date": frappe.utils.add_days(frappe.flags.current_date, 7),
-			"qty": random.randint(10, 30)
-		})
-		po.set_missing_values()
-		try:
-			po.insert()
-		except InvalidCurrency:
-			return
-
-		po.submit()
-
-		# make material request for
-		make_material_request(po.items[0].item_code, po.items[0].qty)
-
-		# transfer material for sub-contract
-		rm_items = get_rm_item(po.items[0], po.supplied_items[0])
-		stock_entry = frappe.get_doc(make_rm_stock_entry(po.name, json.dumps([rm_items])))
-		stock_entry.from_warehouse = "Stores - WPL"
-		stock_entry.to_warehouse = "Supplier - WPL"
-		stock_entry.insert()
-
-def get_rm_item(items, supplied_items):
-	return {
-		"item_code": items.get("item_code"),
-		"rm_item_code": supplied_items.get("rm_item_code"),
-		"item_name": supplied_items.get("rm_item_code"),
-		"qty": supplied_items.get("required_qty") + random.randint(3,10),
-		"amount": supplied_items.get("amount"),
-		"warehouse": supplied_items.get("reserve_warehouse"),
-		"rate": supplied_items.get("rate"),
-		"stock_uom": supplied_items.get("stock_uom")
-	}
diff --git a/erpnext/demo/user/sales.py b/erpnext/demo/user/sales.py
deleted file mode 100644
index ef6e4c4..0000000
--- a/erpnext/demo/user/sales.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-
-import frappe
-from frappe.utils import flt
-from frappe.utils.make_random import add_random_children, get_random
-
-import erpnext
-from erpnext.accounts.doctype.payment_request.payment_request import (
-	make_payment_entry,
-	make_payment_request,
-)
-from erpnext.accounts.party import get_party_account_currency
-from erpnext.setup.utils import get_exchange_rate
-
-
-def work(domain="Manufacturing"):
-	frappe.set_user(frappe.db.get_global('demo_sales_user_2'))
-
-	for i in range(random.randint(1,7)):
-		if random.random() < 0.5:
-			make_opportunity(domain)
-
-	for i in range(random.randint(1,3)):
-		if random.random() < 0.5:
-			make_quotation(domain)
-
-	try:
-		lost_reason = frappe.get_doc({
-			"doctype": "Opportunity Lost Reason",
-			"lost_reason": "Did not ask"
-		})
-		lost_reason.save(ignore_permissions=True)
-	except frappe.exceptions.DuplicateEntryError:
-		pass
-
-	# lost quotations / inquiries
-	if random.random() < 0.3:
-		for i in range(random.randint(1,3)):
-			quotation = get_random('Quotation', doc=True)
-			if quotation and quotation.status == 'Submitted':
-				quotation.declare_order_lost([{'lost_reason': 'Did not ask'}])
-
-		for i in range(random.randint(1,3)):
-			opportunity = get_random('Opportunity', doc=True)
-			if opportunity and opportunity.status in ('Open', 'Replied'):
-				opportunity.declare_enquiry_lost([{'lost_reason': 'Did not ask'}])
-
-	for i in range(random.randint(1,3)):
-		if random.random() < 0.6:
-			make_sales_order()
-
-	if random.random() < 0.5:
-		#make payment request against Sales Order
-		sales_order_name = get_random("Sales Order", filters={"docstatus": 1})
-		try:
-			if sales_order_name:
-				so = frappe.get_doc("Sales Order", sales_order_name)
-				if flt(so.per_billed) != 100:
-					payment_request = make_payment_request(dt="Sales Order", dn=so.name, recipient_id=so.contact_email,
-						submit_doc=True, mute_email=True, use_dummy_message=True)
-
-					payment_entry = frappe.get_doc(make_payment_entry(payment_request.name))
-					payment_entry.posting_date = frappe.flags.current_date
-					payment_entry.submit()
-		except Exception:
-			pass
-
-def make_opportunity(domain):
-	b = frappe.get_doc({
-		"doctype": "Opportunity",
-		"opportunity_from": "Customer",
-		"party_name": frappe.get_value("Customer", get_random("Customer"), 'name'),
-		"opportunity_type": "Sales",
-		"with_items": 1,
-		"transaction_date": frappe.flags.current_date,
-	})
-
-	add_random_children(b, "items", rows=4, randomize = {
-		"qty": (1, 5),
-		"item_code": ("Item", {"has_variants": 0, "is_fixed_asset": 0, "domain": domain})
-	}, unique="item_code")
-
-	b.insert()
-	frappe.db.commit()
-
-def make_quotation(domain):
-	# get open opportunites
-	opportunity = get_random("Opportunity", {"status": "Open", "with_items": 1})
-
-	if opportunity:
-		from erpnext.crm.doctype.opportunity.opportunity import make_quotation
-		qtn = frappe.get_doc(make_quotation(opportunity))
-		qtn.insert()
-		frappe.db.commit()
-		qtn.submit()
-		frappe.db.commit()
-	else:
-		# make new directly
-
-		# get customer, currency and exchange_rate
-		customer = get_random("Customer")
-
-		company_currency = frappe.get_cached_value('Company',  erpnext.get_default_company(),  "default_currency")
-		party_account_currency = get_party_account_currency("Customer", customer, erpnext.get_default_company())
-		if company_currency == party_account_currency:
-			exchange_rate = 1
-		else:
-			exchange_rate = get_exchange_rate(party_account_currency, company_currency, args="for_selling")
-
-		qtn = frappe.get_doc({
-			"creation": frappe.flags.current_date,
-			"doctype": "Quotation",
-			"quotation_to": "Customer",
-			"party_name": customer,
-			"currency": party_account_currency or company_currency,
-			"conversion_rate": exchange_rate,
-			"order_type": "Sales",
-			"transaction_date": frappe.flags.current_date,
-		})
-
-		add_random_children(qtn, "items", rows=3, randomize = {
-			"qty": (1, 5),
-			"item_code": ("Item", {"has_variants": "0", "is_fixed_asset": 0, "domain": domain})
-		}, unique="item_code")
-
-		qtn.insert()
-		frappe.db.commit()
-		qtn.submit()
-		frappe.db.commit()
-
-def make_sales_order():
-	q = get_random("Quotation", {"status": "Submitted"})
-	if q:
-		from erpnext.selling.doctype.quotation.quotation import make_sales_order as mso
-		so = frappe.get_doc(mso(q))
-		so.transaction_date = frappe.flags.current_date
-		so.delivery_date = frappe.utils.add_days(frappe.flags.current_date, 10)
-		so.insert()
-		frappe.db.commit()
-		so.submit()
-		frappe.db.commit()
diff --git a/erpnext/demo/user/stock.py b/erpnext/demo/user/stock.py
deleted file mode 100644
index de37975..0000000
--- a/erpnext/demo/user/stock.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import random
-
-import frappe
-from frappe.desk import query_report
-
-import erpnext
-from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError
-from erpnext.stock.doctype.delivery_note.delivery_note import make_sales_return
-from erpnext.stock.doctype.purchase_receipt.purchase_receipt import make_purchase_return
-from erpnext.stock.doctype.serial_no.serial_no import SerialNoQtyError, SerialNoRequiredError
-from erpnext.stock.stock_ledger import NegativeStockError
-
-
-def work():
-	frappe.set_user(frappe.db.get_global('demo_manufacturing_user'))
-
-	make_purchase_receipt()
-	make_delivery_note()
-	make_stock_reconciliation()
-	submit_draft_stock_entries()
-	make_sales_return_records()
-	make_purchase_return_records()
-
-def make_purchase_receipt():
-	if random.random() < 0.6:
-		from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
-		report = "Purchase Order Items To Be Received"
-		po_list =list(set([r[0] for r in query_report.run(report)["result"] if r[0]!="Total"]))[:random.randint(1, 10)]
-		for po in po_list:
-			pr = frappe.get_doc(make_purchase_receipt(po))
-
-			if pr.is_subcontracted=="Yes":
-				pr.supplier_warehouse = "Supplier - WPL"
-
-			pr.posting_date = frappe.flags.current_date
-			pr.insert()
-			try:
-				pr.submit()
-			except NegativeStockError:
-				print('Negative stock for {0}'.format(po))
-				pass
-			frappe.db.commit()
-
-def make_delivery_note():
-	# make purchase requests
-
-	# make delivery notes (if possible)
-	if random.random() < 0.6:
-		from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
-		report = "Ordered Items To Be Delivered"
-		for so in list(set([r[0] for r in query_report.run(report)["result"]
-			if r[0]!="Total"]))[:random.randint(1, 3)]:
-			dn = frappe.get_doc(make_delivery_note(so))
-			dn.posting_date = frappe.flags.current_date
-			for d in dn.get("items"):
-				if not d.expense_account:
-					d.expense_account = ("Cost of Goods Sold - {0}".format(
-						frappe.get_cached_value('Company',  dn.company,  'abbr')))
-
-			try:
-				dn.insert()
-				dn.submit()
-				frappe.db.commit()
-			except (NegativeStockError, SerialNoRequiredError, SerialNoQtyError, UnableToSelectBatchError):
-				frappe.db.rollback()
-
-def make_stock_reconciliation():
-	# random set some items as damaged
-	from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
-		EmptyStockReconciliationItemsError,
-		OpeningEntryAccountError,
-	)
-
-	if random.random() < 0.4:
-		stock_reco = frappe.new_doc("Stock Reconciliation")
-		stock_reco.posting_date = frappe.flags.current_date
-		stock_reco.company = erpnext.get_default_company()
-		stock_reco.get_items_for("Stores - WPL")
-		if stock_reco.items:
-			for item in stock_reco.items:
-				if item.qty:
-					item.qty = item.qty - round(random.randint(1, item.qty))
-			try:
-				stock_reco.insert(ignore_permissions=True, ignore_mandatory=True)
-				stock_reco.submit()
-				frappe.db.commit()
-			except OpeningEntryAccountError:
-				frappe.db.rollback()
-			except EmptyStockReconciliationItemsError:
-				frappe.db.rollback()
-
-def submit_draft_stock_entries():
-	from erpnext.stock.doctype.stock_entry.stock_entry import (
-		DuplicateEntryForWorkOrderError,
-		IncorrectValuationRateError,
-		OperationsNotCompleteError,
-	)
-
-	# try posting older drafts (if exists)
-	frappe.db.commit()
-	for st in frappe.db.get_values("Stock Entry", {"docstatus":0}, "name"):
-		try:
-			ste = frappe.get_doc("Stock Entry", st[0])
-			ste.posting_date = frappe.flags.current_date
-			ste.save()
-			ste.submit()
-			frappe.db.commit()
-		except (NegativeStockError, IncorrectValuationRateError, DuplicateEntryForWorkOrderError,
-			OperationsNotCompleteError):
-			frappe.db.rollback()
-
-def make_sales_return_records():
-	if random.random() < 0.1:
-		for data in frappe.get_all('Delivery Note', fields=["name"], filters={"docstatus": 1}):
-			if random.random() < 0.1:
-				try:
-					dn = make_sales_return(data.name)
-					dn.insert()
-					dn.submit()
-					frappe.db.commit()
-				except Exception:
-					frappe.db.rollback()
-
-def make_purchase_return_records():
-	if random.random() < 0.1:
-		for data in frappe.get_all('Purchase Receipt', fields=["name"], filters={"docstatus": 1}):
-			if random.random() < 0.1:
-				try:
-					pr = make_purchase_return(data.name)
-					pr.insert()
-					pr.submit()
-					frappe.db.commit()
-				except Exception:
-					frappe.db.rollback()
diff --git a/erpnext/domains/hospitality.py b/erpnext/domains/hospitality.py
deleted file mode 100644
index 09b98c2..0000000
--- a/erpnext/domains/hospitality.py
+++ /dev/null
@@ -1,35 +0,0 @@
-data = {
-	'desktop_icons': [
-		'Restaurant',
-		'Hotels',
-		'Accounts',
-		'Buying',
-		'Stock',
-		'HR',
-		'Project',
-		'ToDo'
-	],
-	'restricted_roles': [
-		'Restaurant Manager',
-		'Hotel Manager',
-		'Hotel Reservation User'
-	],
-	'custom_fields': {
-		'Sales Invoice': [
-			{
-				'fieldname': 'restaurant', 'fieldtype': 'Link', 'options': 'Restaurant',
-				'insert_after': 'customer_name', 'label': 'Restaurant',
-			},
-			{
-				'fieldname': 'restaurant_table', 'fieldtype': 'Link', 'options': 'Restaurant Table',
-				'insert_after': 'restaurant', 'label': 'Restaurant Table',
-			}
-		],
-		'Price List': [
-			{
-				'fieldname':'restaurant_menu', 'fieldtype':'Link', 'options':'Restaurant Menu', 'label':'Restaurant Menu',
-				'insert_after':'currency'
-			}
-		]
-	}
-}
diff --git a/erpnext/domains/non_profit.py b/erpnext/domains/non_profit.py
deleted file mode 100644
index d9fc5e5..0000000
--- a/erpnext/domains/non_profit.py
+++ /dev/null
@@ -1,22 +0,0 @@
-data = {
-	'desktop_icons': [
-		'Non Profit',
-		'Member',
-		'Donor',
-		'Volunteer',
-		'Grant Application',
-		'Accounts',
-		'Buying',
-		'HR',
-		'ToDo'
-	],
-	'restricted_roles': [
-		'Non Profit Manager',
-		'Non Profit Member',
-		'Non Profit Portal User'
-	],
-	'modules': [
-		'Non Profit'
-	],
-	'default_portal_role': 'Non Profit Manager'
-}
diff --git a/erpnext/demo/__init__.py b/erpnext/e_commerce/__init__.py
similarity index 100%
rename from erpnext/demo/__init__.py
rename to erpnext/e_commerce/__init__.py
diff --git a/erpnext/e_commerce/api.py b/erpnext/e_commerce/api.py
new file mode 100644
index 0000000..43cb36c
--- /dev/null
+++ b/erpnext/e_commerce/api.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import json
+
+import frappe
+from frappe.utils import cint
+
+from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
+from erpnext.e_commerce.product_data_engine.query import ProductQuery
+from erpnext.setup.doctype.item_group.item_group import get_child_groups_for_website
+
+
+@frappe.whitelist(allow_guest=True)
+def get_product_filter_data(query_args=None):
+	"""
+		Returns filtered products and discount filters.
+		:param query_args (dict): contains filters to get products list
+
+		Query Args filters:
+		search (str): Search Term.
+		field_filters (dict): Keys include item_group, brand, etc.
+		attribute_filters(dict): Keys include Color, Size, etc.
+		start (int): Offset items by
+		item_group (str): Valid Item Group
+		from_filters (bool): Set as True to jump to page 1
+	"""
+	if isinstance(query_args, str):
+		query_args = json.loads(query_args)
+
+	query_args = frappe._dict(query_args)
+	if query_args:
+		search = query_args.get("search")
+		field_filters = query_args.get("field_filters", {})
+		attribute_filters = query_args.get("attribute_filters", {})
+		start = cint(query_args.start) if query_args.get("start") else 0
+		item_group = query_args.get("item_group")
+		from_filters = query_args.get("from_filters")
+	else:
+		search, attribute_filters, item_group, from_filters = None, None, None, None
+		field_filters = {}
+		start = 0
+
+	# if new filter is checked, reset start to show filtered items from page 1
+	if from_filters:
+		start = 0
+
+	sub_categories = []
+	if item_group:
+		field_filters['item_group'] = item_group
+		sub_categories = get_child_groups_for_website(item_group, immediate=True)
+
+	engine = ProductQuery()
+	try:
+		result = engine.query(
+			attribute_filters,
+			field_filters,
+			search_term=search,
+			start=start,
+			item_group=item_group
+		)
+	except Exception:
+		traceback = frappe.get_traceback()
+		frappe.log_error(traceback, frappe._("Product Engine Error"))
+		return {"exc": "Something went wrong!"}
+
+	# discount filter data
+	filters = {}
+	discounts = result["discounts"]
+
+	if discounts:
+		filter_engine = ProductFiltersBuilder()
+		filters["discount_filters"] = filter_engine.get_discount_filters(discounts)
+
+	return {
+		"items": result["items"] or [],
+		"filters": filters,
+		"settings": engine.settings,
+		"sub_categories": sub_categories,
+		"items_count": result["items_count"]
+	}
+
+@frappe.whitelist(allow_guest=True)
+def get_guest_redirect_on_action():
+	return frappe.db.get_single_value("E Commerce Settings", "redirect_on_action")
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/__init__.py b/erpnext/e_commerce/doctype/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/__init__.py
rename to erpnext/e_commerce/doctype/__init__.py
diff --git a/erpnext/hotels/doctype/hotel_settings/__init__.py b/erpnext/e_commerce/doctype/e_commerce_settings/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/hotel_settings/__init__.py
rename to erpnext/e_commerce/doctype/e_commerce_settings/__init__.py
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
new file mode 100644
index 0000000..6302d26
--- /dev/null
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.js
@@ -0,0 +1,53 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on("E Commerce Settings", {
+	onload: function(frm) {
+		if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
+			frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
+			frm.refresh_field("quotation_series");
+		}
+
+		frm.set_query('payment_gateway_account', function() {
+			return { 'filters': { 'payment_channel': "Email" } };
+		});
+	},
+	refresh: function(frm) {
+		if (frm.doc.enabled) {
+			frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html(
+				`<div>${__("Follow these steps to create a landing page for your store")}:
+					<a href="https://docs.erpnext.com/docs/user/manual/en/website/store-landing-page"
+						style="color: var(--gray-600)">
+						docs/store-landing-page
+					</a>
+				</div>`
+			);
+		}
+
+		frappe.model.with_doctype("Item", () => {
+			const web_item_meta = frappe.get_meta('Website Item');
+
+			const valid_fields = web_item_meta.fields.filter(
+				df => ["Link", "Table MultiSelect"].includes(df.fieldtype) && !df.hidden
+			).map(df => ({ label: df.label, value: df.fieldname }));
+
+			frm.fields_dict.filter_fields.grid.update_docfield_property(
+				'fieldname', 'fieldtype', 'Select'
+			);
+			frm.fields_dict.filter_fields.grid.update_docfield_property(
+				'fieldname', 'options', valid_fields
+			);
+		});
+	},
+	enabled: function(frm) {
+		if (frm.doc.enabled === 1) {
+			frm.set_value('enable_variants', 1);
+		}
+		else {
+			frm.set_value('company', '');
+			frm.set_value('price_list', '');
+			frm.set_value('default_customer_group', '');
+			frm.set_value('quotation_series', '');
+		}
+	}
+});
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
new file mode 100644
index 0000000..d5fb969
--- /dev/null
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.json
@@ -0,0 +1,393 @@
+{
+ "actions": [],
+ "creation": "2021-02-10 17:13:39.139103",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "products_per_page",
+  "filter_categories_section",
+  "enable_field_filters",
+  "filter_fields",
+  "enable_attribute_filters",
+  "filter_attributes",
+  "display_settings_section",
+  "hide_variants",
+  "enable_variants",
+  "show_price",
+  "column_break_9",
+  "show_stock_availability",
+  "show_quantity_in_website",
+  "allow_items_not_in_stock",
+  "column_break_13",
+  "show_apply_coupon_code_in_website",
+  "show_contact_us_button",
+  "show_attachments",
+  "section_break_18",
+  "company",
+  "price_list",
+  "enabled",
+  "store_page_docs",
+  "column_break_21",
+  "default_customer_group",
+  "quotation_series",
+  "checkout_settings_section",
+  "enable_checkout",
+  "show_price_in_quotation",
+  "column_break_27",
+  "save_quotations_as_draft",
+  "payment_gateway_account",
+  "payment_success_url",
+  "add_ons_section",
+  "enable_wishlist",
+  "column_break_22",
+  "enable_reviews",
+  "column_break_23",
+  "enable_recommendations",
+  "item_search_settings_section",
+  "redisearch_warning",
+  "search_index_fields",
+  "show_categories_in_search_autocomplete",
+  "is_redisearch_loaded",
+  "shop_by_category_section",
+  "slideshow",
+  "guest_display_settings_section",
+  "hide_price_for_guest",
+  "redirect_on_action"
+ ],
+ "fields": [
+  {
+   "default": "6",
+   "fieldname": "products_per_page",
+   "fieldtype": "Int",
+   "label": "Products per Page"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "filter_categories_section",
+   "fieldtype": "Section Break",
+   "label": "Filters and Categories"
+  },
+  {
+   "default": "0",
+   "fieldname": "hide_variants",
+   "fieldtype": "Check",
+   "label": "Hide Variants"
+  },
+  {
+   "default": "0",
+   "description": "The field filters will also work as categories in the <b>Shop by Category</b> page.",
+   "fieldname": "enable_field_filters",
+   "fieldtype": "Check",
+   "label": "Enable Field Filters (Categories)"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_attribute_filters",
+   "fieldtype": "Check",
+   "label": "Enable Attribute Filters"
+  },
+  {
+   "depends_on": "enable_field_filters",
+   "fieldname": "filter_fields",
+   "fieldtype": "Table",
+   "label": "Website Item Fields",
+   "options": "Website Filter Field"
+  },
+  {
+   "depends_on": "enable_attribute_filters",
+   "fieldname": "filter_attributes",
+   "fieldtype": "Table",
+   "label": "Attributes",
+   "options": "Website Attribute"
+  },
+  {
+   "default": "0",
+   "fieldname": "enabled",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Enable Shopping Cart"
+  },
+  {
+   "depends_on": "doc.enabled",
+   "fieldname": "store_page_docs",
+   "fieldtype": "HTML"
+  },
+  {
+   "fieldname": "display_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Display Settings"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_attachments",
+   "fieldtype": "Check",
+   "label": "Show Public Attachments"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_price",
+   "fieldtype": "Check",
+   "label": "Show Price"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_stock_availability",
+   "fieldtype": "Check",
+   "label": "Show Stock Availability"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_variants",
+   "fieldtype": "Check",
+   "label": "Enable Variant Selection"
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_contact_us_button",
+   "fieldtype": "Check",
+   "label": "Show Contact Us Button"
+  },
+  {
+   "default": "0",
+   "depends_on": "show_stock_availability",
+   "fieldname": "show_quantity_in_website",
+   "fieldtype": "Check",
+   "label": "Show Stock Quantity"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_apply_coupon_code_in_website",
+   "fieldtype": "Check",
+   "label": "Show Apply Coupon Code"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_items_not_in_stock",
+   "fieldtype": "Check",
+   "label": "Allow items not in stock to be added to cart"
+  },
+  {
+   "fieldname": "section_break_18",
+   "fieldtype": "Section Break",
+   "label": "Shopping Cart"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "mandatory_depends_on": "eval: doc.enabled === 1",
+   "options": "Company",
+   "remember_last_selected_value": 1
+  },
+  {
+   "depends_on": "enabled",
+   "description": "Prices will not be shown if Price List is not set",
+   "fieldname": "price_list",
+   "fieldtype": "Link",
+   "label": "Price List",
+   "mandatory_depends_on": "eval: doc.enabled === 1",
+   "options": "Price List"
+  },
+  {
+   "fieldname": "column_break_21",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "default_customer_group",
+   "fieldtype": "Link",
+   "ignore_user_permissions": 1,
+   "label": "Default Customer Group",
+   "mandatory_depends_on": "eval: doc.enabled === 1",
+   "options": "Customer Group"
+  },
+  {
+   "depends_on": "enabled",
+   "fieldname": "quotation_series",
+   "fieldtype": "Select",
+   "label": "Quotation Series",
+   "mandatory_depends_on": "eval: doc.enabled === 1"
+  },
+  {
+   "collapsible": 1,
+   "collapsible_depends_on": "eval:doc.enable_checkout",
+   "depends_on": "enabled",
+   "fieldname": "checkout_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Checkout Settings"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_checkout",
+   "fieldtype": "Check",
+   "label": "Enable Checkout"
+  },
+  {
+   "default": "Orders",
+   "depends_on": "enable_checkout",
+   "description": "After payment completion redirect user to selected page.",
+   "fieldname": "payment_success_url",
+   "fieldtype": "Select",
+   "label": "Payment Success Url",
+   "mandatory_depends_on": "enable_checkout",
+   "options": "\nOrders\nInvoices\nMy Account"
+  },
+  {
+   "fieldname": "column_break_27",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.enable_checkout == 0",
+   "fieldname": "save_quotations_as_draft",
+   "fieldtype": "Check",
+   "label": "Save Quotations as Draft"
+  },
+  {
+   "depends_on": "enable_checkout",
+   "fieldname": "payment_gateway_account",
+   "fieldtype": "Link",
+   "label": "Payment Gateway Account",
+   "mandatory_depends_on": "enable_checkout",
+   "options": "Payment Gateway Account"
+  },
+  {
+   "collapsible": 1,
+   "depends_on": "enable_field_filters",
+   "fieldname": "shop_by_category_section",
+   "fieldtype": "Section Break",
+   "label": "Shop by Category"
+  },
+  {
+   "fieldname": "slideshow",
+   "fieldtype": "Link",
+   "label": "Slideshow",
+   "options": "Website Slideshow"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "add_ons_section",
+   "fieldtype": "Section Break",
+   "label": "Add-ons"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_wishlist",
+   "fieldtype": "Check",
+   "label": "Enable Wishlist"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_reviews",
+   "fieldtype": "Check",
+   "label": "Enable Reviews and Ratings"
+  },
+  {
+   "fieldname": "search_index_fields",
+   "fieldtype": "Small Text",
+   "label": "Search Index Fields",
+   "read_only_depends_on": "eval:!doc.is_redisearch_loaded"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "item_search_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Item Search Settings"
+  },
+  {
+   "default": "1",
+   "fieldname": "show_categories_in_search_autocomplete",
+   "fieldtype": "Check",
+   "label": "Show Categories in Search Autocomplete",
+   "read_only_depends_on": "eval:!doc.is_redisearch_loaded"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_redisearch_loaded",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Is Redisearch Loaded"
+  },
+  {
+   "depends_on": "eval:!doc.is_redisearch_loaded",
+   "fieldname": "redisearch_warning",
+   "fieldtype": "HTML",
+   "label": "Redisearch Warning",
+   "options": "<p class=\"alert alert-warning\">Redisearch is not loaded. If you want to use the advanced product search feature, refer <a class=\"alert-link\" href=\"https://docs.erpnext.com/docs/v13/user/manual/en/setting-up/articles/installing-redisearch\" target=\"_blank\">here</a>.</p>"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.show_price",
+   "fieldname": "hide_price_for_guest",
+   "fieldtype": "Check",
+   "label": "Hide Price for Guest"
+  },
+  {
+   "fieldname": "column_break_9",
+   "fieldtype": "Column Break"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "guest_display_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Guest Display Settings"
+  },
+  {
+   "description": "Link to redirect Guest on actions that need login such as add to cart, wishlist, etc. <b>E.g.: /login</b>",
+   "fieldname": "redirect_on_action",
+   "fieldtype": "Data",
+   "label": "Redirect on Action"
+  },
+  {
+   "fieldname": "column_break_22",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_23",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_recommendations",
+   "fieldtype": "Check",
+   "label": "Enable Recommendations"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.enable_checkout == 0",
+   "fieldname": "show_price_in_quotation",
+   "fieldtype": "Check",
+   "label": "Show Price in Quotation"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2021-09-02 14:02:44.785824",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "E Commerce Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
new file mode 100644
index 0000000..dd7b114
--- /dev/null
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/e_commerce_settings.py
@@ -0,0 +1,151 @@
+# -*- coding: utf-8 -*-
+# 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
+from frappe.utils import comma_and, flt, unique
+
+from erpnext.e_commerce.redisearch_utils import (
+	create_website_items_index,
+	get_indexable_web_fields,
+	is_search_module_loaded,
+)
+
+
+class ShoppingCartSetupError(frappe.ValidationError): pass
+
+class ECommerceSettings(Document):
+	def onload(self):
+		self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
+		self.is_redisearch_loaded = is_search_module_loaded()
+
+	def validate(self):
+		self.validate_field_filters()
+		self.validate_attribute_filters()
+		self.validate_checkout()
+		self.validate_search_index_fields()
+
+		if self.enabled:
+			self.validate_price_list_exchange_rate()
+
+		frappe.clear_document_cache("E Commerce Settings", "E Commerce Settings")
+
+	def validate_field_filters(self):
+		if not (self.enable_field_filters and self.filter_fields):
+			return
+
+		item_meta = frappe.get_meta("Item")
+		valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ["Link", "Table MultiSelect"]]
+
+		for f in self.filter_fields:
+			if f.fieldname not in valid_fields:
+				frappe.throw(_("Filter Fields Row #{0}: Fieldname <b>{1}</b> must be of type 'Link' or 'Table MultiSelect'").format(f.idx, f.fieldname))
+
+	def validate_attribute_filters(self):
+		if not (self.enable_attribute_filters and self.filter_attributes):
+			return
+
+		# if attribute filters are enabled, hide_variants should be disabled
+		self.hide_variants = 0
+
+	def validate_checkout(self):
+		if self.enable_checkout and not self.payment_gateway_account:
+			self.enable_checkout = 0
+
+	def validate_search_index_fields(self):
+		if not self.search_index_fields:
+			return
+
+		fields = self.search_index_fields.replace(' ', '')
+		fields = unique(fields.strip(',').split(',')) # Remove extra ',' and remove duplicates
+
+		# All fields should be indexable
+		allowed_indexable_fields = get_indexable_web_fields()
+
+		if not (set(fields).issubset(allowed_indexable_fields)):
+			invalid_fields = list(set(fields).difference(allowed_indexable_fields))
+			num_invalid_fields = len(invalid_fields)
+			invalid_fields = comma_and(invalid_fields)
+
+			if num_invalid_fields > 1:
+				frappe.throw(_("{0} are not valid options for Search Index Field.").format(frappe.bold(invalid_fields)))
+			else:
+				frappe.throw(_("{0} is not a valid option for Search Index Field.").format(frappe.bold(invalid_fields)))
+
+		self.search_index_fields = ','.join(fields)
+
+	def validate_price_list_exchange_rate(self):
+		"Check if exchange rate exists for Price List currency (to Company's currency)."
+		from erpnext.setup.utils import get_exchange_rate
+
+		if not self.enabled or not self.company or not self.price_list:
+			return # this function is also called from hooks, check values again
+
+		company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
+		price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency")
+
+		if not company_currency:
+			msg = f"Please specify currency in Company {self.company}"
+			frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
+
+		if not price_list_currency:
+			msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}"
+			frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
+
+		if price_list_currency != company_currency:
+			from_currency, to_currency = price_list_currency, company_currency
+
+			# Get exchange rate checks Currency Exchange Records too
+			exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling")
+
+			if not flt(exchange_rate):
+				msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}"
+				frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError)
+
+	def validate_tax_rule(self):
+		if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
+			frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
+
+	def get_tax_master(self, billing_territory):
+		tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
+			"sales_taxes_and_charges_master")
+		return tax_master and tax_master[0] or None
+
+	def get_shipping_rules(self, shipping_territory):
+		return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule")
+
+	def on_change(self):
+		old_doc = self.get_doc_before_save()
+
+		if old_doc:
+			old_fields = old_doc.search_index_fields
+			new_fields = self.search_index_fields
+
+			# if search index fields get changed
+			if not (new_fields == old_fields):
+				create_website_items_index()
+
+def validate_cart_settings(doc=None, method=None):
+	frappe.get_doc("E Commerce Settings", "E Commerce Settings").run_method("validate")
+
+def get_shopping_cart_settings():
+	if not getattr(frappe.local, "shopping_cart_settings", None):
+		frappe.local.shopping_cart_settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
+
+	return frappe.local.shopping_cart_settings
+
+@frappe.whitelist(allow_guest=True)
+def is_cart_enabled():
+	return get_shopping_cart_settings().enabled
+
+def show_quantity_in_website():
+	return get_shopping_cart_settings().show_quantity_in_website
+
+def check_shopping_cart_enabled():
+	if not get_shopping_cart_settings().enabled:
+		frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
+
+def show_attachments():
+	return get_shopping_cart_settings().show_attachments
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
similarity index 67%
rename from erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py
rename to erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
index c3809b3..86cef30 100644
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/test_shopping_cart_settings.py
+++ b/erpnext/e_commerce/doctype/e_commerce_settings/test_e_commerce_settings.py
@@ -1,24 +1,21 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-# For license information, please see license.txt
-
-
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
 import unittest
 
 import frappe
 
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	ShoppingCartSetupError,
 )
 
 
-class TestShoppingCartSettings(unittest.TestCase):
+class TestECommerceSettings(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql("""delete from `tabSingles` where doctype="Shipping Cart Settings" """)
 
 	def get_cart_settings(self):
-		return frappe.get_doc({"doctype": "Shopping Cart Settings",
+		return frappe.get_doc({"doctype": "E Commerce Settings",
 			"company": "_Test Company"})
 
 	# NOTE: Exchangrate API has all enabled currencies that ERPNext supports.
@@ -34,15 +31,17 @@
 
 	# 	cart_settings = self.get_cart_settings()
 	# 	cart_settings.price_list = "_Test Price List Rest of the World"
-	# 	self.assertRaises(ShoppingCartSetupError, cart_settings.validate_price_list_exchange_rate)
+	# 	self.assertRaises(ShoppingCartSetupError, cart_settings.validate_exchange_rates_exist)
 
-	# 	from erpnext.setup.doctype.currency_exchange.test_currency_exchange import test_records as \
-	# 		currency_exchange_records
+	# 	from erpnext.setup.doctype.currency_exchange.test_currency_exchange import (
+	# 		test_records as currency_exchange_records,
+	# 	)
 	# 	frappe.get_doc(currency_exchange_records[0]).insert()
-	# 	cart_settings.validate_price_list_exchange_rate()
+	# 	cart_settings.validate_exchange_rates_exist()
 
 	def test_tax_rule_validation(self):
 		frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 0")
+		frappe.db.commit() # nosemgrep
 
 		cart_settings = self.get_cart_settings()
 		cart_settings.enabled = 1
@@ -51,4 +50,13 @@
 
 		frappe.db.sql("update `tabTax Rule` set use_for_shopping_cart = 1")
 
+def setup_e_commerce_settings(values_dict):
+	"Accepts a dict of values that updates E Commerce Settings."
+	if not values_dict:
+		return
+
+	doc = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
+	doc.update(values_dict)
+	doc.save()
+
 test_dependencies = ["Tax Rule"]
diff --git a/erpnext/demo/__init__.py b/erpnext/e_commerce/doctype/item_review/__init__.py
similarity index 100%
copy from erpnext/demo/__init__.py
copy to erpnext/e_commerce/doctype/item_review/__init__.py
diff --git a/erpnext/e_commerce/doctype/item_review/item_review.js b/erpnext/e_commerce/doctype/item_review/item_review.js
new file mode 100644
index 0000000..a57c370
--- /dev/null
+++ b/erpnext/e_commerce/doctype/item_review/item_review.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('Item Review', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/e_commerce/doctype/item_review/item_review.json b/erpnext/e_commerce/doctype/item_review/item_review.json
new file mode 100644
index 0000000..57f719f
--- /dev/null
+++ b/erpnext/e_commerce/doctype/item_review/item_review.json
@@ -0,0 +1,134 @@
+{
+ "actions": [],
+ "beta": 1,
+ "creation": "2021-03-23 16:47:26.542226",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "website_item",
+  "user",
+  "customer",
+  "column_break_3",
+  "item",
+  "published_on",
+  "reviews_section",
+  "review_title",
+  "rating",
+  "comment"
+ ],
+ "fields": [
+  {
+   "fieldname": "website_item",
+   "fieldtype": "Link",
+   "label": "Website Item",
+   "options": "Website Item",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "User",
+   "options": "User",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "website_item.item_code",
+   "fieldname": "item",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item",
+   "options": "Item",
+   "read_only": 1
+  },
+  {
+   "fieldname": "reviews_section",
+   "fieldtype": "Section Break",
+   "label": "Reviews"
+  },
+  {
+   "fieldname": "rating",
+   "fieldtype": "Rating",
+   "in_list_view": 1,
+   "label": "Rating",
+   "read_only": 1
+  },
+  {
+   "fieldname": "comment",
+   "fieldtype": "Small Text",
+   "label": "Comment",
+   "read_only": 1
+  },
+  {
+   "fieldname": "review_title",
+   "fieldtype": "Data",
+   "label": "Review Title",
+   "read_only": 1
+  },
+  {
+   "fieldname": "customer",
+   "fieldtype": "Link",
+   "label": "Customer",
+   "options": "Customer",
+   "read_only": 1
+  },
+  {
+   "fieldname": "published_on",
+   "fieldtype": "Data",
+   "label": "Published on",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-08-10 12:08:58.119691",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Item Review",
+ "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": "Website Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "report": 1,
+   "role": "Customer",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/item_review/item_review.py b/erpnext/e_commerce/doctype/item_review/item_review.py
new file mode 100644
index 0000000..966ec35
--- /dev/null
+++ b/erpnext/e_commerce/doctype/item_review/item_review.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from datetime import datetime
+
+import frappe
+from frappe import _
+from frappe.contacts.doctype.contact.contact import get_contact_name
+from frappe.model.document import Document
+from frappe.utils import cint, flt
+
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
+	get_shopping_cart_settings,
+)
+
+
+class UnverifiedReviewer(frappe.ValidationError):
+	pass
+
+class ItemReview(Document):
+	def after_insert(self):
+		# regenerate cache on review creation
+		reviews_dict = get_queried_reviews(self.website_item)
+		set_reviews_in_cache(self.website_item, reviews_dict)
+
+	def after_delete(self):
+		# regenerate cache on review deletion
+		reviews_dict = get_queried_reviews(self.website_item)
+		set_reviews_in_cache(self.website_item, reviews_dict)
+
+
+@frappe.whitelist()
+def get_item_reviews(web_item, start=0, end=10, data=None):
+	"Get Website Item Review Data."
+	start, end = cint(start), cint(end)
+	settings = get_shopping_cart_settings()
+
+	# Get cached reviews for first page (start=0)
+	# avoid cache when page is different
+	from_cache = not bool(start)
+
+	if not data:
+		data = frappe._dict()
+
+	if settings and settings.get("enable_reviews"):
+		reviews_cache = frappe.cache().hget("item_reviews", web_item)
+		if from_cache and reviews_cache:
+			data = reviews_cache
+		else:
+			data = get_queried_reviews(web_item, start, end, data)
+			if from_cache:
+				set_reviews_in_cache(web_item, data)
+
+	return data
+
+def get_queried_reviews(web_item, start=0, end=10, data=None):
+	"""
+		Query Website Item wise reviews and cache if needed.
+		Cache stores only first page of reviews i.e. 10 reviews maximum.
+		Returns:
+			dict: Containing reviews, average ratings, % of reviews per rating and total reviews.
+	"""
+	if not data:
+		data = frappe._dict()
+
+	data.reviews = frappe.db.get_all(
+		"Item Review",
+		filters={"website_item": web_item},
+		fields=["*"],
+		limit_start=start,
+		limit_page_length=end
+	)
+
+	rating_data = frappe.db.get_all(
+		"Item Review",
+		filters={"website_item": web_item},
+		fields=["avg(rating) as average, count(*) as total"]
+	)[0]
+
+	data.average_rating = flt(rating_data.average, 1)
+	data.average_whole_rating = flt(data.average_rating, 0)
+
+	# get % of reviews per rating
+	reviews_per_rating = []
+	for i in range(1,6):
+		count = frappe.db.get_all(
+			"Item Review",
+			filters={"website_item": web_item, "rating": i},
+			fields=["count(*) as count"]
+		)[0].count
+
+		percent = flt((count / rating_data.total or 1) * 100, 0) if count else 0
+		reviews_per_rating.append(percent)
+
+	data.reviews_per_rating = reviews_per_rating
+	data.total_reviews = rating_data.total
+
+	return data
+
+def set_reviews_in_cache(web_item, reviews_dict):
+	frappe.cache().hset("item_reviews", web_item, reviews_dict)
+
+@frappe.whitelist()
+def add_item_review(web_item, title, rating, comment=None):
+	""" Add an Item Review by a user if non-existent. """
+	if frappe.session.user == "Guest":
+		# guest user should not reach here ideally in the case they do via an API, throw error
+		frappe.throw(_("You are not verified to write a review yet."), exc=UnverifiedReviewer)
+
+	if not frappe.db.exists("Item Review", {"user": frappe.session.user, "website_item": web_item}):
+		doc = frappe.get_doc({
+			"doctype": "Item Review",
+			"user": frappe.session.user,
+			"customer": get_customer(),
+			"website_item": web_item,
+			"item": frappe.db.get_value("Website Item", web_item, "item_code"),
+			"review_title": title,
+			"rating": rating,
+			"comment": comment
+		})
+		doc.published_on = datetime.today().strftime("%d %B %Y")
+		doc.insert()
+
+def get_customer(silent=False):
+	"""
+		silent: Return customer if exists else return nothing. Dont throw error.
+	"""
+	user = frappe.session.user
+	contact_name = get_contact_name(user)
+	customer = None
+
+	if contact_name:
+		contact = frappe.get_doc('Contact', contact_name)
+		for link in contact.links:
+			if link.link_doctype == "Customer":
+				customer = link.link_name
+				break
+
+	if customer:
+		return frappe.db.get_value("Customer", customer)
+	elif silent:
+		return None
+	else:
+		# should not reach here unless via an API
+		frappe.throw(_("You are not a verified customer yet. Please contact us to proceed."),
+			exc=UnverifiedReviewer)
diff --git a/erpnext/e_commerce/doctype/item_review/test_item_review.py b/erpnext/e_commerce/doctype/item_review/test_item_review.py
new file mode 100644
index 0000000..8a4befc
--- /dev/null
+++ b/erpnext/e_commerce/doctype/item_review/test_item_review.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+import unittest
+
+import frappe
+from frappe.core.doctype.user_permission.test_user_permission import create_user
+
+from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
+	setup_e_commerce_settings,
+)
+from erpnext.e_commerce.doctype.item_review.item_review import (
+	UnverifiedReviewer,
+	add_item_review,
+	get_item_reviews,
+)
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+from erpnext.e_commerce.shopping_cart.cart import get_party
+from erpnext.stock.doctype.item.test_item import make_item
+
+
+class TestItemReview(unittest.TestCase):
+	def setUp(self):
+		item = make_item("Test Mobile Phone")
+		if not frappe.db.exists("Website Item", {"item_code": "Test Mobile Phone"}):
+			make_website_item(item, save=True)
+
+		setup_e_commerce_settings({"enable_reviews": 1})
+		frappe.local.shopping_cart_settings = None
+
+	def tearDown(self):
+		frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete()
+		setup_e_commerce_settings({"enable_reviews": 0})
+
+	def test_add_and_get_item_reviews_from_customer(self):
+		"Add / Get Reviews from a User that is a valid customer (has added to cart or purchased in the past)"
+		# create user
+		web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"})
+		test_user = create_user("test_reviewer@example.com", "Customer")
+		frappe.set_user(test_user.name)
+
+		# create customer and contact against user
+		customer = get_party()
+
+		# post review on "Test Mobile Phone"
+		try:
+			add_item_review(web_item, "Great Product", 3, "Would recommend this product")
+			review_name = frappe.db.get_value("Item Review", {"website_item": web_item})
+		except Exception:
+			self.fail(f"Error while publishing review for {web_item}")
+
+		review_data = get_item_reviews(web_item, 0, 10)
+
+		self.assertEqual(len(review_data.reviews), 1)
+		self.assertEqual(review_data.average_rating, 3)
+		self.assertEqual(review_data.reviews_per_rating[2], 100)
+
+		# tear down
+		frappe.set_user("Administrator")
+		frappe.delete_doc("Item Review", review_name)
+		customer.delete()
+
+	def test_add_item_review_from_non_customer(self):
+		"Check if logged in user (who is not a customer yet) is blocked from posting reviews."
+		web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"})
+		test_user = create_user("test_reviewer@example.com", "Customer")
+		frappe.set_user(test_user.name)
+
+		with self.assertRaises(UnverifiedReviewer):
+			add_item_review(web_item, "Great Product", 3, "Would recommend this product")
+
+		# tear down
+		frappe.set_user("Administrator")
+
+	def test_add_item_reviews_from_guest_user(self):
+		"Check if Guest user is blocked from posting reviews."
+		web_item = frappe.db.get_value("Website Item", {"item_code": "Test Mobile Phone"})
+		frappe.set_user("Guest")
+
+		with self.assertRaises(UnverifiedReviewer):
+			add_item_review(web_item, "Great Product", 3, "Would recommend this product")
+
+		# tear down
+		frappe.set_user("Administrator")
diff --git a/erpnext/hotels/__init__.py b/erpnext/e_commerce/doctype/recommended_items/__init__.py
similarity index 100%
rename from erpnext/hotels/__init__.py
rename to erpnext/e_commerce/doctype/recommended_items/__init__.py
diff --git a/erpnext/e_commerce/doctype/recommended_items/recommended_items.json b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json
new file mode 100644
index 0000000..06ac3dc
--- /dev/null
+++ b/erpnext/e_commerce/doctype/recommended_items/recommended_items.json
@@ -0,0 +1,87 @@
+{
+ "actions": [],
+ "creation": "2021-07-12 20:52:12.503470",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "website_item",
+  "website_item_name",
+  "column_break_2",
+  "item_code",
+  "more_information_section",
+  "route",
+  "column_break_6",
+  "website_item_image",
+  "website_item_thumbnail"
+ ],
+ "fields": [
+  {
+   "fieldname": "website_item",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Website Item",
+   "options": "Website Item"
+  },
+  {
+   "fetch_from": "website_item.web_item_name",
+   "fieldname": "website_item_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Website Item Name",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "more_information_section",
+   "fieldtype": "Section Break",
+   "label": "More Information"
+  },
+  {
+   "fetch_from": "website_item.route",
+   "fieldname": "route",
+   "fieldtype": "Small Text",
+   "label": "Route",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "website_item.image",
+   "fieldname": "website_item_image",
+   "fieldtype": "Attach",
+   "label": "Website Item Image",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_6",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "website_item.thumbnail",
+   "fieldname": "website_item_thumbnail",
+   "fieldtype": "Data",
+   "label": "Website Item Thumbnail",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "website_item.item_code",
+   "fieldname": "item_code",
+   "fieldtype": "Data",
+   "label": "Item Code"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-13 21:02:19.031652",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Recommended Items",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py
similarity index 77%
rename from erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
rename to erpnext/e_commerce/doctype/recommended_items/recommended_items.py
index bb7f07f..16b6e52 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
+++ b/erpnext/e_commerce/doctype/recommended_items/recommended_items.py
@@ -1,10 +1,9 @@
 # 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 TaxExemption80GCertificateDetail(Document):
+class RecommendedItems(Document):
 	pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py b/erpnext/e_commerce/doctype/website_item/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py
rename to erpnext/e_commerce/doctype/website_item/__init__.py
diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item.html b/erpnext/e_commerce/doctype/website_item/templates/website_item.html
new file mode 100644
index 0000000..db12309
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/templates/website_item.html
@@ -0,0 +1,7 @@
+{% extends "templates/web.html" %}
+
+{% block page_content %}
+<h1>{{ title }}</h1>
+{% endblock %}
+
+<!-- this is a sample default web page template -->
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html b/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html
new file mode 100644
index 0000000..d7014b4
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/templates/website_item_row.html
@@ -0,0 +1,4 @@
+<div>
+	<a href="{{ doc.route }}">{{ doc.title or doc.name }}</a>
+</div>
+<!-- this is a sample default list template -->
diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py
new file mode 100644
index 0000000..b39e4df
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py
@@ -0,0 +1,538 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import unittest
+
+import frappe
+
+from erpnext.controllers.item_variant import create_variant
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
+	get_shopping_cart_settings,
+)
+from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
+	setup_e_commerce_settings,
+)
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
+from erpnext.stock.doctype.item.item import DataValidationError
+from erpnext.stock.doctype.item.test_item import make_item
+
+WEBITEM_DESK_TESTS = ("test_website_item_desk_item_sync", "test_publish_variant_and_template")
+WEBITEM_PRICE_TESTS = ('test_website_item_price_for_logged_in_user', 'test_website_item_price_for_guest_user')
+
+class TestWebsiteItem(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls):
+		setup_e_commerce_settings({
+			"company": "_Test Company",
+			"enabled": 1,
+			"default_customer_group": "_Test Customer Group",
+			"price_list": "_Test Price List India"
+		})
+
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.rollback()
+
+	def setUp(self):
+		if self._testMethodName in WEBITEM_DESK_TESTS:
+			make_item("Test Web Item", {
+				"has_variant": 1,
+				"variant_based_on": "Item Attribute",
+				"attributes": [
+					{
+						"attribute": "Test Size"
+					}
+				]
+			})
+		elif self._testMethodName in WEBITEM_PRICE_TESTS:
+			create_user_and_customer_if_not_exists("test_contact_customer@example.com", "_Test Contact For _Test Customer")
+			create_regular_web_item()
+			make_web_item_price(item_code="Test Mobile Phone")
+
+			# Note: When testing web item pricing rule logged-in user pricing rule must differ from guest pricing rule or test will falsely pass.
+			#	  This is because make_web_pricing_rule creates a pricing rule "selling": 1, without specifying "applicable_for". Therefor,
+			#	  when testing for logged-in user the test will get the previous pricing rule because "selling" is still true.
+			#
+			#     I've attempted to mitigate this by setting applicable_for=Customer, and customer=Guest however, this only results in PermissionError failing the test.
+			make_web_pricing_rule(
+				title="Test Pricing Rule for Test Mobile Phone",
+				item_code="Test Mobile Phone",
+				selling=1)
+			make_web_pricing_rule(
+				title="Test Pricing Rule for Test Mobile Phone (Customer)",
+				item_code="Test Mobile Phone",
+				selling=1,
+				discount_percentage="25",
+				applicable_for="Customer",
+				customer="_Test Customer")
+
+	def test_index_creation(self):
+		"Check if index is getting created in db."
+		from erpnext.e_commerce.doctype.website_item.website_item import on_doctype_update
+		on_doctype_update()
+
+		indices = frappe.db.sql("show index from `tabWebsite Item`", as_dict=1)
+		expected_columns = {"route", "item_group", "brand"}
+		for index in indices:
+			expected_columns.discard(index.get("Column_name"))
+
+		if expected_columns:
+			self.fail(f"Expected db index on these columns: {', '.join(expected_columns)}")
+
+	def test_website_item_desk_item_sync(self):
+		"Check creation/updation/deletion of Website Item and its impact on Item master."
+		web_item = None
+		item = make_item("Test Web Item") # will return item if exists
+		try:
+			web_item = make_website_item(item, save=False)
+			web_item.save()
+		except Exception:
+			self.fail(f"Error while creating website item for {item}")
+
+		# check if website item was created
+		self.assertTrue(bool(web_item))
+		self.assertTrue(bool(web_item.route))
+
+		item.reload()
+		self.assertEqual(web_item.published, 1)
+		self.assertEqual(item.published_in_website, 1) # check if item was back updated
+		self.assertEqual(web_item.item_group, item.item_group)
+
+		# check if changing item data changes it in website item
+		item.item_name = "Test Web Item 1"
+		item.stock_uom = "Unit"
+		item.save()
+		web_item.reload()
+		self.assertEqual(web_item.item_name, item.item_name)
+		self.assertEqual(web_item.stock_uom, item.stock_uom)
+
+		# check if disabling item unpublished website item
+		item.disabled = 1
+		item.save()
+		web_item.reload()
+		self.assertEqual(web_item.published, 0)
+
+		# check if website item deletion, unpublishes desk item
+		web_item.delete()
+		item.reload()
+		self.assertEqual(item.published_in_website, 0)
+
+		item.delete()
+
+	def test_publish_variant_and_template(self):
+		"Check if template is published on publishing variant."
+		# template "Test Web Item" created on setUp
+		variant = create_variant("Test Web Item", {"Test Size": "Large"})
+		variant.save()
+
+		# check if template is not published
+		self.assertIsNone(frappe.db.exists("Website Item", {"item_code": variant.variant_of}))
+
+		variant_web_item = make_website_item(variant, save=False)
+		variant_web_item.save()
+
+		# check if template is published
+		try:
+			template_web_item = frappe.get_doc("Website Item", {"item_code": variant.variant_of})
+		except frappe.DoesNotExistError:
+			self.fail(f"Template of {variant.item_code}, {variant.variant_of} not published")
+
+		# teardown
+		variant_web_item.delete()
+		template_web_item.delete()
+		variant.delete()
+
+	def test_impact_on_merging_items(self):
+		"Check if merging items is blocked if old and new items both have website items"
+		first_item = make_item("Test First Item")
+		second_item = make_item("Test Second Item")
+
+		first_web_item = make_website_item(first_item, save=False)
+		first_web_item.save()
+		second_web_item = make_website_item(second_item, save=False)
+		second_web_item.save()
+
+		with self.assertRaises(DataValidationError):
+			frappe.rename_doc("Item", "Test First Item", "Test Second Item", merge=True)
+
+		# tear down
+		second_web_item.delete()
+		first_web_item.delete()
+		second_item.delete()
+		first_item.delete()
+
+	# Website Item Portal Tests Begin
+
+	def test_website_item_breadcrumbs(self):
+		"Check if breadcrumbs include homepage, product listing navigation page, parent item group(s) and item group."
+		from erpnext.setup.doctype.item_group.item_group import get_parent_item_groups
+
+		item_code = "Test Breadcrumb Item"
+		item = make_item(item_code, {
+			"item_group": "_Test Item Group B - 1",
+		})
+
+		if not frappe.db.exists("Website Item", {"item_code": item_code}):
+			web_item = make_website_item(item, save=False)
+			web_item.save()
+		else:
+			web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code})
+
+		frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
+		frappe.db.set_value("Item Group", "_Test Item Group B", "show_in_website", 1)
+
+		breadcrumbs = get_parent_item_groups(item.item_group)
+
+		self.assertEqual(breadcrumbs[0]["name"], "Home")
+		self.assertEqual(breadcrumbs[1]["name"], "Shop by Category")
+		self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B") # parent item group
+		self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
+
+		# tear down
+		web_item.delete()
+		item.delete()
+
+	def test_website_item_price_for_logged_in_user(self):
+		"Check if price details are fetched correctly while logged in."
+		item_code = "Test Mobile Phone"
+
+		# show price in e commerce settings
+		setup_e_commerce_settings({"show_price": 1})
+
+		# price and pricing rule added via setUp
+
+		# login as customer with pricing rule
+		frappe.set_user("test_contact_customer@example.com")
+
+		# check if price and slashed price is fetched correctly
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertTrue(bool(data.product_info["price"]))
+
+		price_object = data.product_info["price"]
+		self.assertEqual(price_object.get("discount_percent"), 25)
+		self.assertEqual(price_object.get("price_list_rate"), 750)
+		self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00")
+		self.assertEqual(price_object.get("formatted_price"), "₹ 750.00")
+		self.assertEqual(price_object.get("formatted_discount_percent"), "25%")
+
+		# switch to admin and disable show price
+		frappe.set_user("Administrator")
+		setup_e_commerce_settings({"show_price": 0})
+
+		# price should not be fetched for logged in user.
+		frappe.set_user("test_contact_customer@example.com")
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertFalse(bool(data.product_info["price"]))
+
+		# tear down
+		frappe.set_user("Administrator")
+
+	def test_website_item_price_for_guest_user(self):
+		"Check if price details are fetched correctly for guest user."
+		item_code = "Test Mobile Phone"
+
+		# show price for guest user in e commerce settings
+		setup_e_commerce_settings({
+			"show_price": 1,
+			"hide_price_for_guest": 0
+		})
+
+		# price and pricing rule added via setUp
+
+		# switch to guest user
+		frappe.set_user("Guest")
+
+		# price should be fetched
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertTrue(bool(data.product_info["price"]))
+
+		price_object = data.product_info["price"]
+		self.assertEqual(price_object.get("discount_percent"), 10)
+		self.assertEqual(price_object.get("price_list_rate"), 900)
+
+		# hide price for guest user
+		frappe.set_user("Administrator")
+		setup_e_commerce_settings({"hide_price_for_guest": 1})
+		frappe.set_user("Guest")
+
+		# price should not be fetched
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertFalse(bool(data.product_info["price"]))
+
+		# tear down
+		frappe.set_user("Administrator")
+
+	def test_website_item_stock_when_out_of_stock(self):
+		"""
+			Check if stock details are fetched correctly for empty inventory when:
+			1) Showing stock availability enabled:
+				- Warehouse unset
+				- Warehouse set
+			2) Showing stock availability disabled
+		"""
+		item_code = "Test Mobile Phone"
+		create_regular_web_item()
+		setup_e_commerce_settings({"show_stock_availability": 1})
+
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+
+		# check if stock details are fetched and item not in stock without warehouse set
+		self.assertFalse(bool(data.product_info["in_stock"]))
+		self.assertFalse(bool(data.product_info["stock_qty"]))
+
+		# set warehouse
+		frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC")
+
+		# check if stock details are fetched and item not in stock with warehouse set
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertFalse(bool(data.product_info["in_stock"]))
+		self.assertEqual(data.product_info["stock_qty"][0][0], 0)
+
+		# disable show stock availability
+		setup_e_commerce_settings({"show_stock_availability": 0})
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+
+		# check if stock detail attributes are not fetched if stock availability is hidden
+		self.assertIsNone(data.product_info.get("in_stock"))
+		self.assertIsNone(data.product_info.get("stock_qty"))
+		self.assertIsNone(data.product_info.get("show_stock_qty"))
+
+		# tear down
+		frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete()
+
+	def test_website_item_stock_when_in_stock(self):
+		"""
+			Check if stock details are fetched correctly for available inventory when:
+			1) Showing stock availability enabled:
+				- Warehouse set
+				- Warehouse unset
+			2) Showing stock availability disabled
+		"""
+		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+		item_code = "Test Mobile Phone"
+		create_regular_web_item()
+		setup_e_commerce_settings({"show_stock_availability": 1})
+		frappe.local.shopping_cart_settings = None
+
+		# set warehouse
+		frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "_Test Warehouse - _TC")
+
+		# stock up item
+		stock_entry = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=2, rate=100)
+
+		# check if stock details are fetched and item is in stock with warehouse set
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertTrue(bool(data.product_info["in_stock"]))
+		self.assertEqual(data.product_info["stock_qty"][0][0], 2)
+
+		# unset warehouse
+		frappe.db.set_value("Website Item", {"item_code": item_code}, "website_warehouse", "")
+
+		# check if stock details are fetched and item not in stock without warehouse set
+		# (even though it has stock in some warehouse)
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+		self.assertFalse(bool(data.product_info["in_stock"]))
+		self.assertFalse(bool(data.product_info["stock_qty"]))
+
+		# disable show stock availability
+		setup_e_commerce_settings({"show_stock_availability": 0})
+		frappe.local.shopping_cart_settings = None
+		data = get_product_info_for_website(item_code, skip_quotation_creation=True)
+
+		# check if stock detail attributes are not fetched if stock availability is hidden
+		self.assertIsNone(data.product_info.get("in_stock"))
+		self.assertIsNone(data.product_info.get("stock_qty"))
+		self.assertIsNone(data.product_info.get("show_stock_qty"))
+
+		# tear down
+		stock_entry.cancel()
+		frappe.get_cached_doc("Website Item", {"item_code": "Test Mobile Phone"}).delete()
+
+	def test_recommended_item(self):
+		"Check if added recommended items are fetched correctly."
+		item_code = "Test Mobile Phone"
+		web_item = create_regular_web_item(item_code)
+
+		setup_e_commerce_settings({
+			"enable_recommendations": 1,
+			"show_price": 1
+		})
+
+		# create recommended web item and price for it
+		recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
+		make_web_item_price(item_code="Test Mobile Phone 1")
+
+		# add recommended item to first web item
+		web_item.append("recommended_items", {"website_item": recommended_web_item.name})
+		web_item.save()
+
+		frappe.local.shopping_cart_settings = None
+		e_commerce_settings = get_shopping_cart_settings()
+		recommended_items = web_item.get_recommended_items(e_commerce_settings)
+
+		# test results if show price is enabled
+		self.assertEqual(len(recommended_items), 1)
+		recomm_item = recommended_items[0]
+		self.assertEqual(recomm_item.get("website_item_name"), "Test Mobile Phone 1")
+		self.assertTrue(bool(recomm_item.get("price_info"))) # price fetched
+
+		price_info = recomm_item.get("price_info")
+		self.assertEqual(price_info.get("price_list_rate"), 1000)
+		self.assertEqual(price_info.get("formatted_price"), "₹ 1,000.00")
+
+		# test results if show price is disabled
+		setup_e_commerce_settings({"show_price": 0})
+
+		frappe.local.shopping_cart_settings = None
+		e_commerce_settings = get_shopping_cart_settings()
+		recommended_items = web_item.get_recommended_items(e_commerce_settings)
+
+		self.assertEqual(len(recommended_items), 1)
+		self.assertFalse(bool(recommended_items[0].get("price_info"))) # price not fetched
+
+		# tear down
+		web_item.delete()
+		recommended_web_item.delete()
+		frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete()
+
+	def test_recommended_item_for_guest_user(self):
+		"Check if added recommended items are fetched correctly for guest user."
+		item_code = "Test Mobile Phone"
+		web_item = create_regular_web_item(item_code)
+
+		# price visible to guests
+		setup_e_commerce_settings({
+			"enable_recommendations": 1,
+			"show_price": 1,
+			"hide_price_for_guest": 0
+		})
+
+		# create recommended web item and price for it
+		recommended_web_item = create_regular_web_item("Test Mobile Phone 1")
+		make_web_item_price(item_code="Test Mobile Phone 1")
+
+		# add recommended item to first web item
+		web_item.append("recommended_items", {"website_item": recommended_web_item.name})
+		web_item.save()
+
+		frappe.set_user("Guest")
+
+		frappe.local.shopping_cart_settings = None
+		e_commerce_settings = get_shopping_cart_settings()
+		recommended_items = web_item.get_recommended_items(e_commerce_settings)
+
+		# test results if show price is enabled
+		self.assertEqual(len(recommended_items), 1)
+		self.assertTrue(bool(recommended_items[0].get("price_info"))) # price fetched
+
+		# price hidden from guests
+		frappe.set_user("Administrator")
+		setup_e_commerce_settings({"hide_price_for_guest": 1})
+		frappe.set_user("Guest")
+
+		frappe.local.shopping_cart_settings = None
+		e_commerce_settings = get_shopping_cart_settings()
+		recommended_items = web_item.get_recommended_items(e_commerce_settings)
+
+		# test results if show price is enabled
+		self.assertEqual(len(recommended_items), 1)
+		self.assertFalse(bool(recommended_items[0].get("price_info"))) # price fetched
+
+		# tear down
+		frappe.set_user("Administrator")
+		web_item.delete()
+		recommended_web_item.delete()
+		frappe.get_cached_doc("Item", "Test Mobile Phone 1").delete()
+
+def create_regular_web_item(item_code=None, item_args=None, web_args=None):
+	"Create Regular Item and Website Item."
+	item_code = item_code or "Test Mobile Phone"
+	item = make_item(item_code, properties=item_args)
+
+	if not frappe.db.exists("Website Item", {"item_code": item_code}):
+		web_item = make_website_item(item, save=False)
+		if web_args:
+			web_item.update(web_args)
+		web_item.save()
+	else:
+		web_item = frappe.get_cached_doc("Website Item", {"item_code": item_code})
+
+	return web_item
+
+def make_web_item_price(**kwargs):
+	item_code = kwargs.get("item_code")
+	if not item_code:
+		return
+
+	if not frappe.db.exists("Item Price", {"item_code": item_code}):
+		item_price = frappe.get_doc({
+			"doctype": "Item Price",
+			"item_code": item_code,
+			"price_list": kwargs.get("price_list") or "_Test Price List India",
+			"price_list_rate": kwargs.get("price_list_rate") or 1000
+		})
+		item_price.insert()
+	else:
+		item_price = frappe.get_cached_doc("Item Price", {"item_code": item_code})
+
+	return item_price
+
+def make_web_pricing_rule(**kwargs):
+	title = kwargs.get("title")
+	if not title:
+		return
+
+	if not frappe.db.exists("Pricing Rule", title):
+		pricing_rule = frappe.get_doc({
+			"doctype": "Pricing Rule",
+			"title": title,
+			"apply_on": kwargs.get("apply_on") or "Item Code",
+			"items": [{
+				"item_code": kwargs.get("item_code")
+			}],
+			"selling": kwargs.get("selling") or 0,
+			"buying": kwargs.get("buying") or 0,
+			"rate_or_discount": kwargs.get("rate_or_discount") or "Discount Percentage",
+			"discount_percentage": kwargs.get("discount_percentage") or 10,
+			"company": kwargs.get("company") or "_Test Company",
+			"currency": kwargs.get("currency") or "INR",
+			"for_price_list": kwargs.get("price_list") or "_Test Price List India",
+			"applicable_for": kwargs.get("applicable_for") or "",
+			"customer": kwargs.get("customer") or "",
+		})
+		pricing_rule.insert()
+	else:
+		pricing_rule = frappe.get_doc("Pricing Rule", {"title": title})
+
+	return pricing_rule
+
+
+def create_user_and_customer_if_not_exists(email, first_name = None):
+	if frappe.db.exists("User", email):
+		return
+
+	frappe.get_doc({
+		"doctype": "User",
+		"user_type": "Website User",
+		"email": email,
+		"send_welcome_email": 0,
+		"first_name": first_name or email.split("@")[0]
+	}).insert(ignore_permissions=True)
+
+	contact = frappe.get_last_doc("Contact", filters={"email_id": email})
+	link = contact.append('links', {})
+	link.link_doctype = "Customer"
+	link.link_name = "_Test Customer"
+	link.link_title = "_Test Customer"
+	contact.save()
+
+test_dependencies = ["Price List", "Item Price", "Customer", "Contact", "Item"]
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.js b/erpnext/e_commerce/doctype/website_item/website_item.js
new file mode 100644
index 0000000..741e78f
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/website_item.js
@@ -0,0 +1,24 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Website Item', {
+	onload: function(frm) {
+		// should never check Private
+		frm.fields_dict["website_image"].df.is_private = 0;
+	},
+
+	image: function() {
+		refresh_field("image_view");
+	},
+
+	copy_from_item_group: function(frm) {
+		return frm.call({
+			doc: frm.doc,
+			method: "copy_specification_from_item_group"
+		});
+	},
+
+	set_meta_tags(frm) {
+		frappe.utils.set_meta_tag(frm.doc.route);
+	}
+});
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.json b/erpnext/e_commerce/doctype/website_item/website_item.json
new file mode 100644
index 0000000..245042a
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/website_item.json
@@ -0,0 +1,415 @@
+{
+ "actions": [],
+ "allow_guest_to_view": 1,
+ "allow_import": 1,
+ "autoname": "naming_series",
+ "creation": "2021-02-09 21:06:14.441698",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "naming_series",
+  "web_item_name",
+  "route",
+  "has_variants",
+  "variant_of",
+  "published",
+  "column_break_3",
+  "item_code",
+  "item_name",
+  "item_group",
+  "stock_uom",
+  "column_break_11",
+  "description",
+  "brand",
+  "image",
+  "display_section",
+  "website_image",
+  "website_image_alt",
+  "column_break_13",
+  "slideshow",
+  "thumbnail",
+  "stock_information_section",
+  "website_warehouse",
+  "column_break_24",
+  "on_backorder",
+  "section_break_17",
+  "short_description",
+  "web_long_description",
+  "column_break_27",
+  "website_specifications",
+  "copy_from_item_group",
+  "display_additional_information_section",
+  "show_tabbed_section",
+  "tabs",
+  "recommended_items_section",
+  "recommended_items",
+  "offers_section",
+  "offers",
+  "section_break_6",
+  "ranking",
+  "set_meta_tags",
+  "column_break_22",
+  "website_item_groups",
+  "advanced_display_section",
+  "website_content"
+ ],
+ "fields": [
+  {
+   "description": "Website display name",
+   "fetch_from": "item_code.item_name",
+   "fetch_if_empty": 1,
+   "fieldname": "web_item_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Website Item Name",
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "label": "Item Code",
+   "options": "Item",
+   "read_only_depends_on": "eval:!doc.__islocal",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break",
+   "label": "Search and SEO"
+  },
+  {
+   "fieldname": "route",
+   "fieldtype": "Small Text",
+   "in_list_view": 1,
+   "label": "Route",
+   "no_copy": 1
+  },
+  {
+   "description": "Items with higher ranking will be shown higher",
+   "fieldname": "ranking",
+   "fieldtype": "Int",
+   "label": "Ranking"
+  },
+  {
+   "description": "Show a slideshow at the top of the page",
+   "fieldname": "slideshow",
+   "fieldtype": "Link",
+   "label": "Slideshow",
+   "options": "Website Slideshow"
+  },
+  {
+   "description": "Item Image (if not slideshow)",
+   "fieldname": "website_image",
+   "fieldtype": "Attach",
+   "label": "Website Image"
+  },
+  {
+   "description": "Image Alternative Text",
+   "fieldname": "website_image_alt",
+   "fieldtype": "Data",
+   "label": "Image Description"
+  },
+  {
+   "fieldname": "thumbnail",
+   "fieldtype": "Data",
+   "label": "Thumbnail",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_13",
+   "fieldtype": "Column Break"
+  },
+  {
+   "description": "Show Stock availability based on this warehouse.",
+   "fieldname": "website_warehouse",
+   "fieldtype": "Link",
+   "ignore_user_permissions": 1,
+   "label": "Website Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "description": "List this Item in multiple groups on the website.",
+   "fieldname": "website_item_groups",
+   "fieldtype": "Table",
+   "label": "Website Item Groups",
+   "options": "Website Item Group"
+  },
+  {
+   "fieldname": "set_meta_tags",
+   "fieldtype": "Button",
+   "label": "Set Meta Tags"
+  },
+  {
+   "fieldname": "section_break_17",
+   "fieldtype": "Section Break",
+   "label": "Display Information"
+  },
+  {
+   "fieldname": "copy_from_item_group",
+   "fieldtype": "Button",
+   "label": "Copy From Item Group"
+  },
+  {
+   "fieldname": "website_specifications",
+   "fieldtype": "Table",
+   "label": "Website Specifications",
+   "options": "Item Website Specification"
+  },
+  {
+   "fieldname": "web_long_description",
+   "fieldtype": "Text Editor",
+   "label": "Website Description"
+  },
+  {
+   "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.",
+   "fieldname": "website_content",
+   "fieldtype": "HTML Editor",
+   "label": "Website Content"
+  },
+  {
+   "fetch_from": "item_code.item_group",
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item Group",
+   "options": "Item Group",
+   "read_only": 1
+  },
+  {
+   "fieldname": "image",
+   "fieldtype": "Attach Image",
+   "hidden": 1,
+   "in_preview": 1,
+   "label": "Image",
+   "print_hide": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "published",
+   "fieldtype": "Check",
+   "label": "Published"
+  },
+  {
+   "default": "0",
+   "depends_on": "has_variants",
+   "fetch_from": "item_code.has_variants",
+   "fieldname": "has_variants",
+   "fieldtype": "Check",
+   "in_standard_filter": 1,
+   "label": "Has Variants",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "depends_on": "variant_of",
+   "fetch_from": "item_code.variant_of",
+   "fieldname": "variant_of",
+   "fieldtype": "Link",
+   "ignore_user_permissions": 1,
+   "in_standard_filter": 1,
+   "label": "Variant Of",
+   "options": "Item",
+   "read_only": 1,
+   "search_index": 1,
+   "set_only_once": 1
+  },
+  {
+   "fetch_from": "item_code.stock_uom",
+   "fieldname": "stock_uom",
+   "fieldtype": "Link",
+   "label": "Stock UOM",
+   "options": "UOM",
+   "read_only": 1
+  },
+  {
+   "depends_on": "brand",
+   "fetch_from": "item_code.brand",
+   "fieldname": "brand",
+   "fieldtype": "Link",
+   "label": "Brand",
+   "options": "Brand"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "advanced_display_section",
+   "fieldtype": "Section Break",
+   "label": "Advanced Display Content"
+  },
+  {
+   "fieldname": "display_section",
+   "fieldtype": "Section Break",
+   "label": "Display Images"
+  },
+  {
+   "fieldname": "column_break_27",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_22",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "item_code.description",
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Item Description",
+   "read_only": 1
+  },
+  {
+   "default": "WEB-ITM-.####",
+   "fieldname": "naming_series",
+   "fieldtype": "Select",
+   "hidden": 1,
+   "label": "Naming Series",
+   "no_copy": 1,
+   "options": "WEB-ITM-.####",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "display_additional_information_section",
+   "fieldtype": "Section Break",
+   "label": "Display Additional Information"
+  },
+  {
+   "depends_on": "show_tabbed_section",
+   "fieldname": "tabs",
+   "fieldtype": "Table",
+   "label": "Tabs",
+   "options": "Website Item Tabbed Section"
+  },
+  {
+   "default": "0",
+   "fieldname": "show_tabbed_section",
+   "fieldtype": "Check",
+   "label": "Add Section with Tabs"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "offers_section",
+   "fieldtype": "Section Break",
+   "label": "Offers"
+  },
+  {
+   "fieldname": "offers",
+   "fieldtype": "Table",
+   "label": "Offers to Display",
+   "options": "Website Offer"
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
+  },
+  {
+   "description": "Short Description for List View",
+   "fieldname": "short_description",
+   "fieldtype": "Small Text",
+   "label": "Short Website Description"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "recommended_items_section",
+   "fieldtype": "Section Break",
+   "label": "Recommended Items"
+  },
+  {
+   "fieldname": "recommended_items",
+   "fieldtype": "Table",
+   "label": "Recommended/Similar Items",
+   "options": "Recommended Items"
+  },
+  {
+   "fieldname": "stock_information_section",
+   "fieldtype": "Section Break",
+   "label": "Stock Information"
+  },
+  {
+   "fieldname": "column_break_24",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "Indicate that Item is available on backorder and not usually pre-stocked",
+   "fieldname": "on_backorder",
+   "fieldtype": "Check",
+   "label": "On Backorder"
+  }
+ ],
+ "has_web_view": 1,
+ "image_field": "image",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-09-02 13:08:41.942726",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Website Item",
+ "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": "Website Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "search_fields": "web_item_name, item_code, item_group",
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "title_field": "web_item_name",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/website_item/website_item.py b/erpnext/e_commerce/doctype/website_item/website_item.py
new file mode 100644
index 0000000..62f7f49
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/website_item.py
@@ -0,0 +1,441 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import json
+
+import frappe
+from frappe import _
+from frappe.utils import cint, cstr, flt, random_string
+from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
+from frappe.website.website_generator import WebsiteGenerator
+
+from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
+from erpnext.e_commerce.redisearch_utils import (
+	delete_item_from_index,
+	insert_item_to_index,
+	update_index_for_item,
+)
+from erpnext.e_commerce.shopping_cart.cart import _set_price_list
+from erpnext.setup.doctype.item_group.item_group import (
+	get_parent_item_groups,
+	invalidate_cache_for,
+)
+from erpnext.utilities.product import get_price
+
+
+class WebsiteItem(WebsiteGenerator):
+	website = frappe._dict(
+		page_title_field="web_item_name",
+		condition_field="published",
+		template="templates/generators/item/item.html",
+		no_cache=1
+	)
+
+	def autoname(self):
+		# use naming series to accomodate items with same name (different item code)
+		from frappe.model.naming import make_autoname
+
+		from erpnext.setup.doctype.naming_series.naming_series import get_default_naming_series
+
+		naming_series = get_default_naming_series("Website Item")
+		if not self.name and naming_series:
+			self.name = make_autoname(naming_series, doc=self)
+
+	def onload(self):
+		super(WebsiteItem, self).onload()
+
+	def validate(self):
+		super(WebsiteItem, self).validate()
+
+		if not self.item_code:
+			frappe.throw(_("Item Code is required"), title=_("Mandatory"))
+
+		self.validate_duplicate_website_item()
+		self.validate_website_image()
+		self.make_thumbnail()
+		self.publish_unpublish_desk_item(publish=True)
+
+		if not self.get("__islocal"):
+			wig = frappe.qb.DocType("Website Item Group")
+			query = (
+				frappe.qb.from_(wig)
+				.select(wig.item_group)
+				.where(
+					(wig.parentfield == "website_item_groups")
+					& (wig.parenttype == "Website Item")
+					& (wig.parent == self.name)
+				)
+			)
+			result = query.run(as_list=True)
+
+			self.old_website_item_groups = [x[0] for x in result]
+
+	def on_update(self):
+		invalidate_cache_for_web_item(self)
+		self.update_template_item()
+
+	def on_trash(self):
+		super(WebsiteItem, self).on_trash()
+		delete_item_from_index(self)
+		self.publish_unpublish_desk_item(publish=False)
+
+	def validate_duplicate_website_item(self):
+		existing_web_item = frappe.db.exists("Website Item", {"item_code": self.item_code})
+		if existing_web_item and existing_web_item != self.name:
+			message = _("Website Item already exists against Item {0}").format(frappe.bold(self.item_code))
+			frappe.throw(message, title=_("Already Published"))
+
+	def publish_unpublish_desk_item(self, publish=True):
+		if frappe.db.get_value("Item", self.item_code, "published_in_website") and publish:
+			return # if already published don't publish again
+		frappe.db.set_value("Item", self.item_code, "published_in_website", publish)
+
+	def make_route(self):
+		"""Called from set_route in WebsiteGenerator."""
+		if not self.route:
+			return cstr(frappe.db.get_value('Item Group', self.item_group,
+					'route')) + '/' + self.scrub((self.item_name if self.item_name else self.item_code) + '-' + random_string(5))
+
+	def update_template_item(self):
+		"""Publish Template Item if Variant is published."""
+		if self.variant_of:
+			if self.published:
+				# show template
+				template_item = frappe.get_doc("Item", self.variant_of)
+
+				if not template_item.published_in_website:
+					template_item.flags.ignore_permissions = True
+					make_website_item(template_item)
+
+	def validate_website_image(self):
+		if frappe.flags.in_import:
+			return
+
+		"""Validate if the website image is a public file"""
+		auto_set_website_image = False
+		if not self.website_image and self.image:
+			auto_set_website_image = True
+			self.website_image = self.image
+
+		if not self.website_image:
+			return
+
+		# find if website image url exists as public
+		file_doc = frappe.get_all(
+			"File",
+			filters={
+				"file_url": self.website_image
+			},
+			fields=["name", "is_private"],
+			order_by="is_private asc",
+			limit_page_length=1
+		)
+
+		if file_doc:
+			file_doc = file_doc[0]
+
+		if not file_doc:
+			if not auto_set_website_image:
+				frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
+
+			self.website_image = None
+
+		elif file_doc.is_private:
+			if not auto_set_website_image:
+				frappe.msgprint(_("Website Image should be a public file or website URL"))
+
+			self.website_image = None
+
+	def make_thumbnail(self):
+		"""Make a thumbnail of `website_image`"""
+		if frappe.flags.in_import or frappe.flags.in_migrate:
+			return
+
+		import requests.exceptions
+
+		if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
+			self.thumbnail = None
+
+		if self.website_image and not self.thumbnail:
+			file_doc = None
+
+			try:
+				file_doc = frappe.get_doc("File", {
+					"file_url": self.website_image,
+					"attached_to_doctype": "Website Item",
+					"attached_to_name": self.name
+				})
+			except frappe.DoesNotExistError:
+				pass
+				# cleanup
+				frappe.local.message_log.pop()
+
+			except requests.exceptions.HTTPError:
+				frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
+				self.website_image = None
+
+			except requests.exceptions.SSLError:
+				frappe.msgprint(
+					_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
+				self.website_image = None
+
+			# for CSV import
+			if self.website_image and not file_doc:
+				try:
+					file_doc = frappe.get_doc({
+						"doctype": "File",
+						"file_url": self.website_image,
+						"attached_to_doctype": "Website Item",
+						"attached_to_name": self.name
+					}).save()
+
+				except IOError:
+					self.website_image = None
+
+			if file_doc:
+				if not file_doc.thumbnail_url:
+					file_doc.make_thumbnail()
+
+				self.thumbnail = file_doc.thumbnail_url
+
+	def get_context(self, context):
+		context.show_search = True
+		context.search_link = "/search"
+		context.body_class = "product-page"
+
+		context.parents = get_parent_item_groups(self.item_group, from_item=True) # breadcumbs
+		self.attributes = frappe.get_all(
+			"Item Variant Attribute",
+			fields=["attribute", "attribute_value"],
+			filters={"parent": self.item_code}
+		)
+
+		if self.slideshow:
+			context.update(get_slideshow(self))
+
+		self.set_metatags(context)
+		self.set_shopping_cart_data(context)
+
+		settings = context.shopping_cart.cart_settings
+
+		self.get_product_details_section(context)
+
+		if settings.get("enable_reviews"):
+			reviews_data = get_item_reviews(self.name)
+			context.update(reviews_data)
+			context.reviews = context.reviews[:4]
+
+		context.wished = False
+		if frappe.db.exists("Wishlist Item", {"item_code": self.item_code, "parent": frappe.session.user}):
+			context.wished = True
+
+		context.user_is_customer = check_if_user_is_customer()
+
+		context.recommended_items = None
+		if settings and settings.enable_recommendations:
+			context.recommended_items = self.get_recommended_items(settings)
+
+		return context
+
+	def set_selected_attributes(self, variants, context, attribute_values_available):
+		for variant in variants:
+			variant.attributes = frappe.get_all(
+				"Item Variant Attribute",
+				filters={"parent": variant.name},
+				fields=["attribute", "attribute_value as value"])
+
+			# make an attribute-value map for easier access in templates
+			variant.attribute_map = frappe._dict(
+				{attr.attribute : attr.value for attr in variant.attributes}
+			)
+
+			for attr in variant.attributes:
+				values = attribute_values_available.setdefault(attr.attribute, [])
+				if attr.value not in values:
+					values.append(attr.value)
+
+				if variant.name == context.variant.name:
+					context.selected_attributes[attr.attribute] = attr.value
+
+	def set_attribute_values(self, attributes, context, attribute_values_available):
+		for attr in attributes:
+			values = context.attribute_values.setdefault(attr.attribute, [])
+
+			if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
+				for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
+					values.append(val)
+			else:
+				# get list of values defined (for sequence)
+				for attr_value in frappe.db.get_all("Item Attribute Value",
+					fields=["attribute_value"],
+					filters={"parent": attr.attribute}, order_by="idx asc"):
+
+					if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
+						values.append(attr_value.attribute_value)
+
+	def set_metatags(self, context):
+		context.metatags = frappe._dict({})
+
+		safe_description = frappe.utils.to_markdown(self.description)
+
+		context.metatags.url = frappe.utils.get_url() + '/' + context.route
+
+		if context.website_image:
+			if context.website_image.startswith('http'):
+				url = context.website_image
+			else:
+				url = frappe.utils.get_url() + context.website_image
+			context.metatags.image = url
+
+		context.metatags.description = safe_description[:300]
+
+		context.metatags.title = self.web_item_name or self.item_name or self.item_code
+
+		context.metatags['og:type'] = 'product'
+		context.metatags['og:site_name'] = 'ERPNext'
+
+	def set_shopping_cart_data(self, context):
+		from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
+		context.shopping_cart = get_product_info_for_website(self.item_code, skip_quotation_creation=True)
+
+	def copy_specification_from_item_group(self):
+		self.set("website_specifications", [])
+		if self.item_group:
+			for label, desc in frappe.db.get_values("Item Website Specification",
+				{"parent": self.item_group}, ["label", "description"]):
+				row = self.append("website_specifications")
+				row.label = label
+				row.description = desc
+
+	def get_product_details_section(self, context):
+		""" Get section with tabs or website specifications. """
+		context.show_tabs = self.show_tabbed_section
+		if self.show_tabbed_section and (self.tabs or self.website_specifications):
+			context.tabs = self.get_tabs()
+		else:
+			context.website_specifications = self.website_specifications
+
+	def get_tabs(self):
+		tab_values = {}
+		tab_values["tab_1_title"] = "Product Details"
+		tab_values["tab_1_content"] = frappe.render_template(
+			"templates/generators/item/item_specifications.html",
+			{
+				"website_specifications": self.website_specifications,
+				"show_tabs": self.show_tabbed_section
+			})
+
+		for row in self.tabs:
+			tab_values[f"tab_{row.idx + 1}_title"] = _(row.label)
+			tab_values[f"tab_{row.idx + 1}_content"] = row.content
+
+		return tab_values
+
+	def get_recommended_items(self, settings):
+		ri = frappe.qb.DocType("Recommended Items")
+		wi = frappe.qb.DocType("Website Item")
+
+		query = (
+			frappe.qb.from_(ri)
+			.join(wi).on(ri.item_code == wi.item_code)
+			.select(
+				ri.item_code, ri.route,
+				ri.website_item_name,
+				ri.website_item_thumbnail
+			).where(
+				(ri.parent == self.name)
+				& (wi.published == 1)
+			).orderby(ri.idx)
+		)
+		items = query.run(as_dict=True)
+
+		if settings.show_price:
+			is_guest = frappe.session.user == "Guest"
+			# Show Price if logged in.
+			# If not logged in and price is hidden for guest, skip price fetch.
+			if is_guest and settings.hide_price_for_guest:
+				return items
+
+			selling_price_list = _set_price_list(settings, None)
+			for item in items:
+				item.price_info = get_price(
+					item.item_code,
+					selling_price_list,
+					settings.default_customer_group,
+					settings.company
+				)
+
+		return items
+
+def invalidate_cache_for_web_item(doc):
+	"""Invalidate Website Item Group cache and rebuild ItemVariantsCacheManager."""
+	from erpnext.stock.doctype.item.item import invalidate_item_variants_cache_for_website
+
+	invalidate_cache_for(doc, doc.item_group)
+
+	website_item_groups = list(set((doc.get("old_website_item_groups") or [])
+		+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
+
+	for item_group in website_item_groups:
+		invalidate_cache_for(doc, item_group)
+
+	# Update Search Cache
+	update_index_for_item(doc)
+
+	invalidate_item_variants_cache_for_website(doc)
+
+def on_doctype_update():
+	# since route is a Text column, it needs a length for indexing
+	frappe.db.add_index("Website Item", ["route(500)"])
+
+	frappe.db.add_index("Website Item", ["item_group"])
+	frappe.db.add_index("Website Item", ["brand"])
+
+def check_if_user_is_customer(user=None):
+	from frappe.contacts.doctype.contact.contact import get_contact_name
+
+	if not user:
+		user = frappe.session.user
+
+	contact_name = get_contact_name(user)
+	customer = None
+
+	if contact_name:
+		contact = frappe.get_doc('Contact', contact_name)
+		for link in contact.links:
+			if link.link_doctype == "Customer":
+				customer = link.link_name
+				break
+
+	return True if customer else False
+
+@frappe.whitelist()
+def make_website_item(doc, save=True):
+	if not doc:
+		return
+
+	if isinstance(doc, str):
+		doc = json.loads(doc)
+
+	if frappe.db.exists("Website Item", {"item_code": doc.get("item_code")}):
+		message = _("Website Item already exists against {0}").format(frappe.bold(doc.get("item_code")))
+		frappe.throw(message, title=_("Already Published"))
+
+	website_item = frappe.new_doc("Website Item")
+	website_item.web_item_name = doc.get("item_name")
+
+	fields_to_map = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
+		"has_variants", "variant_of", "description"]
+	for field in fields_to_map:
+		website_item.update({field: doc.get(field)})
+
+	if not save:
+		return website_item
+
+	website_item.save()
+
+	# Add to search cache
+	insert_item_to_index(website_item)
+
+	return [website_item.name, website_item.web_item_name]
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/website_item/website_item_list.js b/erpnext/e_commerce/doctype/website_item/website_item_list.js
new file mode 100644
index 0000000..21be942
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item/website_item_list.js
@@ -0,0 +1,20 @@
+frappe.listview_settings['Website Item'] = {
+	add_fields: ["item_name", "web_item_name", "published", "image", "has_variants", "variant_of"],
+	filters: [["published", "=", "1"]],
+
+	get_indicator: function(doc) {
+		if (doc.has_variants && doc.published) {
+			return [__("Template"), "orange", "has_variants,=,Yes|published,=,1"];
+		} else if (doc.has_variants && !doc.published) {
+			return [__("Template"), "grey", "has_variants,=,Yes|published,=,0"];
+		} else if (doc.variant_of  && doc.published) {
+			return [__("Variant"), "blue", "published,=,1|variant_of,=," + doc.variant_of];
+		} else if (doc.variant_of  && !doc.published) {
+			return [__("Variant"), "grey", "published,=,0|variant_of,=," + doc.variant_of];
+		} else if (doc.published) {
+			return [__("Published"), "green", "published,=,1"];
+		} else {
+			return [__("Not Published"), "grey", "published,=,0"];
+		}
+	}
+};
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/__init__.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py
similarity index 100%
rename from erpnext/hotels/doctype/hotel_room_reservation/__init__.py
rename to erpnext/e_commerce/doctype/website_item_tabbed_section/__init__.py
diff --git a/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json
new file mode 100644
index 0000000..6601dd8
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.json
@@ -0,0 +1,37 @@
+{
+ "actions": [],
+ "creation": "2021-03-18 20:32:15.321402",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "label",
+  "content"
+ ],
+ "fields": [
+  {
+   "fieldname": "label",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Label"
+  },
+  {
+   "fieldname": "content",
+   "fieldtype": "HTML Editor",
+   "in_list_view": 1,
+   "label": "Content"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-18 20:35:26.991192",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Website Item Tabbed Section",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py
similarity index 73%
copy from erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
copy to erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py
index bb7f07f..91148b8 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
+++ b/erpnext/e_commerce/doctype/website_item_tabbed_section/website_item_tabbed_section.py
@@ -1,10 +1,10 @@
+# -*- coding: utf-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 TaxExemption80GCertificateDetail(Document):
+class WebsiteItemTabbedSection(Document):
 	pass
diff --git a/erpnext/demo/user/__init__.py b/erpnext/e_commerce/doctype/website_offer/__init__.py
similarity index 100%
rename from erpnext/demo/user/__init__.py
rename to erpnext/e_commerce/doctype/website_offer/__init__.py
diff --git a/erpnext/e_commerce/doctype/website_offer/website_offer.json b/erpnext/e_commerce/doctype/website_offer/website_offer.json
new file mode 100644
index 0000000..627d548
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_offer/website_offer.json
@@ -0,0 +1,43 @@
+{
+ "actions": [],
+ "creation": "2021-04-21 13:37:14.162162",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "offer_title",
+  "offer_subtitle",
+  "offer_details"
+ ],
+ "fields": [
+  {
+   "fieldname": "offer_title",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Offer Title"
+  },
+  {
+   "fieldname": "offer_subtitle",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Offer Subtitle"
+  },
+  {
+   "fieldname": "offer_details",
+   "fieldtype": "Text Editor",
+   "label": "Offer Details"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-04-21 13:56:04.660331",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Website Offer",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/website_offer/website_offer.py b/erpnext/e_commerce/doctype/website_offer/website_offer.py
new file mode 100644
index 0000000..d73c132
--- /dev/null
+++ b/erpnext/e_commerce/doctype/website_offer/website_offer.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-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 WebsiteOffer(Document):
+	pass
+
+@frappe.whitelist(allow_guest=True)
+def get_offer_details(offer_id):
+	return frappe.db.get_value('Website Offer', {'name': offer_id}, ['offer_details'])
diff --git a/erpnext/demo/__init__.py b/erpnext/e_commerce/doctype/wishlist/__init__.py
similarity index 100%
copy from erpnext/demo/__init__.py
copy to erpnext/e_commerce/doctype/wishlist/__init__.py
diff --git a/erpnext/e_commerce/doctype/wishlist/test_wishlist.py b/erpnext/e_commerce/doctype/wishlist/test_wishlist.py
new file mode 100644
index 0000000..504bb65
--- /dev/null
+++ b/erpnext/e_commerce/doctype/wishlist/test_wishlist.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+import unittest
+
+import frappe
+from frappe.core.doctype.user_permission.test_user_permission import create_user
+
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+from erpnext.e_commerce.doctype.wishlist.wishlist import add_to_wishlist, remove_from_wishlist
+from erpnext.stock.doctype.item.test_item import make_item
+
+
+class TestWishlist(unittest.TestCase):
+	def setUp(self):
+		item = make_item("Test Phone Series X")
+		if not frappe.db.exists("Website Item", {"item_code": "Test Phone Series X"}):
+			make_website_item(item, save=True)
+
+		item = make_item("Test Phone Series Y")
+		if not frappe.db.exists("Website Item", {"item_code": "Test Phone Series Y"}):
+			make_website_item(item, save=True)
+
+	def tearDown(self):
+		frappe.get_cached_doc("Website Item", {"item_code": "Test Phone Series X"}).delete()
+		frappe.get_cached_doc("Website Item", {"item_code": "Test Phone Series Y"}).delete()
+		frappe.get_cached_doc("Item", "Test Phone Series X").delete()
+		frappe.get_cached_doc("Item", "Test Phone Series Y").delete()
+
+	def test_add_remove_items_in_wishlist(self):
+		"Check if items are added and removed from user's wishlist."
+		# add first item
+		add_to_wishlist("Test Phone Series X")
+
+		# check if wishlist was created and item was added
+		self.assertTrue(frappe.db.exists("Wishlist", {"user": frappe.session.user}))
+		self.assertTrue(frappe.db.exists("Wishlist Item", {"item_code": "Test Phone Series X", "parent": frappe.session.user}))
+
+		# add second item to wishlist
+		add_to_wishlist("Test Phone Series Y")
+		wishlist_length = frappe.db.get_value(
+			"Wishlist Item",
+			{"parent": frappe.session.user},
+			"count(*)"
+		)
+		self.assertEqual(wishlist_length, 2)
+
+		remove_from_wishlist("Test Phone Series X")
+		remove_from_wishlist("Test Phone Series Y")
+
+		wishlist_length = frappe.db.get_value(
+			"Wishlist Item",
+			{"parent": frappe.session.user},
+			"count(*)"
+		)
+		self.assertIsNone(frappe.db.exists("Wishlist Item", {"parent": frappe.session.user}))
+		self.assertEqual(wishlist_length, 0)
+
+		# tear down
+		frappe.get_doc("Wishlist", {"user": frappe.session.user}).delete()
+
+	def test_add_remove_in_wishlist_multiple_users(self):
+		"Check if items are added and removed from the correct user's wishlist."
+		test_user = create_user("test_reviewer@example.com", "Customer")
+		test_user_1 = create_user("test_reviewer_1@example.com", "Customer")
+
+		# add to wishlist for first user
+		frappe.set_user(test_user.name)
+		add_to_wishlist("Test Phone Series X")
+
+		# add to wishlist for second user
+		frappe.set_user(test_user_1.name)
+		add_to_wishlist("Test Phone Series X")
+
+		# check wishlist and its content for users
+		self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user.name}))
+		self.assertTrue(frappe.db.exists("Wishlist Item",
+			{"item_code": "Test Phone Series X", "parent": test_user.name}))
+
+		self.assertTrue(frappe.db.exists("Wishlist", {"user": test_user_1.name}))
+		self.assertTrue(frappe.db.exists("Wishlist Item",
+			{"item_code": "Test Phone Series X", "parent": test_user_1.name}))
+
+		# remove item for second user
+		remove_from_wishlist("Test Phone Series X")
+
+		# make sure item was removed for second user and not first
+		self.assertFalse(frappe.db.exists("Wishlist Item",
+			{"item_code": "Test Phone Series X", "parent": test_user_1.name}))
+		self.assertTrue(frappe.db.exists("Wishlist Item",
+			{"item_code": "Test Phone Series X", "parent": test_user.name}))
+
+		# remove item for first user
+		frappe.set_user(test_user.name)
+		remove_from_wishlist("Test Phone Series X")
+		self.assertFalse(frappe.db.exists("Wishlist Item",
+			{"item_code": "Test Phone Series X", "parent": test_user.name}))
+
+		# tear down
+		frappe.set_user("Administrator")
+		frappe.get_doc("Wishlist", {"user": test_user.name}).delete()
+		frappe.get_doc("Wishlist", {"user": test_user_1.name}).delete()
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.js b/erpnext/e_commerce/doctype/wishlist/wishlist.js
new file mode 100644
index 0000000..d96e552
--- /dev/null
+++ b/erpnext/e_commerce/doctype/wishlist/wishlist.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('Wishlist', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.json b/erpnext/e_commerce/doctype/wishlist/wishlist.json
new file mode 100644
index 0000000..922924e
--- /dev/null
+++ b/erpnext/e_commerce/doctype/wishlist/wishlist.json
@@ -0,0 +1,65 @@
+{
+ "actions": [],
+ "autoname": "field:user",
+ "creation": "2021-03-10 18:52:28.769126",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "user",
+  "section_break_2",
+  "items"
+ ],
+ "fields": [
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "User",
+   "options": "User",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "label": "Items",
+   "options": "Wishlist Item"
+  }
+ ],
+ "in_create": 1,
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-07-08 13:11:21.693956",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Wishlist",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Website Manager",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/e_commerce/doctype/wishlist/wishlist.py b/erpnext/e_commerce/doctype/wishlist/wishlist.py
new file mode 100644
index 0000000..50e3d3a
--- /dev/null
+++ b/erpnext/e_commerce/doctype/wishlist/wishlist.py
@@ -0,0 +1,68 @@
+# -*- coding: utf-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 Wishlist(Document):
+	pass
+
+@frappe.whitelist()
+def add_to_wishlist(item_code):
+	"""Insert Item into wishlist."""
+
+	if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}):
+		return
+
+	web_item_data = frappe.db.get_value(
+		"Website Item",
+		{"item_code": item_code},
+		["image", "website_warehouse", "name", "web_item_name", "item_name", "item_group", "route"],
+		as_dict=1)
+
+	wished_item_dict = {
+		"item_code": item_code,
+		"item_name": web_item_data.get("item_name"),
+		"item_group": web_item_data.get("item_group"),
+		"website_item": web_item_data.get("name"),
+		"web_item_name": web_item_data.get("web_item_name"),
+		"image": web_item_data.get("image"),
+		"warehouse": web_item_data.get("website_warehouse"),
+		"route": web_item_data.get("route")
+	}
+
+	if not frappe.db.exists("Wishlist", frappe.session.user):
+		# initialise wishlist
+		wishlist = frappe.get_doc({"doctype": "Wishlist"})
+		wishlist.user = frappe.session.user
+		wishlist.append("items", wished_item_dict)
+		wishlist.save(ignore_permissions=True)
+	else:
+		wishlist = frappe.get_doc("Wishlist", frappe.session.user)
+		item = wishlist.append('items', wished_item_dict)
+		item.db_insert()
+
+	if hasattr(frappe.local, "cookie_manager"):
+		frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist.items)))
+
+@frappe.whitelist()
+def remove_from_wishlist(item_code):
+	if frappe.db.exists("Wishlist Item", {"item_code": item_code, "parent": frappe.session.user}):
+		frappe.db.delete(
+			"Wishlist Item",
+			{
+				"item_code": item_code,
+				"parent": frappe.session.user
+			}
+		)
+		frappe.db.commit() # nosemgrep
+
+		wishlist_items = frappe.db.get_values(
+			"Wishlist Item",
+			filters={"parent": frappe.session.user}
+		)
+
+		if hasattr(frappe.local, "cookie_manager"):
+			frappe.local.cookie_manager.set_cookie("wish_count", str(len(wishlist_items)))
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py b/erpnext/e_commerce/doctype/wishlist_item/__init__.py
similarity index 100%
copy from erpnext/hotels/doctype/hotel_room_pricing_item/__init__.py
copy to erpnext/e_commerce/doctype/wishlist_item/__init__.py
diff --git a/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json
new file mode 100644
index 0000000..c0414a7
--- /dev/null
+++ b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.json
@@ -0,0 +1,147 @@
+{
+ "actions": [],
+ "creation": "2021-03-10 19:03:00.662714",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_code",
+  "website_item",
+  "web_item_name",
+  "column_break_3",
+  "item_name",
+  "item_group",
+  "item_details_section",
+  "description",
+  "column_break_7",
+  "route",
+  "image",
+  "image_view",
+  "section_break_8",
+  "warehouse_section",
+  "warehouse"
+ ],
+ "fields": [
+  {
+   "fetch_from": "website_item.item_code",
+   "fetch_if_empty": 1,
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item Code",
+   "options": "Item",
+   "reqd": 1
+  },
+  {
+   "fieldname": "website_item",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Website Item",
+   "options": "Website Item",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fetch_if_empty": 1,
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name",
+   "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "item_details_section",
+   "fieldtype": "Section Break",
+   "label": "Item Details",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "item_code.description",
+   "fetch_if_empty": 1,
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Description",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "item_code.image",
+   "fetch_if_empty": 1,
+   "fieldname": "image",
+   "fieldtype": "Attach",
+   "hidden": 1,
+   "label": "Image"
+  },
+  {
+   "fetch_from": "item_code.image",
+   "fetch_if_empty": 1,
+   "fieldname": "image_view",
+   "fieldtype": "Image",
+   "hidden": 1,
+   "label": "Image View",
+   "options": "image",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "warehouse_section",
+   "fieldtype": "Section Break",
+   "label": "Warehouse"
+  },
+  {
+   "fieldname": "warehouse",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Warehouse",
+   "options": "Warehouse",
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_8",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fetch_from": "item_code.item_group",
+   "fetch_if_empty": 1,
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "label": "Item Group",
+   "options": "Item Group",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "website_item.route",
+   "fetch_if_empty": 1,
+   "fieldname": "route",
+   "fieldtype": "Small Text",
+   "label": "Route",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "website_item.web_item_name",
+   "fetch_if_empty": 1,
+   "fieldname": "web_item_name",
+   "fieldtype": "Data",
+   "label": "Website Item Name",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-08-09 10:30:41.964802",
+ "modified_by": "Administrator",
+ "module": "E-commerce",
+ "name": "Wishlist Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py
similarity index 76%
copy from erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
copy to erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py
index bb7f07f..75ebccb 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.py
+++ b/erpnext/e_commerce/doctype/wishlist_item/wishlist_item.py
@@ -1,10 +1,10 @@
+# -*- coding: utf-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 TaxExemption80GCertificateDetail(Document):
+class WishlistItem(Document):
 	pass
diff --git a/erpnext/shopping_cart/search.py b/erpnext/e_commerce/legacy_search.py
similarity index 95%
rename from erpnext/shopping_cart/search.py
rename to erpnext/e_commerce/legacy_search.py
index 5d2de78..752c33e 100644
--- a/erpnext/shopping_cart/search.py
+++ b/erpnext/e_commerce/legacy_search.py
@@ -6,6 +6,7 @@
 from whoosh.qparser import FieldsPlugin, MultifieldParser, WildcardPlugin
 from whoosh.query import Prefix
 
+# TODO: Make obsolete
 INDEX_NAME = "products"
 
 class ProductSearch(FullTextSearch):
@@ -111,7 +112,7 @@
 		)
 
 def get_all_published_items():
-	return frappe.get_all("Item", filters={"variant_of": "", "show_in_website": 1},pluck="name")
+	return frappe.get_all("Website Item", filters={"variant_of": "", "published": 1}, pluck="item_code")
 
 def update_index_for_path(path):
 	search = ProductSearch(INDEX_NAME)
diff --git a/erpnext/e_commerce/product_data_engine/filters.py b/erpnext/e_commerce/product_data_engine/filters.py
new file mode 100644
index 0000000..c4a3cb9
--- /dev/null
+++ b/erpnext/e_commerce/product_data_engine/filters.py
@@ -0,0 +1,139 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+import frappe
+from frappe.utils import floor
+
+
+class ProductFiltersBuilder:
+	def __init__(self, item_group=None):
+		if not item_group:
+			self.doc = frappe.get_doc("E Commerce Settings")
+		else:
+			self.doc = frappe.get_doc("Item Group", item_group)
+
+		self.item_group = item_group
+
+	def get_field_filters(self):
+		if not self.item_group and not self.doc.enable_field_filters:
+			return
+
+		fields, filter_data = [], []
+		filter_fields = [row.fieldname for row in self.doc.filter_fields] # fields in settings
+
+		# filter valid field filters i.e. those that exist in Item
+		item_meta = frappe.get_meta('Item', cached=True)
+		fields = [item_meta.get_field(field) for field in filter_fields if item_meta.has_field(field)]
+
+		for df in fields:
+			item_filters, item_or_filters = {}, []
+			link_doctype_values = self.get_filtered_link_doctype_records(df)
+
+			if df.fieldtype == "Link":
+				if self.item_group:
+					item_or_filters.extend([
+						["item_group", "=", self.item_group],
+						["Website Item Group", "item_group", "=", self.item_group] # consider website item groups
+					])
+
+				# Get link field values attached to published items
+				item_filters['published_in_website'] = 1
+				item_values = frappe.get_all(
+					"Item",
+					fields=[df.fieldname],
+					filters=item_filters,
+					or_filters=item_or_filters,
+					distinct="True",
+					pluck=df.fieldname
+				)
+
+				values = list(set(item_values) & link_doctype_values) # intersection of both
+			else:
+				# table multiselect
+				values = list(link_doctype_values)
+
+			# Remove None
+			if None in values:
+				values.remove(None)
+
+			if values:
+				filter_data.append([df, values])
+
+		return filter_data
+
+	def get_filtered_link_doctype_records(self, field):
+		"""
+			Get valid link doctype records depending on filters.
+			Apply enable/disable/show_in_website filter.
+			Returns:
+				set: A set containing valid record names
+		"""
+		link_doctype = field.get_link_doctype()
+		meta = frappe.get_meta(link_doctype, cached=True) if link_doctype else None
+		if meta:
+			filters = self.get_link_doctype_filters(meta)
+			link_doctype_values = set(d.name for d in frappe.get_all(link_doctype, filters))
+
+		return link_doctype_values if meta else set()
+
+	def get_link_doctype_filters(self, meta):
+		"Filters for Link Doctype eg. 'show_in_website'."
+		filters = {}
+		if not meta:
+			return filters
+
+		if meta.has_field('enabled'):
+			filters['enabled'] = 1
+		if meta.has_field('disabled'):
+			filters['disabled'] = 0
+		if meta.has_field('show_in_website'):
+			filters['show_in_website'] = 1
+
+		return filters
+
+	def get_attribute_filters(self):
+		if not self.item_group and not self.doc.enable_attribute_filters:
+			return
+
+		attributes = [row.attribute for row in self.doc.filter_attributes]
+
+		if not attributes:
+			return []
+
+		result = frappe.get_all(
+			"Item Variant Attribute",
+			filters={
+				"attribute": ["in", attributes],
+				"attribute_value": ["is", "set"]
+			},
+			fields=["attribute", "attribute_value"],
+			distinct=True
+		)
+
+		attribute_value_map = {}
+		for d in result:
+			attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value)
+
+		out = []
+		for name, values in attribute_value_map.items():
+			out.append(frappe._dict(name=name, item_attribute_values=values))
+		return out
+
+	def get_discount_filters(self, discounts):
+		discount_filters = []
+
+		# [25.89, 60.5] min max
+		min_discount, max_discount = discounts[0], discounts[1]
+		# [25, 60] rounded min max
+		min_range_absolute, max_range_absolute = floor(min_discount), floor(max_discount)
+
+		min_range = int(min_discount - (min_range_absolute % 10)) # 20
+		max_range = int(max_discount - (max_range_absolute % 10)) # 60
+
+		min_range = (min_range + 10) if min_range != min_range_absolute else min_range # 30 (upper limit of 25.89 in range of 10)
+		max_range = (max_range + 10) if max_range != max_range_absolute else max_range # 60
+
+		for discount in range(min_range, (max_range + 1), 10):
+			label = f"{discount}% and below"
+			discount_filters.append([discount, label])
+
+		return discount_filters
diff --git a/erpnext/e_commerce/product_data_engine/query.py b/erpnext/e_commerce/product_data_engine/query.py
new file mode 100644
index 0000000..cfc3c7b
--- /dev/null
+++ b/erpnext/e_commerce/product_data_engine/query.py
@@ -0,0 +1,301 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe.utils import flt
+
+from erpnext.e_commerce.doctype.item_review.item_review import get_customer
+from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
+from erpnext.utilities.product import get_non_stock_item_status
+
+
+class ProductQuery:
+	"""Query engine for product listing
+
+	Attributes:
+		fields (list): Fields to fetch in query
+		conditions (string): Conditions for query building
+		or_conditions (string): Search conditions
+		page_length (Int): Length of page for the query
+		settings (Document): E Commerce Settings DocType
+	"""
+	def __init__(self):
+		self.settings = frappe.get_doc("E Commerce Settings")
+		self.page_length = self.settings.products_per_page or 20
+
+		self.or_filters = []
+		self.filters = [["published", "=", 1]]
+		self.fields = [
+			"web_item_name", "name", "item_name", "item_code", "website_image",
+			"variant_of", "has_variants", "item_group", "image", "web_long_description",
+			"short_description", "route", "website_warehouse", "ranking", "on_backorder"
+		]
+
+	def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
+		"""
+		Args:
+			attributes (dict, optional): Item Attribute filters
+			fields (dict, optional): Field level filters
+			search_term (str, optional): Search term to lookup
+			start (int, optional): Page start
+
+		Returns:
+			dict: Dict containing items, item count & discount range
+		"""
+		# track if discounts included in field filters
+		self.filter_with_discount = bool(fields.get("discount"))
+		result, discount_list, website_item_groups, cart_items, count = [], [], [], [], 0
+
+		website_item_groups = self.get_website_item_group_results(item_group, website_item_groups)
+
+		if fields:
+			self.build_fields_filters(fields)
+		if search_term:
+			self.build_search_filters(search_term)
+		if self.settings.hide_variants:
+			self.filters.append(["variant_of", "is", "not set"])
+
+		# query results
+		if attributes:
+			result, count = self.query_items_with_attributes(attributes, start)
+		else:
+			result, count = self.query_items(start=start)
+
+		result = self.combine_web_item_group_results(item_group, result, website_item_groups)
+
+		# sort combined results by ranking
+		result = sorted(result, key=lambda x: x.get("ranking"), reverse=True)
+
+		if self.settings.enabled:
+			cart_items = self.get_cart_items()
+
+		result, discount_list = self.add_display_details(result, discount_list, cart_items)
+
+		discounts = []
+		if discount_list:
+			discounts = [min(discount_list), max(discount_list)]
+
+		result = self.filter_results_by_discount(fields, result)
+
+		return {
+			"items": result,
+			"items_count": count,
+			"discounts": discounts
+		}
+
+	def query_items(self, start=0):
+		"""Build a query to fetch Website Items based on field filters."""
+		# MySQL does not support offset without limit,
+		# frappe does not accept two parameters for limit
+		# https://dev.mysql.com/doc/refman/8.0/en/select.html#id4651989
+		count_items = frappe.db.get_all(
+			"Website Item",
+			filters=self.filters,
+			or_filters=self.or_filters,
+			limit_page_length=184467440737095516,
+			limit_start=start, # get all items from this offset for total count ahead
+			order_by="ranking desc")
+		count = len(count_items)
+
+		# If discounts included, return all rows.
+		# Slice after filtering rows with discount (See `filter_results_by_discount`).
+		# Slicing before hand will miss discounted items on the 3rd or 4th page.
+		# Discounts are fetched on computing Pricing Rules so we cannot query them directly.
+		page_length = 184467440737095516 if self.filter_with_discount else self.page_length
+
+		items = frappe.db.get_all(
+			"Website Item",
+			fields=self.fields,
+			filters=self.filters,
+			or_filters=self.or_filters,
+			limit_page_length=page_length,
+			limit_start=start,
+			order_by="ranking desc")
+
+		return items, count
+
+	def query_items_with_attributes(self, attributes, start=0):
+		"""Build a query to fetch Website Items based on field & attribute filters."""
+		item_codes = []
+
+		for attribute, values in attributes.items():
+			if not isinstance(values, list):
+				values = [values]
+
+			# get items that have selected attribute & value
+			item_code_list = frappe.db.get_all(
+				"Item",
+				fields=["item_code"],
+				filters=[
+					["published_in_website", "=", 1],
+					["Item Variant Attribute", "attribute", "=", attribute],
+					["Item Variant Attribute", "attribute_value", "in", values]
+				])
+			item_codes.append({x.item_code for x in item_code_list})
+
+		if item_codes:
+			item_codes = list(set.intersection(*item_codes))
+			self.filters.append(["item_code", "in", item_codes])
+
+		items, count = self.query_items(start=start)
+
+		return items, count
+
+	def build_fields_filters(self, filters):
+		"""Build filters for field values
+
+		Args:
+			filters (dict): Filters
+		"""
+		for field, values in filters.items():
+			if not values or field == "discount":
+				continue
+
+			# handle multiselect fields in filter addition
+			meta = frappe.get_meta('Website Item', cached=True)
+			df = meta.get_field(field)
+			if df.fieldtype == 'Table MultiSelect':
+				child_doctype = df.options
+				child_meta = frappe.get_meta(child_doctype, cached=True)
+				fields = child_meta.get("fields")
+				if fields:
+					self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
+			elif isinstance(values, list):
+				# If value is a list use `IN` query
+				self.filters.append([field, "in", values])
+			else:
+				# `=` will be faster than `IN` for most cases
+				self.filters.append([field, "=", values])
+
+	def build_search_filters(self, search_term):
+		"""Query search term in specified fields
+
+		Args:
+			search_term (str): Search candidate
+		"""
+		# Default fields to search from
+		default_fields = {'item_code', 'item_name', 'web_long_description', 'item_group'}
+
+		# Get meta search fields
+		meta = frappe.get_meta("Website Item")
+		meta_fields = set(meta.get_search_fields())
+
+		# Join the meta fields and default fields set
+		search_fields = default_fields.union(meta_fields)
+		if frappe.db.count('Website Item', cache=True) > 50000:
+			search_fields.discard('web_long_description')
+
+		# Build or filters for query
+		search = '%{}%'.format(search_term)
+		for field in search_fields:
+			self.or_filters.append([field, "like", search])
+
+	def get_website_item_group_results(self, item_group, website_item_groups):
+		"""Get Web Items for Item Group Page via Website Item Groups."""
+		if item_group:
+			website_item_groups = frappe.db.get_all(
+				"Website Item",
+				fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
+				filters=[
+					["Website Item Group", "item_group", "=", item_group],
+					["published", "=", 1]
+				]
+			)
+		return website_item_groups
+
+	def add_display_details(self, result, discount_list, cart_items):
+		"""Add price and availability details in result."""
+		for item in result:
+			product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
+
+			if product_info and product_info['price']:
+				# update/mutate item and discount_list objects
+				self.get_price_discount_info(item, product_info['price'], discount_list)
+
+			if self.settings.show_stock_availability:
+				self.get_stock_availability(item)
+
+			item.in_cart = item.item_code in cart_items
+
+			item.wished = False
+			if frappe.db.exists("Wishlist Item", {"item_code": item.item_code, "parent": frappe.session.user}):
+				item.wished = True
+
+		return result, discount_list
+
+	def get_price_discount_info(self, item, price_object, discount_list):
+		"""Modify item object and add price details."""
+		fields = ["formatted_mrp", "formatted_price", "price_list_rate"]
+		for field in fields:
+			item[field] = price_object.get(field)
+
+		if price_object.get('discount_percent'):
+			item.discount_percent = flt(price_object.discount_percent)
+			discount_list.append(price_object.discount_percent)
+
+		if item.formatted_mrp:
+			item.discount = price_object.get('formatted_discount_percent') or \
+				price_object.get('formatted_discount_rate')
+
+	def get_stock_availability(self, item):
+		"""Modify item object and add stock details."""
+		item.in_stock = False
+		warehouse = item.get("website_warehouse")
+		is_stock_item = frappe.get_cached_value("Item", item.item_code, "is_stock_item")
+
+		if item.get("on_backorder"):
+			return
+
+		if not is_stock_item:
+			if warehouse:
+				# product bundle case
+				item.in_stock = get_non_stock_item_status(item.item_code, "website_warehouse")
+			else:
+				item.in_stock = True
+		elif warehouse:
+			# stock item and has warehouse
+			actual_qty = frappe.db.get_value(
+				"Bin",
+				{"item_code": item.item_code,"warehouse": item.get("website_warehouse")},
+				"actual_qty")
+			item.in_stock = bool(flt(actual_qty))
+
+	def get_cart_items(self):
+		customer = get_customer(silent=True)
+		if customer:
+			quotation = frappe.get_all("Quotation", fields=["name"], filters=
+				{"party_name": customer, "contact_email": frappe.session.user, "order_type": "Shopping Cart", "docstatus": 0},
+				order_by="modified desc", limit_page_length=1)
+			if quotation:
+				items = frappe.get_all(
+					"Quotation Item",
+					fields=["item_code"],
+					filters={
+						"parent": quotation[0].get("name")
+					})
+				items = [row.item_code for row in items]
+				return items
+
+		return []
+
+	def combine_web_item_group_results(self, item_group, result, website_item_groups):
+		"""Combine results with context of website item groups into item results."""
+		if item_group and website_item_groups:
+			items_list = {row.name for row in result}
+			for row in website_item_groups:
+				if row.wig_parent not in items_list:
+					result.append(row)
+
+		return result
+
+	def filter_results_by_discount(self, fields, result):
+		if fields and fields.get("discount"):
+			discount_percent = frappe.utils.flt(fields["discount"][0])
+			result = [row for row in result if row.get("discount_percent") and row.discount_percent <= discount_percent]
+
+		if self.filter_with_discount:
+			# no limit was added to results while querying
+			# slice results manually
+			result[:self.page_length]
+
+		return result
diff --git a/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
new file mode 100644
index 0000000..f0f7918
--- /dev/null
+++ b/erpnext/e_commerce/product_data_engine/test_item_group_product_data_engine.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import unittest
+
+import frappe
+
+from erpnext.e_commerce.api import get_product_filter_data
+from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item
+
+test_dependencies = ["Item", "Item Group"]
+
+class TestItemGroupProductDataEngine(unittest.TestCase):
+	"Test Products & Sub-Category Querying for Product Listing on Item Group Page."
+
+	@classmethod
+	def setUpClass(cls):
+		item_codes = [
+			("Test Mobile A", "_Test Item Group B"),
+			("Test Mobile B", "_Test Item Group B"),
+			("Test Mobile C", "_Test Item Group B - 1"),
+			("Test Mobile D", "_Test Item Group B - 1"),
+			("Test Mobile E", "_Test Item Group B - 2")
+		]
+		for item in item_codes:
+			item_code = item[0]
+			item_args = {"item_group": item[1]}
+			if not frappe.db.exists("Website Item", {"item_code": item_code}):
+				create_regular_web_item(item_code, item_args=item_args)
+
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.rollback()
+
+	def test_product_listing_in_item_group(self):
+		"Test if only products belonging to the Item Group are fetched."
+		result = get_product_filter_data(query_args={
+			"field_filters": {},
+			"attribute_filters": {},
+			"start": 0,
+			"item_group": "_Test Item Group B"
+		})
+
+		items = result.get("items")
+		item_codes = [item.get("item_code") for item in items]
+
+		self.assertEqual(len(items), 2)
+		self.assertIn("Test Mobile A", item_codes)
+		self.assertNotIn("Test Mobile C", item_codes)
+
+	def test_products_in_multiple_item_groups(self):
+		"""Test if product is visible on multiple item group pages barring its own."""
+		website_item = frappe.get_doc("Website Item", {"item_code": "Test Mobile E"})
+
+		# show item belonging to '_Test Item Group B - 2' in '_Test Item Group B - 1' as well
+		website_item.append("website_item_groups", {
+			"item_group": "_Test Item Group B - 1"
+		})
+		website_item.save()
+
+		result = get_product_filter_data(query_args={
+			"field_filters": {},
+			"attribute_filters": {},
+			"start": 0,
+			"item_group": "_Test Item Group B - 1"
+		})
+
+		items = result.get("items")
+		item_codes = [item.get("item_code") for item in items]
+
+		self.assertEqual(len(items), 3)
+		self.assertIn("Test Mobile E", item_codes) # visible in other item groups
+		self.assertIn("Test Mobile C", item_codes)
+		self.assertIn("Test Mobile D", item_codes)
+
+		result = get_product_filter_data(query_args={
+			"field_filters": {},
+			"attribute_filters": {},
+			"start": 0,
+			"item_group": "_Test Item Group B - 2"
+		})
+
+		items = result.get("items")
+
+		self.assertEqual(len(items), 1)
+		self.assertEqual(items[0].get("item_code"), "Test Mobile E") # visible in own item group
+
+	def test_item_group_with_sub_groups(self):
+		"Test Valid Sub Item Groups in Item Group Page."
+		frappe.db.set_value("Item Group", "_Test Item Group B - 1", "show_in_website", 1)
+		frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 0)
+
+		result = get_product_filter_data(query_args={
+			"field_filters": {},
+			"attribute_filters": {},
+			"start": 0,
+			"item_group": "_Test Item Group B"
+		})
+
+		self.assertTrue(bool(result.get("sub_categories")))
+
+		child_groups = [d.name for d in result.get("sub_categories")]
+		# check if child group is fetched if shown in website
+		self.assertIn("_Test Item Group B - 1", child_groups)
+
+		frappe.db.set_value("Item Group", "_Test Item Group B - 2", "show_in_website", 1)
+		result = get_product_filter_data(query_args={
+			"field_filters": {},
+			"attribute_filters": {},
+			"start": 0,
+			"item_group": "_Test Item Group B"
+		})
+		child_groups = [d.name for d in result.get("sub_categories")]
+
+		# check if child group is fetched if shown in website
+		self.assertIn("_Test Item Group B - 1", child_groups)
+		self.assertIn("_Test Item Group B - 2", child_groups)
\ No newline at end of file
diff --git a/erpnext/e_commerce/product_data_engine/test_product_data_engine.py b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py
new file mode 100644
index 0000000..9ec336d
--- /dev/null
+++ b/erpnext/e_commerce/product_data_engine/test_product_data_engine.py
@@ -0,0 +1,350 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import unittest
+
+import frappe
+
+from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
+	setup_e_commerce_settings,
+)
+from erpnext.e_commerce.doctype.website_item.test_website_item import create_regular_web_item
+from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
+from erpnext.e_commerce.product_data_engine.query import ProductQuery
+
+test_dependencies = ["Item", "Item Group"]
+
+class TestProductDataEngine(unittest.TestCase):
+	"Test Products Querying and Filters for Product Listing."
+
+	@classmethod
+	def setUpClass(cls):
+		item_codes = [
+			("Test 11I Laptop", "Products"), # rank 1
+			("Test 12I Laptop", "Products"), # rank 2
+			("Test 13I Laptop", "Products"), # rank 3
+			("Test 14I Laptop", "Raw Material"), # rank 4
+			("Test 15I Laptop", "Raw Material"), # rank 5
+			("Test 16I Laptop", "Raw Material"), # rank 6
+			("Test 17I Laptop", "Products") # rank 7
+		]
+		for index, item in enumerate(item_codes, start=1):
+			item_code = item[0]
+			item_args = {"item_group": item[1]}
+			web_args = {"ranking": index}
+			if not frappe.db.exists("Website Item", {"item_code": item_code}):
+				create_regular_web_item(item_code, item_args=item_args, web_args=web_args)
+
+		setup_e_commerce_settings({
+			"products_per_page": 4,
+			"enable_field_filters": 1,
+			"filter_fields": [{"fieldname": "item_group"}],
+			"enable_attribute_filters": 1,
+			"filter_attributes": [{"attribute": "Test Size"}],
+			"company": "_Test Company",
+			"enabled": 1,
+			"default_customer_group": "_Test Customer Group",
+			"price_list": "_Test Price List India"
+		})
+		frappe.local.shopping_cart_settings = None
+
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.rollback()
+
+	def test_product_list_ordering_and_paging(self):
+		"Test if website items appear by ranking on different pages."
+		engine = ProductQuery()
+		result = engine.query(
+			attributes={},
+			fields={},
+			search_term=None,
+			start=0,
+			item_group=None
+		)
+		items = result.get("items")
+
+		self.assertIsNotNone(items)
+		self.assertEqual(len(items), 4)
+		self.assertGreater(result.get("items_count"), 4)
+
+		# check if items appear as per ranking set in setUpClass
+		self.assertEqual(items[0].get("item_code"), "Test 17I Laptop")
+		self.assertEqual(items[1].get("item_code"), "Test 16I Laptop")
+		self.assertEqual(items[2].get("item_code"), "Test 15I Laptop")
+		self.assertEqual(items[3].get("item_code"), "Test 14I Laptop")
+
+		# check next page
+		result = engine.query(
+			attributes={},
+			fields={},
+			search_term=None,
+			start=4,
+			item_group=None
+		)
+		items = result.get("items")
+
+		# check if items appear as per ranking set in setUpClass on next page
+		self.assertEqual(items[0].get("item_code"), "Test 13I Laptop")
+		self.assertEqual(items[1].get("item_code"), "Test 12I Laptop")
+		self.assertEqual(items[2].get("item_code"), "Test 11I Laptop")
+
+	def test_change_product_ranking(self):
+		"Test if item on second page appear on first if ranking is changed."
+		item_code = "Test 12I Laptop"
+		old_ranking = frappe.db.get_value("Website Item", {"item_code": item_code}, "ranking")
+
+		# low rank, appears on second page
+		self.assertEqual(old_ranking, 2)
+
+		# set ranking as highest rank
+		frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", 10)
+
+		engine = ProductQuery()
+		result = engine.query(
+			attributes={},
+			fields={},
+			search_term=None,
+			start=0,
+			item_group=None
+		)
+		items = result.get("items")
+
+		# check if item is the first item on the first page
+		self.assertEqual(items[0].get("item_code"), item_code)
+		self.assertEqual(items[1].get("item_code"), "Test 17I Laptop")
+
+		# tear down
+		frappe.db.set_value("Website Item", {"item_code": item_code}, "ranking", old_ranking)
+
+	def test_product_list_field_filter_builder(self):
+		"Test if field filters are fetched correctly."
+		frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 0)
+
+		filter_engine = ProductFiltersBuilder()
+		field_filters = filter_engine.get_field_filters()
+
+		# Web Items belonging to 'Products' and 'Raw Material' are available
+		# but only 'Products' has 'show_in_website' enabled
+		item_group_filters = field_filters[0]
+		docfield = item_group_filters[0]
+		valid_item_groups = item_group_filters[1]
+
+		self.assertEqual(docfield.options, "Item Group")
+		self.assertIn("Products", valid_item_groups)
+		self.assertNotIn("Raw Material", valid_item_groups)
+
+		frappe.db.set_value("Item Group", "Raw Material", "show_in_website", 1)
+		field_filters = filter_engine.get_field_filters()
+
+		#'Products' and 'Raw Materials' both have 'show_in_website' enabled
+		item_group_filters = field_filters[0]
+		docfield = item_group_filters[0]
+		valid_item_groups = item_group_filters[1]
+
+		self.assertEqual(docfield.options, "Item Group")
+		self.assertIn("Products", valid_item_groups)
+		self.assertIn("Raw Material", valid_item_groups)
+
+	def test_product_list_with_field_filter(self):
+		"Test if field filters are applied correctly."
+		field_filters = {"item_group": "Raw Material"}
+
+		engine = ProductQuery()
+		result = engine.query(
+			attributes={},
+			fields=field_filters,
+			search_term=None,
+			start=0,
+			item_group=None
+		)
+		items = result.get("items")
+
+		# check if only 'Raw Material' are fetched in the right order
+		self.assertEqual(len(items), 3)
+		self.assertEqual(items[0].get("item_code"), "Test 16I Laptop")
+		self.assertEqual(items[1].get("item_code"), "Test 15I Laptop")
+
+	# def test_product_list_with_field_filter_table_multiselect(self):
+	# 	TODO
+	# 	pass
+
+	def test_product_list_attribute_filter_builder(self):
+		"Test if attribute filters are fetched correctly."
+		create_variant_web_item()
+
+		filter_engine = ProductFiltersBuilder()
+		attribute_filter = filter_engine.get_attribute_filters()[0]
+		attribute_values = attribute_filter.item_attribute_values
+
+		self.assertEqual(attribute_filter.name, "Test Size")
+		self.assertGreater(len(attribute_values), 0)
+		self.assertIn("Large", attribute_values)
+
+	def test_product_list_with_attribute_filter(self):
+		"Test if attribute filters are applied correctly."
+		create_variant_web_item()
+
+		attribute_filters = {"Test Size": ["Large"]}
+		engine = ProductQuery()
+		result = engine.query(
+			attributes=attribute_filters,
+			fields={},
+			search_term=None,
+			start=0,
+			item_group=None
+		)
+		items = result.get("items")
+
+		# check if only items with Test Size 'Large' are fetched
+		self.assertEqual(len(items), 1)
+		self.assertEqual(items[0].get("item_code"), "Test Web Item-L")
+
+	def test_product_list_discount_filter_builder(self):
+		"Test if discount filters are fetched correctly."
+		from erpnext.e_commerce.doctype.website_item.test_website_item import (
+			make_web_item_price,
+			make_web_pricing_rule,
+		)
+
+		item_code = "Test 12I Laptop"
+		make_web_item_price(item_code=item_code)
+		make_web_pricing_rule(
+			title=f"Test Pricing Rule for {item_code}",
+			item_code=item_code,
+			selling=1
+		)
+
+		setup_e_commerce_settings({"show_price": 1})
+		frappe.local.shopping_cart_settings = None
+
+
+		engine = ProductQuery()
+		result = engine.query(
+			attributes={},
+			fields={},
+			search_term=None,
+			start=4,
+			item_group=None
+		)
+		self.assertTrue(bool(result.get("discounts")))
+
+		filter_engine = ProductFiltersBuilder()
+		discount_filters = filter_engine.get_discount_filters(result["discounts"])
+
+		self.assertEqual(len(discount_filters[0]), 2)
+		self.assertEqual(discount_filters[0][0], 10)
+		self.assertEqual(discount_filters[0][1], "10% and below")
+
+	def test_product_list_with_discount_filters(self):
+		"Test if discount filters are applied correctly."
+		from erpnext.e_commerce.doctype.website_item.test_website_item import (
+			make_web_item_price,
+			make_web_pricing_rule,
+		)
+
+		field_filters = {"discount": [10]}
+
+		make_web_item_price(item_code="Test 12I Laptop")
+		make_web_pricing_rule(
+			title="Test Pricing Rule for Test 12I Laptop", # 10% discount
+			item_code="Test 12I Laptop",
+			selling=1
+		)
+		make_web_item_price(item_code="Test 13I Laptop")
+		make_web_pricing_rule(
+			title="Test Pricing Rule for Test 13I Laptop", # 15% discount
+			item_code="Test 13I Laptop",
+			discount_percentage=15,
+			selling=1
+		)
+
+		setup_e_commerce_settings({"show_price": 1})
+		frappe.local.shopping_cart_settings = None
+
+		engine = ProductQuery()
+		result = engine.query(
+			attributes={},
+			fields=field_filters,
+			search_term=None,
+			start=0,
+			item_group=None
+		)
+		items = result.get("items")
+
+		# check if only product with 10% and below discount are fetched
+		self.assertEqual(len(items), 1)
+		self.assertEqual(items[0].get("item_code"), "Test 12I Laptop")
+
+	def test_product_list_with_api(self):
+		"Test products listing using API."
+		from erpnext.e_commerce.api import get_product_filter_data
+
+		create_variant_web_item()
+
+		result = get_product_filter_data(query_args={
+			"field_filters": {
+				"item_group": "Products"
+			},
+			"attribute_filters": {
+				"Test Size": ["Large"]
+			},
+			"start": 0
+		})
+
+		items = result.get("items")
+
+		self.assertEqual(len(items), 1)
+		self.assertEqual(items[0].get("item_code"), "Test Web Item-L")
+
+	def test_product_list_with_variants(self):
+		"Test if variants are hideen on hiding variants in settings."
+		create_variant_web_item()
+
+		setup_e_commerce_settings({
+			"enable_attribute_filters": 0,
+			"hide_variants": 1
+		})
+		frappe.local.shopping_cart_settings = None
+
+		attribute_filters = {"Test Size": ["Large"]}
+		engine = ProductQuery()
+		result = engine.query(
+			attributes=attribute_filters,
+			fields={},
+			search_term=None,
+			start=0,
+			item_group=None
+		)
+		items = result.get("items")
+
+		# check if any variants are fetched even though published variant exists
+		self.assertEqual(len(items), 0)
+
+		# tear down
+		setup_e_commerce_settings({
+			"enable_attribute_filters": 1,
+			"hide_variants": 0
+		})
+
+def create_variant_web_item():
+	"Create Variant and Template Website Items."
+	from erpnext.controllers.item_variant import create_variant
+	from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+	from erpnext.stock.doctype.item.test_item import make_item
+
+	make_item("Test Web Item", {
+		"has_variant": 1,
+		"variant_based_on": "Item Attribute",
+		"attributes": [
+			{
+				"attribute": "Test Size"
+			}
+		]
+	})
+	if not frappe.db.exists("Item", "Test Web Item-L"):
+		variant = create_variant("Test Web Item", {"Test Size": "Large"})
+		variant.save()
+
+	if not frappe.db.exists("Website Item", {"variant_of": "Test Web Item"}):
+		make_website_item(variant, save=True)
\ No newline at end of file
diff --git a/erpnext/e_commerce/product_ui/grid.js b/erpnext/e_commerce/product_ui/grid.js
new file mode 100644
index 0000000..9eb1d45
--- /dev/null
+++ b/erpnext/e_commerce/product_ui/grid.js
@@ -0,0 +1,201 @@
+erpnext.ProductGrid = class {
+	/* Options:
+		- items: Items
+		- settings: E Commerce Settings
+		- products_section: Products Wrapper
+		- preference: If preference is not grid view, render but hide
+	*/
+	constructor(options) {
+		Object.assign(this, options);
+
+		if (this.preference !== "Grid View") {
+			this.products_section.addClass("hidden");
+		}
+
+		this.products_section.empty();
+		this.make();
+	}
+
+	make() {
+		let me = this;
+		let html = ``;
+
+		this.items.forEach(item => {
+			let title = item.web_item_name || item.item_name || item.item_code || "";
+			title =  title.length > 90 ? title.substr(0, 90) + "..." : title;
+
+			html += `<div class="col-sm-4 item-card"><div class="card text-left">`;
+			html += me.get_image_html(item, title);
+			html += me.get_card_body_html(item, title, me.settings);
+			html += `</div></div>`;
+		});
+
+		let $product_wrapper = this.products_section;
+		$product_wrapper.append(html);
+	}
+
+	get_image_html(item, title) {
+		let image = item.website_image || item.image;
+
+		if (image) {
+			return `
+				<div class="card-img-container">
+					<a href="/${ item.route || '#' }" style="text-decoration: none;">
+						<img class="card-img" src="${ image }" alt="${ title }">
+					</a>
+				</div>
+			`;
+		} else {
+			return `
+				<div class="card-img-container">
+					<a href="/${ item.route || '#' }" style="text-decoration: none;">
+						<div class="card-img-top no-image">
+							${ frappe.get_abbr(title) }
+						</div>
+					</a>
+				</div>
+			`;
+		}
+	}
+
+	get_card_body_html(item, title, settings) {
+		let body_html = `
+			<div class="card-body text-left card-body-flex" style="width:100%">
+				<div style="margin-top: 1rem; display: flex;">
+		`;
+		body_html += this.get_title(item, title);
+
+		// get floating elements
+		if (!item.has_variants) {
+			if (settings.enable_wishlist) {
+				body_html += this.get_wishlist_icon(item);
+			}
+			if (settings.enabled) {
+				body_html += this.get_cart_indicator(item);
+			}
+
+		}
+
+		body_html += `</div>`;
+		body_html += `<div class="product-category">${ item.item_group || '' }</div>`;
+
+		if (item.formatted_price) {
+			body_html += this.get_price_html(item);
+		}
+
+		body_html += this.get_stock_availability(item, settings);
+		body_html += this.get_primary_button(item, settings);
+		body_html += `</div>`; // close div on line 49
+
+		return body_html;
+	}
+
+	get_title(item, title) {
+		let title_html = `
+			<a href="/${ item.route || '#' }">
+				<div class="product-title">
+					${ title || '' }
+				</div>
+			</a>
+		`;
+		return title_html;
+	}
+
+	get_wishlist_icon(item) {
+		let icon_class = item.wished ? "wished" : "not-wished";
+		return `
+			<div class="like-action ${ item.wished ? "like-action-wished" : ''}"
+				data-item-code="${ item.item_code }">
+				<svg class="icon sm">
+					<use class="${ icon_class } wish-icon" href="#icon-heart"></use>
+				</svg>
+			</div>
+		`;
+	}
+
+	get_cart_indicator(item) {
+		return `
+			<div class="cart-indicator ${item.in_cart ? '' : 'hidden'}" data-item-code="${ item.item_code }">
+				1
+			</div>
+		`;
+	}
+
+	get_price_html(item) {
+		let price_html = `
+			<div class="product-price">
+				${ item.formatted_price || '' }
+		`;
+
+		if (item.formatted_mrp) {
+			price_html += `
+				<small class="striked-price">
+					<s>${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" }</s>
+				</small>
+				<small class="ml-1 product-info-green">
+					${ item.discount } OFF
+				</small>
+			`;
+		}
+		price_html += `</div>`;
+		return price_html;
+	}
+
+	get_stock_availability(item, settings) {
+		if (settings.show_stock_availability && !item.has_variants) {
+			if (item.on_backorder) {
+				return `
+					<span class="out-of-stock mb-2 mt-1" style="color: var(--primary-color)">
+						${ __("Available on backorder") }
+					</span>
+				`;
+			} else if (!item.in_stock) {
+				return `
+					<span class="out-of-stock mb-2 mt-1">
+						${ __("Out of stock") }
+					</span>
+				`;
+			}
+		}
+
+		return ``;
+	}
+
+	get_primary_button(item, settings) {
+		if (item.has_variants) {
+			return `
+				<a href="/${ item.route || '#' }">
+					<div class="btn btn-sm btn-explore-variants w-100 mt-4">
+						${ __('Explore') }
+					</div>
+				</a>
+			`;
+		} else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) {
+			return `
+				<div id="${ item.name }" class="btn
+					btn-sm btn-primary btn-add-to-cart-list
+					w-100 mt-2 ${ item.in_cart ? 'hidden' : '' }"
+					data-item-code="${ item.item_code }">
+					<span class="mr-2">
+						<svg class="icon icon-md">
+							<use href="#icon-assets"></use>
+						</svg>
+					</span>
+					${ settings.enable_checkout ? __('Add to Cart') :  __('Add to Quote') }
+				</div>
+
+				<a href="/cart">
+					<div id="${ item.name }" class="btn
+						btn-sm btn-primary btn-add-to-cart-list
+						w-100 mt-4 go-to-cart-grid
+						${ item.in_cart ? '' : 'hidden' }"
+						data-item-code="${ item.item_code }">
+						${ settings.enable_checkout ? __('Go to Cart') :  __('Go to Quote') }
+					</div>
+				</a>
+			`;
+		} else {
+			return ``;
+		}
+	}
+};
\ No newline at end of file
diff --git a/erpnext/e_commerce/product_ui/list.js b/erpnext/e_commerce/product_ui/list.js
new file mode 100644
index 0000000..691cd4d
--- /dev/null
+++ b/erpnext/e_commerce/product_ui/list.js
@@ -0,0 +1,204 @@
+erpnext.ProductList = class {
+	/* Options:
+		- items: Items
+		- settings: E Commerce Settings
+		- products_section: Products Wrapper
+		- preference: If preference is not list view, render but hide
+	*/
+	constructor(options) {
+		Object.assign(this, options);
+
+		if (this.preference !== "List View") {
+			this.products_section.addClass("hidden");
+		}
+
+		this.products_section.empty();
+		this.make();
+	}
+
+	make() {
+		let me = this;
+		let html = `<br><br>`;
+
+		this.items.forEach(item => {
+			let title = item.web_item_name || item.item_name || item.item_code || "";
+			title =  title.length > 200 ? title.substr(0, 200) + "..." : title;
+
+			html += `<div class='row list-row w-100 mb-4'>`;
+			html += me.get_image_html(item, title, me.settings);
+			html += me.get_row_body_html(item, title, me.settings);
+			html += `</div>`;
+		});
+
+		let $product_wrapper = this.products_section;
+		$product_wrapper.append(html);
+	}
+
+	get_image_html(item, title, settings) {
+		let image = item.website_image || item.image;
+		let wishlist_enabled = !item.has_variants && settings.enable_wishlist;
+		let image_html = ``;
+
+		if (image) {
+			image_html += `
+				<div class="col-2 border text-center rounded list-image">
+					<a class="product-link product-list-link" href="/${ item.route || '#' }">
+						<img itemprop="image" class="website-image h-100 w-100" alt="${ title }"
+							src="${ image }">
+					</a>
+					${ wishlist_enabled ? this.get_wishlist_icon(item): '' }
+				</div>
+			`;
+		} else {
+			image_html += `
+				<div class="col-2 border text-center rounded list-image">
+					<a class="product-link product-list-link" href="/${ item.route || '#' }"
+						style="text-decoration: none">
+						<div class="card-img-top no-image-list">
+							${ frappe.get_abbr(title) }
+						</div>
+					</a>
+					${ wishlist_enabled ? this.get_wishlist_icon(item): '' }
+				</div>
+			`;
+		}
+
+		return image_html;
+	}
+
+	get_row_body_html(item, title, settings) {
+		let body_html = `<div class='col-10 text-left'>`;
+		body_html += this.get_title_html(item, title, settings);
+		body_html += this.get_item_details(item, settings);
+		body_html += `</div>`;
+		return body_html;
+	}
+
+	get_title_html(item, title, settings) {
+		let title_html = `<div style="display: flex; margin-left: -15px;">`;
+		title_html += `
+			<div class="col-8" style="margin-right: -15px;">
+				<a class="" href="/${ item.route || '#' }"
+					style="color: var(--gray-800); font-weight: 500;">
+					${ title }
+				</a>
+			</div>
+		`;
+
+		if (settings.enabled) {
+			title_html += `<div class="col-4 cart-action-container ${item.in_cart ? 'd-flex' : ''}">`;
+			title_html += this.get_primary_button(item, settings);
+			title_html += `</div>`;
+		}
+		title_html += `</div>`;
+
+		return title_html;
+	}
+
+	get_item_details(item, settings) {
+		let details = `
+			<p class="product-code">
+				${ item.item_group } | Item Code : ${ item.item_code }
+			</p>
+			<div class="mt-2" style="color: var(--gray-600) !important; font-size: 13px;">
+				${ item.short_description || '' }
+			</div>
+			<div class="product-price">
+				${ item.formatted_price || '' }
+		`;
+
+		if (item.formatted_mrp) {
+			details += `
+				<small class="striked-price">
+					<s>${ item.formatted_mrp ? item.formatted_mrp.replace(/ +/g, "") : "" }</s>
+				</small>
+				<small class="ml-1 product-info-green">
+					${ item.discount } OFF
+				</small>
+			`;
+		}
+
+		details += this.get_stock_availability(item, settings);
+		details += `</div>`;
+
+		return details;
+	}
+
+	get_stock_availability(item, settings) {
+		if (settings.show_stock_availability && !item.has_variants) {
+			if (item.on_backorder) {
+				return `
+					<br>
+					<span class="out-of-stock mt-2" style="color: var(--primary-color)">
+						${ __("Available on backorder") }
+					</span>
+				`;
+			} else if (!item.in_stock) {
+				return `
+					<br>
+					<span class="out-of-stock mt-2">${ __("Out of stock") }</span>
+				`;
+			}
+		}
+		return ``;
+	}
+
+	get_wishlist_icon(item) {
+		let icon_class = item.wished ? "wished" : "not-wished";
+
+		return `
+			<div class="like-action-list ${ item.wished ? "like-action-wished" : ''}"
+				data-item-code="${ item.item_code }">
+				<svg class="icon sm">
+					<use class="${ icon_class } wish-icon" href="#icon-heart"></use>
+				</svg>
+			</div>
+		`;
+	}
+
+	get_primary_button(item, settings) {
+		if (item.has_variants) {
+			return `
+				<a href="/${ item.route || '#' }">
+					<div class="btn btn-sm btn-explore-variants btn mb-0 mt-0">
+						${ __('Explore') }
+					</div>
+				</a>
+			`;
+		} else if (settings.enabled && (settings.allow_items_not_in_stock || item.in_stock)) {
+			return `
+				<div id="${ item.name }" class="btn
+					btn-sm btn-primary btn-add-to-cart-list mb-0
+					${ item.in_cart ? 'hidden' : '' }"
+					data-item-code="${ item.item_code }"
+					style="margin-top: 0px !important; max-height: 30px; float: right;
+						padding: 0.25rem 1rem; min-width: 135px;">
+					<span class="mr-2">
+						<svg class="icon icon-md">
+							<use href="#icon-assets"></use>
+						</svg>
+					</span>
+					${ settings.enable_checkout ? __('Add to Cart') :  __('Add to Quote') }
+				</div>
+
+				<div class="cart-indicator list-indicator ${item.in_cart ? '' : 'hidden'}">
+					1
+				</div>
+
+				<a href="/cart">
+					<div id="${ item.name }" class="btn
+						btn-sm btn-primary btn-add-to-cart-list
+						ml-4 go-to-cart mb-0 mt-0
+						${ item.in_cart ? '' : 'hidden' }"
+						data-item-code="${ item.item_code }"
+						style="padding: 0.25rem 1rem; min-width: 135px;">
+						${ settings.enable_checkout ? __('Go to Cart') :  __('Go to Quote') }
+					</div>
+				</a>
+			`;
+		} else {
+			return ``;
+		}
+	}
+
+};
\ No newline at end of file
diff --git a/erpnext/e_commerce/product_ui/search.js b/erpnext/e_commerce/product_ui/search.js
new file mode 100644
index 0000000..6192245
--- /dev/null
+++ b/erpnext/e_commerce/product_ui/search.js
@@ -0,0 +1,244 @@
+erpnext.ProductSearch = class {
+	constructor(opts) {
+		/* Options: search_box_id (for custom search box) */
+		$.extend(this, opts);
+		this.MAX_RECENT_SEARCHES = 4;
+		this.search_box_id = this.search_box_id || "#search-box";
+		this.searchBox = $(this.search_box_id);
+
+		this.setupSearchDropDown();
+		this.bindSearchAction();
+	}
+
+	setupSearchDropDown() {
+		this.search_area = $("#dropdownMenuSearch");
+		this.setupSearchResultContainer();
+		this.populateRecentSearches();
+	}
+
+	bindSearchAction() {
+		let me = this;
+
+		// Show Search dropdown
+		this.searchBox.on("focus", () => {
+			this.search_dropdown.removeClass("hidden");
+		});
+
+		// If click occurs outside search input/results, hide results.
+		// Click can happen anywhere on the page
+		$("body").on("click", (e) => {
+			let searchEvent = $(e.target).closest(this.search_box_id).length;
+			let resultsEvent = $(e.target).closest('#search-results-container').length;
+			let isResultHidden = this.search_dropdown.hasClass("hidden");
+
+			if (!searchEvent && !resultsEvent && !isResultHidden) {
+				this.search_dropdown.addClass("hidden");
+			}
+		});
+
+		// Process search input
+		this.searchBox.on("input", (e) => {
+			let query = e.target.value;
+
+			if (query.length == 0) {
+				me.populateResults(null);
+				me.populateCategoriesList(null);
+			}
+
+			if (query.length < 3 || !query.length) return;
+
+			frappe.call({
+				method: "erpnext.templates.pages.product_search.search",
+				args: {
+					query: query
+				},
+				callback: (data) => {
+					let product_results = null, category_results = null;
+
+					// Populate product results
+					product_results = data.message ? data.message.product_results : null;
+					me.populateResults(product_results);
+
+					// Populate categories
+					if (me.category_container) {
+						category_results = data.message ? data.message.category_results : null;
+						me.populateCategoriesList(category_results);
+					}
+
+					// Populate recent search chips only on successful queries
+					if (!$.isEmptyObject(product_results) || !$.isEmptyObject(category_results)) {
+						me.setRecentSearches(query);
+					}
+				}
+			});
+
+			this.search_dropdown.removeClass("hidden");
+		});
+	}
+
+	setupSearchResultContainer() {
+		this.search_dropdown = this.search_area.append(`
+			<div class="overflow-hidden shadow dropdown-menu w-100 hidden"
+				id="search-results-container"
+				aria-labelledby="dropdownMenuSearch"
+				style="display: flex; flex-direction: column;">
+			</div>
+		`).find("#search-results-container");
+
+		this.setupCategoryContainer();
+		this.setupProductsContainer();
+		this.setupRecentsContainer();
+	}
+
+	setupProductsContainer() {
+		this.products_container = this.search_dropdown.append(`
+			<div id="product-results mt-2">
+				<div id="product-scroll" style="overflow: scroll; max-height: 300px">
+				</div>
+			</div>
+		`).find("#product-scroll");
+	}
+
+	setupCategoryContainer() {
+		this.category_container = this.search_dropdown.append(`
+			<div class="category-container mt-2 mb-1">
+				<div class="category-chips">
+				</div>
+			</div>
+		`).find(".category-chips");
+	}
+
+	setupRecentsContainer() {
+		let $recents_section = this.search_dropdown.append(`
+			<div class="mb-2 mt-2 recent-searches">
+				<div>
+					<b>${ __("Recent") }</b>
+				</div>
+			</div>
+		`).find(".recent-searches");
+
+		this.recents_container = $recents_section.append(`
+			<div id="recents" style="padding: .25rem 0 1rem 0;">
+			</div>
+		`).find("#recents");
+	}
+
+	getRecentSearches() {
+		return JSON.parse(localStorage.getItem("recent_searches") || "[]");
+	}
+
+	attachEventListenersToChips() {
+		let me  = this;
+		const chips = $(".recent-search");
+		window.chips = chips;
+
+		for (let chip of chips) {
+			chip.addEventListener("click", () => {
+				me.searchBox[0].value = chip.innerText.trim();
+
+				// Start search with `recent query`
+				me.searchBox.trigger("input");
+				me.searchBox.focus();
+			});
+		}
+	}
+
+	setRecentSearches(query) {
+		let recents = this.getRecentSearches();
+		if (recents.length >= this.MAX_RECENT_SEARCHES) {
+			// Remove the `first` query
+			recents.splice(0, 1);
+		}
+
+		if (recents.indexOf(query) >= 0) {
+			return;
+		}
+
+		recents.push(query);
+		localStorage.setItem("recent_searches", JSON.stringify(recents));
+
+		this.populateRecentSearches();
+	}
+
+	populateRecentSearches() {
+		let recents = this.getRecentSearches();
+
+		if (!recents.length) {
+			this.recents_container.html(`<span class=""text-muted">No searches yet.</span>`);
+			return;
+		}
+
+		let html = "";
+		recents.forEach((key) => {
+			html += `
+				<div class="recent-search mr-1" style="font-size: 13px">
+					<span class="mr-2">
+						<svg width="20" height="20" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+							<path d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14Z" stroke="var(--gray-500)"" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+							<path d="M8.00027 5.20947V8.00017L10 10" stroke="var(--gray-500)" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+						</svg>
+					</span>
+					${ key }
+				</div>
+			`;
+		});
+
+		this.recents_container.html(html);
+		this.attachEventListenersToChips();
+	}
+
+	populateResults(product_results) {
+		if (!product_results || product_results.length === 0) {
+			let empty_html = ``;
+			this.products_container.html(empty_html);
+			return;
+		}
+
+		let html = "";
+
+		product_results.forEach((res) => {
+			let thumbnail = res.thumbnail || '/assets/erpnext/images/ui-states/cart-empty-state.png';
+			html += `
+				<div class="dropdown-item" style="display: flex;">
+					<img class="item-thumb col-2" src=${thumbnail} />
+					<div class="col-9" style="white-space: normal;">
+						<a href="/${res.route}">${res.web_item_name}</a><br>
+						<span class="brand-line">${res.brand ? "by " + res.brand : ""}</span>
+					</div>
+				</div>
+			`;
+		});
+
+		this.products_container.html(html);
+	}
+
+	populateCategoriesList(category_results) {
+		if (!category_results || category_results.length === 0) {
+			let empty_html = `
+				<div class="category-container mt-2">
+					<div class="category-chips">
+					</div>
+				</div>
+			`;
+			this.category_container.html(empty_html);
+			return;
+		}
+
+		let html = `
+			<div class="mb-2">
+				<b>${ __("Categories") }</b>
+			</div>
+		`;
+
+		category_results.forEach((category) => {
+			html += `
+				<a href="/${category.route}" class="btn btn-sm category-chip mr-2 mb-2"
+					style="font-size: 13px" role="button">
+				${ category.name }
+				</button>
+			`;
+		});
+
+		this.category_container.html(html);
+	}
+};
\ No newline at end of file
diff --git a/erpnext/e_commerce/product_ui/views.js b/erpnext/e_commerce/product_ui/views.js
new file mode 100644
index 0000000..1b5c440
--- /dev/null
+++ b/erpnext/e_commerce/product_ui/views.js
@@ -0,0 +1,532 @@
+erpnext.ProductView =  class {
+	/* Options:
+		- View Type
+		- Products Section Wrapper,
+		- Item Group: If its an Item Group page
+	*/
+	constructor(options) {
+		Object.assign(this, options);
+		this.preference = this.view_type;
+		this.make();
+	}
+
+	make(from_filters=false) {
+		this.products_section.empty();
+		this.prepare_toolbar();
+		this.get_item_filter_data(from_filters);
+	}
+
+	prepare_toolbar() {
+		this.products_section.append(`
+			<div class="toolbar d-flex">
+			</div>
+		`);
+		this.prepare_search();
+		this.prepare_view_toggler();
+
+		new erpnext.ProductSearch();
+	}
+
+	prepare_view_toggler() {
+
+		if (!$("#list").length || !$("#image-view").length) {
+			this.render_view_toggler();
+			this.bind_view_toggler_actions();
+			this.set_view_state();
+		}
+	}
+
+	get_item_filter_data(from_filters=false) {
+		// Get and render all Product related views
+		let me = this;
+		this.from_filters = from_filters;
+		let args = this.get_query_filters();
+
+		this.disable_view_toggler(true);
+
+		frappe.call({
+			method: "erpnext.e_commerce.api.get_product_filter_data",
+			args: {
+				query_args: args
+			},
+			callback: function(result) {
+				if (!result || result.exc || !result.message || result.message.exc) {
+					me.render_no_products_section(true);
+				} else {
+					// Sub Category results are independent of Items
+					if (me.item_group && result.message["sub_categories"].length) {
+						me.render_item_sub_categories(result.message["sub_categories"]);
+					}
+
+					if (!result.message["items"].length) {
+						// if result has no items or result is empty
+						me.render_no_products_section();
+					} else {
+						// Add discount filters
+						me.re_render_discount_filters(result.message["filters"].discount_filters);
+
+						// Render views
+						me.render_list_view(result.message["items"], result.message["settings"]);
+						me.render_grid_view(result.message["items"], result.message["settings"]);
+
+						me.products = result.message["items"];
+						me.product_count = result.message["items_count"];
+					}
+
+					// Bind filter actions
+					if (!from_filters) {
+						// If `get_product_filter_data` was triggered after checking a filter,
+						// don't touch filters unnecessarily, only data must change
+						// filter persistence is handle on filter change event
+						me.bind_filters();
+						me.restore_filters_state();
+					}
+
+					// Bottom paging
+					me.add_paging_section(result.message["settings"]);
+				}
+
+				me.disable_view_toggler(false);
+			}
+		});
+	}
+
+	disable_view_toggler(disable=false) {
+		$('#list').prop('disabled', disable);
+		$('#image-view').prop('disabled', disable);
+	}
+
+	render_grid_view(items, settings) {
+		// loop over data and add grid html to it
+		let me = this;
+		this.prepare_product_area_wrapper("grid");
+
+		new erpnext.ProductGrid({
+			items: items,
+			products_section: $("#products-grid-area"),
+			settings: settings,
+			preference: me.preference
+		});
+	}
+
+	render_list_view(items, settings) {
+		let me = this;
+		this.prepare_product_area_wrapper("list");
+
+		new erpnext.ProductList({
+			items: items,
+			products_section: $("#products-list-area"),
+			settings: settings,
+			preference: me.preference
+		});
+	}
+
+	prepare_product_area_wrapper(view) {
+		let left_margin = view == "list" ? "ml-2" : "";
+		let top_margin = view == "list" ? "mt-6" : "mt-minus-1";
+		return this.products_section.append(`
+			<br>
+			<div id="products-${view}-area" class="row products-list ${ top_margin } ${ left_margin }"></div>
+		`);
+	}
+
+	get_query_filters() {
+		const filters = frappe.utils.get_query_params();
+		let {field_filters, attribute_filters} = filters;
+
+		field_filters = field_filters ? JSON.parse(field_filters) : {};
+		attribute_filters = attribute_filters ? JSON.parse(attribute_filters) : {};
+
+		return {
+			field_filters: field_filters,
+			attribute_filters: attribute_filters,
+			item_group: this.item_group,
+			start: filters.start || null,
+			from_filters: this.from_filters || false
+		};
+	}
+
+	add_paging_section(settings) {
+		$(".product-paging-area").remove();
+
+		if (this.products) {
+			let paging_html = `
+				<div class="row product-paging-area mt-5">
+					<div class="col-3">
+					</div>
+					<div class="col-9 text-right">
+			`;
+			let query_params = frappe.utils.get_query_params();
+			let start = query_params.start ? cint(JSON.parse(query_params.start)) : 0;
+			let page_length = settings.products_per_page || 0;
+
+			let prev_disable = start > 0 ? "" : "disabled";
+			let next_disable = (this.product_count > page_length) ? "" : "disabled";
+
+			paging_html += `
+				<button class="btn btn-default btn-prev" data-start="${ start - page_length }"
+					style="float: left" ${prev_disable}>
+					${ __("Prev") }
+				</button>`;
+
+			paging_html += `
+				<button class="btn btn-default btn-next" data-start="${ start + page_length }"
+					${next_disable}>
+					${ __("Next") }
+				</button>
+			`;
+
+			paging_html += `</div></div>`;
+
+			$(".page_content").append(paging_html);
+			this.bind_paging_action();
+		}
+	}
+
+	prepare_search() {
+		$(".toolbar").append(`
+			<div class="input-group col-8 p-0">
+				<div class="dropdown w-100" id="dropdownMenuSearch">
+					<input type="search" name="query" id="search-box" class="form-control font-md"
+						placeholder="Search for Products"
+						aria-label="Product" aria-describedby="button-addon2">
+					<div class="search-icon">
+						<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
+							fill="none"
+							stroke="currentColor" stroke-width="2" stroke-linecap="round"
+							stroke-linejoin="round"
+							class="feather feather-search">
+							<circle cx="11" cy="11" r="8"></circle>
+							<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
+						</svg>
+					</div>
+					<!-- Results dropdown rendered in product_search.js -->
+				</div>
+			</div>
+		`);
+	}
+
+	render_view_toggler() {
+		$(".toolbar").append(`<div class="toggle-container col-4 p-0"></div>`);
+
+		["btn-list-view", "btn-grid-view"].forEach(view => {
+			let icon = view === "btn-list-view" ? "list" : "image-view";
+			$(".toggle-container").append(`
+				<div class="form-group mb-0" id="toggle-view">
+					<button id="${ icon }" class="btn ${ view } mr-2">
+						<span>
+							<svg class="icon icon-md">
+								<use href="#icon-${ icon }"></use>
+							</svg>
+						</span>
+					</button>
+				</div>
+			`);
+		});
+	}
+
+	bind_view_toggler_actions() {
+		$("#list").click(function() {
+			let $btn = $(this);
+			$btn.removeClass('btn-primary');
+			$btn.addClass('btn-primary');
+			$(".btn-grid-view").removeClass('btn-primary');
+
+			$("#products-grid-area").addClass("hidden");
+			$("#products-list-area").removeClass("hidden");
+			localStorage.setItem("product_view", "List View");
+		});
+
+		$("#image-view").click(function() {
+			let $btn = $(this);
+			$btn.removeClass('btn-primary');
+			$btn.addClass('btn-primary');
+			$(".btn-list-view").removeClass('btn-primary');
+
+			$("#products-list-area").addClass("hidden");
+			$("#products-grid-area").removeClass("hidden");
+			localStorage.setItem("product_view", "Grid View");
+		});
+	}
+
+	set_view_state() {
+		if (this.preference === "List View") {
+			$("#list").addClass('btn-primary');
+			$("#image-view").removeClass('btn-primary');
+		} else {
+			$("#image-view").addClass('btn-primary');
+			$("#list").removeClass('btn-primary');
+		}
+	}
+
+	bind_paging_action() {
+		let me = this;
+		$('.btn-prev, .btn-next').click((e) => {
+			const $btn = $(e.target);
+			me.from_filters = false;
+
+			$btn.prop('disabled', true);
+			const start = $btn.data('start');
+
+			let query_params = frappe.utils.get_query_params();
+			query_params.start = start;
+			let path = window.location.pathname + '?' + frappe.utils.get_url_from_dict(query_params);
+			window.location.href = path;
+		});
+	}
+
+	re_render_discount_filters(filter_data) {
+		this.get_discount_filter_html(filter_data);
+		if (this.from_filters) {
+			// Bind filter action if triggered via filters
+			// if not from filter action, page load will bind actions
+			this.bind_discount_filter_action();
+		}
+		// discount filters are rendered with Items (later)
+		// unlike the other filters
+		this.restore_discount_filter();
+	}
+
+	get_discount_filter_html(filter_data) {
+		$("#discount-filters").remove();
+		if (filter_data) {
+			$("#product-filters").append(`
+				<div id="discount-filters" class="mb-4 filter-block pb-5">
+					<div class="filter-label mb-3">${ __("Discounts") }</div>
+				</div>
+			`);
+
+			let html = `<div class="filter-options">`;
+			filter_data.forEach(filter => {
+				html += `
+					<div class="checkbox">
+						<label data-value="${ filter[0] }">
+							<input type="radio"
+								class="product-filter discount-filter"
+								name="discount" id="${ filter[0] }"
+								data-filter-name="discount"
+								data-filter-value="${ filter[0] }"
+								style="width: 14px !important"
+							>
+								<span class="label-area" for="${ filter[0] }">
+									${ filter[1] }
+								</span>
+						</label>
+					</div>
+				`;
+			});
+			html += `</div>`;
+
+			$("#discount-filters").append(html);
+		}
+	}
+
+	restore_discount_filter() {
+		const filters = frappe.utils.get_query_params();
+		let field_filters = filters.field_filters;
+		if (!field_filters) return;
+
+		field_filters = JSON.parse(field_filters);
+
+		if (field_filters && field_filters["discount"]) {
+			const values = field_filters["discount"];
+			const selector = values.map(value => {
+				return `input[data-filter-name="discount"][data-filter-value="${value}"]`;
+			}).join(',');
+			$(selector).prop('checked', true);
+			this.field_filters = field_filters;
+		}
+	}
+
+	bind_discount_filter_action() {
+		let me = this;
+		$('.discount-filter').on('change', (e) => {
+			const $checkbox = $(e.target);
+			const is_checked = $checkbox.is(':checked');
+
+			const {
+				filterValue: filter_value
+			} = $checkbox.data();
+
+			delete this.field_filters["discount"];
+
+			if (is_checked) {
+				this.field_filters["discount"] = [];
+				this.field_filters["discount"].push(filter_value);
+			}
+
+			if (this.field_filters["discount"].length === 0) {
+				delete this.field_filters["discount"];
+			}
+
+			me.change_route_with_filters();
+		});
+	}
+
+	bind_filters() {
+		let me = this;
+		this.field_filters = {};
+		this.attribute_filters = {};
+
+		$('.product-filter').on('change', (e) => {
+			me.from_filters = true;
+
+			const $checkbox = $(e.target);
+			const is_checked = $checkbox.is(':checked');
+
+			if ($checkbox.is('.attribute-filter')) {
+				const {
+					attributeName: attribute_name,
+					attributeValue: attribute_value
+				} = $checkbox.data();
+
+				if (is_checked) {
+					this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
+					this.attribute_filters[attribute_name].push(attribute_value);
+				} else {
+					this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
+					this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value);
+				}
+
+				if (this.attribute_filters[attribute_name].length === 0) {
+					delete this.attribute_filters[attribute_name];
+				}
+			} else if ($checkbox.is('.field-filter') || $checkbox.is('.discount-filter')) {
+				const {
+					filterName: filter_name,
+					filterValue: filter_value
+				} = $checkbox.data();
+
+				if ($checkbox.is('.discount-filter')) {
+					// clear previous discount filter to accomodate new
+					delete this.field_filters["discount"];
+				}
+				if (is_checked) {
+					this.field_filters[filter_name] = this.field_filters[filter_name] || [];
+					if (!in_list(this.field_filters[filter_name], filter_value)) {
+						this.field_filters[filter_name].push(filter_value);
+					}
+				} else {
+					this.field_filters[filter_name] = this.field_filters[filter_name] || [];
+					this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value);
+				}
+
+				if (this.field_filters[filter_name].length === 0) {
+					delete this.field_filters[filter_name];
+				}
+			}
+
+			me.change_route_with_filters();
+		});
+	}
+
+	change_route_with_filters() {
+		let route_params = frappe.utils.get_query_params();
+
+		let start = this.if_key_exists(route_params.start) || 0;
+		if (this.from_filters) {
+			start = 0; // show items from first page if new filters are triggered
+		}
+
+		const query_string = this.get_query_string({
+			start: start,
+			field_filters: JSON.stringify(this.if_key_exists(this.field_filters)),
+			attribute_filters: JSON.stringify(this.if_key_exists(this.attribute_filters)),
+		});
+		window.history.pushState('filters', '', `${location.pathname}?` + query_string);
+
+		$('.page_content input').prop('disabled', true);
+
+		this.make(true);
+		$('.page_content input').prop('disabled', false);
+	}
+
+	restore_filters_state() {
+		const filters = frappe.utils.get_query_params();
+		let {field_filters, attribute_filters} = filters;
+
+		if (field_filters) {
+			field_filters = JSON.parse(field_filters);
+			for (let fieldname in field_filters) {
+				const values = field_filters[fieldname];
+				const selector = values.map(value => {
+					return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`;
+				}).join(',');
+				$(selector).prop('checked', true);
+			}
+			this.field_filters = field_filters;
+		}
+		if (attribute_filters) {
+			attribute_filters = JSON.parse(attribute_filters);
+			for (let attribute in attribute_filters) {
+				const values = attribute_filters[attribute];
+				const selector = values.map(value => {
+					return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`;
+				}).join(',');
+				$(selector).prop('checked', true);
+			}
+			this.attribute_filters = attribute_filters;
+		}
+	}
+
+	render_no_products_section(error=false) {
+		let error_section = `
+			<div class="mt-4 w-100 alert alert-error font-md">
+				Something went wrong. Please refresh or contact us.
+			</div>
+		`;
+		let no_results_section = `
+			<div class="cart-empty frappe-card mt-4">
+				<div class="cart-empty-state">
+					<img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
+				</div>
+				<div class="cart-empty-message mt-4">${ __('No products found') }</p>
+			</div>
+		`;
+
+		this.products_section.append(error ? error_section : no_results_section);
+	}
+
+	render_item_sub_categories(categories) {
+		if (categories && categories.length) {
+			let sub_group_html = `
+				<div class="sub-category-container scroll-categories">
+			`;
+
+			categories.forEach(category => {
+				sub_group_html += `
+					<a href="${ category.route || '#' }" style="text-decoration: none;">
+						<div class="category-pill">
+							${ category.name }
+						</div>
+					</a>
+				`;
+			});
+			sub_group_html += `</div>`;
+
+			$("#product-listing").prepend(sub_group_html);
+		}
+	}
+
+	get_query_string(object) {
+		const url = new URLSearchParams();
+		for (let key in object) {
+			const value = object[key];
+			if (value) {
+				url.append(key, value);
+			}
+		}
+		return url.toString();
+	}
+
+	if_key_exists(obj) {
+		let exists = false;
+		for (let key in obj) {
+			if (Object.prototype.hasOwnProperty.call(obj, key) && obj[key]) {
+				exists = true;
+				break;
+			}
+		}
+		return exists ? obj : undefined;
+	}
+};
\ No newline at end of file
diff --git a/erpnext/e_commerce/redisearch_utils.py b/erpnext/e_commerce/redisearch_utils.py
new file mode 100644
index 0000000..59c7f32
--- /dev/null
+++ b/erpnext/e_commerce/redisearch_utils.py
@@ -0,0 +1,210 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe.utils.redis_wrapper import RedisWrapper
+from redisearch import AutoCompleter, Client, IndexDefinition, Suggestion, TagField, TextField
+
+WEBSITE_ITEM_INDEX = 'website_items_index'
+WEBSITE_ITEM_KEY_PREFIX = 'website_item:'
+WEBSITE_ITEM_NAME_AUTOCOMPLETE = 'website_items_name_dict'
+WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE = 'website_items_category_dict'
+
+def get_indexable_web_fields():
+	"Return valid fields from Website Item that can be searched for."
+	web_item_meta = frappe.get_meta("Website Item", cached=True)
+	valid_fields = filter(
+		lambda df: df.fieldtype in ("Link", "Table MultiSelect", "Data", "Small Text", "Text Editor"),
+		web_item_meta.fields)
+
+	return [df.fieldname for df in valid_fields]
+
+def is_search_module_loaded():
+	try:
+		cache = frappe.cache()
+		out = cache.execute_command('MODULE LIST')
+
+		parsed_output = " ".join(
+			(" ".join([s.decode() for s in o if not isinstance(s, int)]) for o in out)
+		)
+		return "search" in parsed_output
+	except Exception:
+		return False
+
+def if_redisearch_loaded(function):
+	"Decorator to check if Redisearch is loaded."
+	def wrapper(*args, **kwargs):
+		if is_search_module_loaded():
+			func = function(*args, **kwargs)
+			return func
+		return
+
+	return wrapper
+
+def make_key(key):
+	return "{0}|{1}".format(frappe.conf.db_name, key).encode('utf-8')
+
+@if_redisearch_loaded
+def create_website_items_index():
+	"Creates Index Definition."
+
+	# CREATE index
+	client = Client(make_key(WEBSITE_ITEM_INDEX), conn=frappe.cache())
+
+	# DROP if already exists
+	try:
+		client.drop_index()
+	except Exception:
+		pass
+
+	idx_def = IndexDefinition([make_key(WEBSITE_ITEM_KEY_PREFIX)])
+
+	# Based on e-commerce settings
+	idx_fields = frappe.db.get_single_value(
+		'E Commerce Settings',
+		'search_index_fields'
+	)
+	idx_fields = idx_fields.split(',') if idx_fields else []
+
+	if 'web_item_name' in idx_fields:
+		idx_fields.remove('web_item_name')
+
+	idx_fields = list(map(to_search_field, idx_fields))
+
+	client.create_index(
+		[TextField("web_item_name", sortable=True)] + idx_fields,
+		definition=idx_def,
+	)
+
+	reindex_all_web_items()
+	define_autocomplete_dictionary()
+
+def to_search_field(field):
+	if field == "tags":
+		return TagField("tags", separator=",")
+
+	return TextField(field)
+
+@if_redisearch_loaded
+def insert_item_to_index(website_item_doc):
+	# Insert item to index
+	key = get_cache_key(website_item_doc.name)
+	cache = frappe.cache()
+	web_item = create_web_item_map(website_item_doc)
+
+	for k, v in web_item.items():
+		super(RedisWrapper, cache).hset(make_key(key), k, v)
+
+	insert_to_name_ac(website_item_doc.web_item_name, website_item_doc.name)
+
+@if_redisearch_loaded
+def insert_to_name_ac(web_name, doc_name):
+	ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=frappe.cache())
+	ac.add_suggestions(Suggestion(web_name, payload=doc_name))
+
+def create_web_item_map(website_item_doc):
+	fields_to_index = get_fields_indexed()
+	web_item = {}
+
+	for f in fields_to_index:
+		web_item[f] = website_item_doc.get(f) or ''
+
+	return web_item
+
+@if_redisearch_loaded
+def update_index_for_item(website_item_doc):
+	# Reinsert to Cache
+	insert_item_to_index(website_item_doc)
+	define_autocomplete_dictionary()
+
+@if_redisearch_loaded
+def delete_item_from_index(website_item_doc):
+	cache = frappe.cache()
+	key = get_cache_key(website_item_doc.name)
+
+	try:
+		cache.delete(key)
+	except Exception:
+		return False
+
+	delete_from_ac_dict(website_item_doc)
+	return True
+
+@if_redisearch_loaded
+def delete_from_ac_dict(website_item_doc):
+	'''Removes this items's name from autocomplete dictionary'''
+	cache = frappe.cache()
+	name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
+	name_ac.delete(website_item_doc.web_item_name)
+
+@if_redisearch_loaded
+def define_autocomplete_dictionary():
+	"""Creates an autocomplete search dictionary for `name`.
+		Also creats autocomplete dictionary for `categories` if
+		checked in E Commerce Settings"""
+
+	cache = frappe.cache()
+	name_ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=cache)
+	cat_ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=cache)
+
+	ac_categories = frappe.db.get_single_value(
+		'E Commerce Settings',
+		'show_categories_in_search_autocomplete'
+	)
+
+	# Delete both autocomplete dicts
+	try:
+		cache.delete(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE))
+		cache.delete(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE))
+	except Exception:
+		return False
+
+	items = frappe.get_all(
+		'Website Item',
+		fields=['web_item_name', 'item_group'],
+		filters={"published": 1}
+	)
+
+	for item in items:
+		name_ac.add_suggestions(Suggestion(item.web_item_name))
+		if ac_categories and item.item_group:
+			cat_ac.add_suggestions(Suggestion(item.item_group))
+
+	return True
+
+@if_redisearch_loaded
+def reindex_all_web_items():
+	items = frappe.get_all(
+		'Website Item',
+		fields=get_fields_indexed(),
+		filters={"published": True}
+	)
+
+	cache = frappe.cache()
+	for item in items:
+		web_item = create_web_item_map(item)
+		key = make_key(get_cache_key(item.name))
+
+		for k, v in web_item.items():
+			super(RedisWrapper, cache).hset(key, k, v)
+
+def get_cache_key(name):
+	name = frappe.scrub(name)
+	return f"{WEBSITE_ITEM_KEY_PREFIX}{name}"
+
+def get_fields_indexed():
+	fields_to_index = frappe.db.get_single_value(
+		'E Commerce Settings',
+		'search_index_fields'
+	)
+	fields_to_index = fields_to_index.split(',') if fields_to_index else []
+
+	mandatory_fields = ['name', 'web_item_name', 'route', 'thumbnail', 'ranking']
+	fields_to_index = fields_to_index + mandatory_fields
+
+	return fields_to_index
+
+# TODO: Remove later
+# # Figure out a way to run this at startup
+define_autocomplete_dictionary()
+create_website_items_index()
diff --git a/erpnext/hotels/report/__init__.py b/erpnext/e_commerce/shopping_cart/__init__.py
similarity index 100%
rename from erpnext/hotels/report/__init__.py
rename to erpnext/e_commerce/shopping_cart/__init__.py
diff --git a/erpnext/shopping_cart/cart.py b/erpnext/e_commerce/shopping_cart/cart.py
similarity index 87%
rename from erpnext/shopping_cart/cart.py
rename to erpnext/e_commerce/shopping_cart/cart.py
index ebbe233..372aed0 100644
--- a/erpnext/shopping_cart/cart.py
+++ b/erpnext/e_commerce/shopping_cart/cart.py
@@ -1,7 +1,6 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
 import frappe.defaults
 from frappe import _, throw
@@ -11,20 +10,20 @@
 from frappe.utils.nestedset import get_root_of
 
 from erpnext.accounts.utils import get_account_name
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
 )
-from erpnext.utilities.product import get_qty_in_stock
+from erpnext.utilities.product import get_web_item_qty_in_stock
 
 
 class WebsitePriceListMissingError(frappe.ValidationError):
 	pass
 
 def set_cart_count(quotation=None):
-	if cint(frappe.db.get_singles_value("Shopping Cart Settings", "enabled")):
+	if cint(frappe.db.get_singles_value("E Commerce Settings", "enabled")):
 		if not quotation:
 			quotation = _get_cart_quotation()
-		cart_count = cstr(len(quotation.get("items")))
+		cart_count = cstr(cint(quotation.get("total_qty")))
 
 		if hasattr(frappe.local, "cookie_manager"):
 			frappe.local.cookie_manager.set_cookie("cart_count", cart_count)
@@ -48,7 +47,7 @@
 		"shipping_addresses": get_shipping_addresses(party),
 		"billing_addresses": get_billing_addresses(party),
 		"shipping_rules": get_applicable_shipping_rules(party),
-		"cart_settings": frappe.get_cached_doc("Shopping Cart Settings")
+		"cart_settings": frappe.get_cached_doc("E Commerce Settings")
 	}
 
 @frappe.whitelist()
@@ -72,7 +71,7 @@
 @frappe.whitelist()
 def place_order():
 	quotation = _get_cart_quotation()
-	cart_settings = frappe.db.get_value("Shopping Cart Settings", None,
+	cart_settings = frappe.db.get_value("E Commerce Settings", None,
 		["company", "allow_items_not_in_stock"], as_dict=1)
 	quotation.company = cart_settings.company
 
@@ -92,13 +91,19 @@
 
 	if not cint(cart_settings.allow_items_not_in_stock):
 		for item in sales_order.get("items"):
-			item.reserved_warehouse, is_stock_item = frappe.db.get_value("Item",
-				item.item_code, ["website_warehouse", "is_stock_item"])
+			item.warehouse = frappe.db.get_value(
+				"Website Item",
+				{
+					"item_code": item.item_code
+				},
+				"website_warehouse"
+			)
+			is_stock_item = frappe.db.get_value("Item", item.item_code, "is_stock_item")
 
 			if is_stock_item:
-				item_stock = get_qty_in_stock(item.item_code, "website_warehouse")
+				item_stock = get_web_item_qty_in_stock(item.item_code, "website_warehouse")
 				if not cint(item_stock.in_stock):
-					throw(_("{1} Not in Stock").format(item.item_code))
+					throw(_("{0} Not in Stock").format(item.item_code))
 				if item.qty > item_stock.stock_qty[0][0]:
 					throw(_("Only {0} in Stock for item {1}").format(item_stock.stock_qty[0][0], item.item_code))
 
@@ -156,19 +161,19 @@
 
 	set_cart_count(quotation)
 
-	context = get_cart_quotation(quotation)
-
 	if cint(with_items):
+		context = get_cart_quotation(quotation)
 		return {
 			"items": frappe.render_template("templates/includes/cart/cart_items.html",
 				context),
-			"taxes": frappe.render_template("templates/includes/order/order_taxes.html",
+			"total": frappe.render_template("templates/includes/cart/cart_items_total.html",
 				context),
+			"taxes_and_totals": frappe.render_template("templates/includes/cart/cart_payment_summary.html",
+				context)
 		}
 	else:
 		return {
-			'name': quotation.name,
-			'shopping_cart_menu': get_shopping_cart_menu(context)
+			'name': quotation.name
 		}
 
 @frappe.whitelist()
@@ -265,13 +270,36 @@
 		territory = frappe.db.get_value("Territory", geoip_country)
 
 	return territory or \
-		frappe.db.get_value("Shopping Cart Settings", None, "territory") or \
+		frappe.db.get_value("E Commerce Settings", None, "territory") or \
 			get_root_of("Territory")
 
 def decorate_quotation_doc(doc):
 	for d in doc.get("items", []):
-		d.update(frappe.db.get_value("Item", d.item_code,
-			["thumbnail", "website_image", "description", "route"], as_dict=True))
+		item_code = d.item_code
+		fields = ["web_item_name", "thumbnail", "website_image", "description", "route"]
+
+		# Variant Item
+		if not frappe.db.exists("Website Item", {"item_code": item_code}):
+			variant_data = frappe.db.get_values(
+				"Item",
+				filters={"item_code": item_code},
+				fieldname=["variant_of", "item_name", "image"],
+				as_dict=True
+			)[0]
+			item_code = variant_data.variant_of
+			fields = fields[1:]
+			d.web_item_name = variant_data.item_name
+
+			if variant_data.image: # get image from variant or template web item
+				d.thumbnail = variant_data.image
+				fields = fields[2:]
+
+		d.update(frappe.db.get_value(
+			"Website Item",
+			{"item_code": item_code},
+			fields,
+			as_dict=True)
+		)
 
 	return doc
 
@@ -282,13 +310,13 @@
 		party = get_party()
 
 	quotation = frappe.get_all("Quotation", fields=["name"], filters=
-		{"party_name": party.name, "order_type": "Shopping Cart", "docstatus": 0},
+		{"party_name": party.name, "contact_email": frappe.session.user, "order_type": "Shopping Cart", "docstatus": 0},
 		order_by="modified desc", limit_page_length=1)
 
 	if quotation:
 		qdoc = frappe.get_doc("Quotation", quotation[0].name)
 	else:
-		company = frappe.db.get_value("Shopping Cart Settings", None, ["company"])
+		company = frappe.db.get_value("E Commerce Settings", None, ["company"])
 		qdoc = frappe.get_doc({
 			"doctype": "Quotation",
 			"naming_series": get_shopping_cart_settings().quotation_series or "QTN-CART-",
@@ -343,7 +371,7 @@
 	if not quotation:
 		quotation = _get_cart_quotation(party)
 
-	cart_settings = frappe.get_doc("Shopping Cart Settings")
+	cart_settings = frappe.get_doc("E Commerce Settings")
 
 	set_price_list_and_rate(quotation, cart_settings)
 
@@ -420,7 +448,7 @@
 			party_doctype = contact.links[0].link_doctype
 			party = contact.links[0].link_name
 
-	cart_settings = frappe.get_doc("Shopping Cart Settings")
+	cart_settings = frappe.get_doc("E Commerce Settings")
 
 	debtors_account = ''
 
@@ -557,10 +585,20 @@
 	if quotation.shipping_address_name:
 		country = frappe.db.get_value("Address", quotation.shipping_address_name, "country")
 		if country:
-			shipping_rules = frappe.db.sql_list("""select distinct sr.name
-				from `tabShipping Rule Country` src, `tabShipping Rule` sr
-				where src.country = %s and
-				sr.disabled != 1 and sr.name = src.parent""", country)
+			sr_country = frappe.qb.DocType("Shipping Rule Country")
+			sr = frappe.qb.DocType("Shipping Rule")
+			query = (
+				frappe.qb.from_(sr_country)
+				.join(sr).on(sr.name == sr_country.parent)
+				.select(sr.name)
+				.distinct()
+				.where(
+					(sr_country.country == country)
+					& (sr.disabled != 1)
+				)
+			)
+			result = query.run(as_list=True)
+			shipping_rules = [x[0] for x in result]
 
 	return shipping_rules
 
diff --git a/erpnext/e_commerce/shopping_cart/product_info.py b/erpnext/e_commerce/shopping_cart/product_info.py
new file mode 100644
index 0000000..595fed0
--- /dev/null
+++ b/erpnext/e_commerce/shopping_cart/product_info.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
+	get_shopping_cart_settings,
+	show_quantity_in_website,
+)
+from erpnext.e_commerce.shopping_cart.cart import _get_cart_quotation, _set_price_list
+from erpnext.utilities.product import (
+	get_non_stock_item_status,
+	get_price,
+	get_web_item_qty_in_stock,
+)
+
+
+@frappe.whitelist(allow_guest=True)
+def get_product_info_for_website(item_code, skip_quotation_creation=False):
+	"""get product price / stock info for website"""
+
+	cart_settings = get_shopping_cart_settings()
+	if not cart_settings.enabled:
+		# return settings even if cart is disabled
+		return frappe._dict({
+			"product_info": {},
+			"cart_settings": cart_settings
+		})
+
+	cart_quotation = frappe._dict()
+	if not skip_quotation_creation:
+		cart_quotation = _get_cart_quotation()
+
+	selling_price_list = cart_quotation.get("selling_price_list") if cart_quotation else _set_price_list(cart_settings, None)
+
+	price = {}
+	if cart_settings.show_price:
+		is_guest = frappe.session.user == "Guest"
+		# Show Price if logged in.
+		# If not logged in, check if price is hidden for guest.
+		if not is_guest or not cart_settings.hide_price_for_guest:
+			price = get_price(
+				item_code,
+				selling_price_list,
+				cart_settings.default_customer_group,
+				cart_settings.company
+			)
+
+	stock_status = None
+
+	if cart_settings.show_stock_availability:
+		on_backorder = frappe.get_cached_value("Website Item", {"item_code": item_code}, "on_backorder")
+		if on_backorder:
+			stock_status = frappe._dict({"on_backorder": True})
+		else:
+			stock_status = get_web_item_qty_in_stock(item_code, "website_warehouse")
+
+	product_info = {
+		"price": price,
+		"qty": 0,
+		"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
+		"sales_uom": frappe.db.get_value("Item", item_code, "sales_uom")
+	}
+
+	if stock_status:
+		if stock_status.on_backorder:
+			product_info["on_backorder"] = True
+		else:
+			product_info["stock_qty"] = stock_status.stock_qty
+			product_info["in_stock"] = stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse")
+			product_info["show_stock_qty"] = show_quantity_in_website()
+
+	if product_info["price"]:
+		if frappe.session.user != "Guest":
+			item = cart_quotation.get({"item_code": item_code}) if cart_quotation else None
+			if item:
+				product_info["qty"] = item[0].qty
+
+	return frappe._dict({
+		"product_info": product_info,
+		"cart_settings": cart_settings
+	})
+
+def set_product_info_for_website(item):
+	"""set product price uom for website"""
+	product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
+
+	if product_info:
+		item.update(product_info)
+		item["stock_uom"] = product_info.get("uom")
+		item["sales_uom"] = product_info.get("sales_uom")
+		if product_info.get("price"):
+			item["price_stock_uom"] = product_info.get("price").get("formatted_price")
+			item["price_sales_uom"] = product_info.get("price").get("formatted_price_sales_uom")
+		else:
+			item["price_stock_uom"] = ""
+			item["price_sales_uom"] = ""
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
similarity index 70%
rename from erpnext/shopping_cart/test_shopping_cart.py
rename to erpnext/e_commerce/shopping_cart/test_shopping_cart.py
index 60c220a..9c389d0 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/e_commerce/shopping_cart/test_shopping_cart.py
@@ -5,10 +5,17 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import change_settings
 from frappe.utils import add_months, nowdate
 
 from erpnext.accounts.doctype.tax_rule.tax_rule import ConflictingTaxRule
-from erpnext.shopping_cart.cart import _get_cart_quotation, get_party, update_cart
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+from erpnext.e_commerce.shopping_cart.cart import (
+	_get_cart_quotation,
+	get_cart_quotation,
+	get_party,
+	update_cart,
+)
 from erpnext.tests.utils import create_test_contact_and_address
 
 # test_dependencies = ['Payment Terms Template']
@@ -27,8 +34,14 @@
 		frappe.set_user("Administrator")
 		create_test_contact_and_address()
 		self.enable_shopping_cart()
+		if not frappe.db.exists("Website Item", {"item_code": "_Test Item"}):
+			make_website_item(frappe.get_cached_doc("Item",  "_Test Item"))
+
+		if not frappe.db.exists("Website Item", {"item_code": "_Test Item 2"}):
+			make_website_item(frappe.get_cached_doc("Item",  "_Test Item 2"))
 
 	def tearDown(self):
+		frappe.db.rollback()
 		frappe.set_user("Administrator")
 		self.disable_shopping_cart()
 
@@ -45,13 +58,19 @@
 		return quotation
 
 	def test_get_cart_customer(self):
-		self.login_as_customer()
+		def validate_quotation():
+			# test if quotation with customer is fetched
+			quotation = _get_cart_quotation()
+			self.assertEqual(quotation.quotation_to, "Customer")
+			self.assertEqual(quotation.party_name, "_Test Customer")
+			self.assertEqual(quotation.contact_email, frappe.session.user)
+			return quotation
 
-		# test if quotation with customer is fetched
-		quotation = _get_cart_quotation()
-		self.assertEqual(quotation.quotation_to, "Customer")
-		self.assertEqual(quotation.party_name, "_Test Customer")
-		self.assertEqual(quotation.contact_email, frappe.session.user)
+		self.login_as_customer("test_contact_two_customer@example.com", "_Test Contact 2 For _Test Customer")
+		validate_quotation()
+
+		self.login_as_customer()
+		quotation = validate_quotation()
 
 		return quotation
 
@@ -123,10 +142,47 @@
 
 		self.remove_test_quotation(quotation)
 
+	@change_settings("E Commerce Settings",{
+		"company": "_Test Company",
+		"enabled": 1,
+		"default_customer_group": "_Test Customer Group",
+		"price_list": "_Test Price List India",
+		"show_price": 1
+	})
+	def test_add_item_variant_without_web_item_to_cart(self):
+		"Test adding Variants having no Website Items in cart via Template Web Item."
+		from erpnext.controllers.item_variant import create_variant
+		from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		template_item = make_item("Test-Tshirt-Temp", {
+			"has_variant": 1,
+			"variant_based_on": "Item Attribute",
+			"attributes": [
+				{"attribute": "Test Size"},
+				{"attribute": "Test Colour"}
+			]
+		})
+		variant = create_variant("Test-Tshirt-Temp", {
+			"Test Size": "Small", "Test Colour": "Red"
+		})
+		variant.save()
+		make_website_item(template_item) # publish template not variant
+
+		update_cart("Test-Tshirt-Temp-S-R", 1)
+
+		cart = get_cart_quotation() # test if cart page gets data without errors
+		doc = cart.get("doc")
+
+		self.assertEqual(doc.get("items")[0].item_name, "Test-Tshirt-Temp-S-R")
+
+		# test if items are rendered without error
+		frappe.render_template("templates/includes/cart/cart_items.html", cart)
+
 	def create_tax_rule(self):
 		tax_rule = frappe.get_test_records("Tax Rule")[0]
 		try:
-			frappe.get_doc(tax_rule).insert()
+			frappe.get_doc(tax_rule).insert(ignore_if_duplicate=True)
 		except (frappe.DuplicateEntryError, ConflictingTaxRule):
 			pass
 
@@ -166,7 +222,7 @@
 
 	# helper functions
 	def enable_shopping_cart(self):
-		settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
+		settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
 
 		settings.update({
 			"enabled": 1,
@@ -196,7 +252,7 @@
 		frappe.local.shopping_cart_settings = None
 
 	def disable_shopping_cart(self):
-		settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
+		settings = frappe.get_doc("E Commerce Settings", "E Commerce Settings")
 		settings.enabled = 0
 		settings.save()
 		frappe.local.shopping_cart_settings = None
@@ -205,10 +261,9 @@
 		self.create_user_if_not_exists("test_cart_user@example.com")
 		frappe.set_user("test_cart_user@example.com")
 
-	def login_as_customer(self):
-		self.create_user_if_not_exists("test_contact_customer@example.com",
-			"_Test Contact For _Test Customer")
-		frappe.set_user("test_contact_customer@example.com")
+	def login_as_customer(self, email="test_contact_customer@example.com", name="_Test Contact For _Test Customer"):
+		self.create_user_if_not_exists(email, name)
+		frappe.set_user(email)
 
 	def clear_existing_quotations(self):
 		quotations = frappe.get_all("Quotation", filters={
diff --git a/erpnext/shopping_cart/utils.py b/erpnext/e_commerce/shopping_cart/utils.py
similarity index 84%
rename from erpnext/shopping_cart/utils.py
rename to erpnext/e_commerce/shopping_cart/utils.py
index 5f0c792..e9745a4 100644
--- a/erpnext/shopping_cart/utils.py
+++ b/erpnext/e_commerce/shopping_cart/utils.py
@@ -1,10 +1,8 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 import frappe
 
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	is_cart_enabled,
-)
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import is_cart_enabled
 
 
 def show_cart_count():
@@ -23,7 +21,7 @@
 		return
 
 	if show_cart_count():
-		from erpnext.shopping_cart.cart import set_cart_count
+		from erpnext.e_commerce.shopping_cart.cart import set_cart_count
 
 		# set_cart_count will try to fetch existing cart quotation
 		# or create one if non existent (and create a customer too)
diff --git a/erpnext/portal/product_configurator/__init__.py b/erpnext/e_commerce/variant_selector/__init__.py
similarity index 100%
rename from erpnext/portal/product_configurator/__init__.py
rename to erpnext/e_commerce/variant_selector/__init__.py
diff --git a/erpnext/portal/product_configurator/item_variants_cache.py b/erpnext/e_commerce/variant_selector/item_variants_cache.py
similarity index 77%
rename from erpnext/portal/product_configurator/item_variants_cache.py
rename to erpnext/e_commerce/variant_selector/item_variants_cache.py
index 636ae8d..3107c01 100644
--- a/erpnext/portal/product_configurator/item_variants_cache.py
+++ b/erpnext/e_commerce/variant_selector/item_variants_cache.py
@@ -44,7 +44,7 @@
 		val = frappe.cache().get_value('ordered_attribute_values_map')
 		if val: return val
 
-		all_attribute_values = frappe.db.get_all('Item Attribute Value',
+		all_attribute_values = frappe.get_all('Item Attribute Value',
 			['attribute_value', 'idx', 'parent'], order_by='idx asc')
 
 		ordered_attribute_values_map = frappe._dict({})
@@ -57,22 +57,33 @@
 	def build_cache(self):
 		parent_item_code = self.item_code
 
-		attributes = [a.attribute for a in frappe.db.get_all('Item Variant Attribute',
-			{'parent': parent_item_code}, ['attribute'], order_by='idx asc')
+		attributes = [
+			a.attribute for a in frappe.get_all(
+				'Item Variant Attribute',
+				{'parent': parent_item_code},
+				['attribute'],
+				order_by='idx asc'
+			)
 		]
 
-		item_variants_data = frappe.db.get_all('Item Variant Attribute',
-			{'variant_of': parent_item_code}, ['parent', 'attribute', 'attribute_value'],
-			order_by='name',
-			as_list=1
+		# Get Variants and tehir Attributes that are not disabled
+		iva = frappe.qb.DocType("Item Variant Attribute")
+		item = frappe.qb.DocType("Item")
+		query = (
+			frappe.qb.from_(iva)
+			.join(item).on(item.name == iva.parent)
+			.select(
+				iva.parent, iva.attribute, iva.attribute_value
+			).where(
+				(iva.variant_of == parent_item_code)
+				& (item.disabled == 0)
+			).orderby(iva.name)
 		)
+		item_variants_data = query.run()
 
-		disabled_items = set([i.name for i in frappe.db.get_all('Item', {'disabled': 1})])
+		attribute_value_item_map = frappe._dict()
+		item_attribute_value_map = frappe._dict()
 
-		attribute_value_item_map = frappe._dict({})
-		item_attribute_value_map = frappe._dict({})
-
-		item_variants_data = [r for r in item_variants_data if r[0] not in disabled_items]
 		for row in item_variants_data:
 			item_code, attribute, attribute_value = row
 			# (attr, value) => [item1, item2]
@@ -111,4 +122,7 @@
 def enqueue_build_cache(item_code):
 	if frappe.cache().hget('item_cache_build_in_progress', item_code):
 		return
-	frappe.enqueue(build_cache, item_code=item_code, queue='long')
+	frappe.enqueue(
+		"erpnext.e_commerce.variant_selector.item_variants_cache.build_cache",
+		item_code=item_code, queue='long'
+	)
diff --git a/erpnext/e_commerce/variant_selector/test_variant_selector.py b/erpnext/e_commerce/variant_selector/test_variant_selector.py
new file mode 100644
index 0000000..ee098e1
--- /dev/null
+++ b/erpnext/e_commerce/variant_selector/test_variant_selector.py
@@ -0,0 +1,119 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.controllers.item_variant import create_variant
+from erpnext.e_commerce.doctype.e_commerce_settings.test_e_commerce_settings import (
+	setup_e_commerce_settings,
+)
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+from erpnext.e_commerce.variant_selector.utils import get_next_attribute_and_values
+from erpnext.stock.doctype.item.test_item import make_item
+
+test_dependencies = ["Item"]
+
+class TestVariantSelector(FrappeTestCase):
+
+	@classmethod
+	def setUpClass(cls):
+		template_item = make_item("Test-Tshirt-Temp", {
+			"has_variant": 1,
+			"variant_based_on": "Item Attribute",
+			"attributes": [
+				{"attribute": "Test Size"},
+				{"attribute": "Test Colour"}
+			]
+		})
+
+		# create L-R, L-G, M-R, M-G and S-R
+		for size in ("Large", "Medium",):
+			for colour in ("Red", "Green",):
+				variant = create_variant("Test-Tshirt-Temp", {
+					"Test Size": size, "Test Colour": colour
+				})
+				variant.save()
+
+		variant = create_variant("Test-Tshirt-Temp", {
+			"Test Size": "Small", "Test Colour": "Red"
+		})
+		variant.save()
+
+		make_website_item(template_item) # publish template not variants
+
+	def test_item_attributes(self):
+		"""
+			Test if the right attributes are fetched in the popup.
+			(Attributes must only come from active items)
+
+			Attribute selection must not be linked to Website Items.
+		"""
+		from erpnext.e_commerce.variant_selector.utils import get_attributes_and_values
+
+		attr_data = get_attributes_and_values("Test-Tshirt-Temp")
+
+		self.assertEqual(attr_data[0]["attribute"], "Test Size")
+		self.assertEqual(attr_data[1]["attribute"], "Test Colour")
+		self.assertEqual(len(attr_data[0]["values"]), 3) # ['Small', 'Medium', 'Large']
+		self.assertEqual(len(attr_data[1]["values"]), 2) # ['Red', 'Green']
+
+		# disable small red tshirt, now there are no small tshirts.
+		# but there are some red tshirts
+		small_variant = frappe.get_doc("Item", "Test-Tshirt-Temp-S-R")
+		small_variant.disabled = 1
+		small_variant.save() # trigger cache rebuild
+
+		attr_data = get_attributes_and_values("Test-Tshirt-Temp")
+
+		# Only L and M attribute values must be fetched since S is disabled
+		self.assertEqual(len(attr_data[0]["values"]), 2)  # ['Medium', 'Large']
+
+		# teardown
+		small_variant.disabled = 0
+		small_variant.save()
+
+	def test_next_item_variant_values(self):
+		"""
+			Test if on selecting an attribute value, the next possible values
+			are filtered accordingly.
+			Values that dont apply should not be fetched.
+			E.g.
+			There is a ** Small-Red ** Tshirt. No other colour in this size.
+			On selecting ** Small **, only ** Red ** should be selectable next.
+		"""
+		next_values = get_next_attribute_and_values("Test-Tshirt-Temp", selected_attributes={"Test Size": "Small"})
+		next_colours = next_values["valid_options_for_attributes"]["Test Colour"]
+		filtered_items = next_values["filtered_items"]
+
+		self.assertEqual(len(next_colours), 1)
+		self.assertEqual(next_colours.pop(), "Red")
+		self.assertEqual(len(filtered_items), 1)
+		self.assertEqual(filtered_items.pop(), "Test-Tshirt-Temp-S-R")
+
+	def test_exact_match_with_price(self):
+		"""
+			Test price fetching and matching of variant without Website Item
+		"""
+		from erpnext.e_commerce.doctype.website_item.test_website_item import make_web_item_price
+
+		frappe.set_user("Administrator")
+		setup_e_commerce_settings({
+			"company": "_Test Company",
+			"enabled": 1,
+			"default_customer_group": "_Test Customer Group",
+			"price_list": "_Test Price List India",
+			"show_price": 1
+		})
+
+		make_web_item_price(item_code="Test-Tshirt-Temp-S-R", price_list_rate=100)
+
+		frappe.local.shopping_cart_settings = None # clear cached settings values
+		next_values = get_next_attribute_and_values(
+			"Test-Tshirt-Temp",
+			selected_attributes={"Test Size": "Small", "Test Colour": "Red"}
+		)
+		print(">>>>", next_values)
+		price_info = next_values["product_info"]["price"]
+
+		self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
+		self.assertEqual(next_values["exact_match"][0],"Test-Tshirt-Temp-S-R")
+		self.assertEqual(price_info["price_list_rate"], 100.0)
+		self.assertEqual(price_info["formatted_price_sales_uom"], "₹ 100.00")
diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py
new file mode 100644
index 0000000..3380273
--- /dev/null
+++ b/erpnext/e_commerce/variant_selector/utils.py
@@ -0,0 +1,218 @@
+import frappe
+from frappe.utils import cint
+
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
+	get_shopping_cart_settings,
+)
+from erpnext.e_commerce.shopping_cart.cart import _set_price_list
+from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
+from erpnext.utilities.product import get_price
+
+
+def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
+	items = []
+
+	for attribute, values in attribute_filters.items():
+		attribute_values = values
+
+		if not isinstance(attribute_values, list):
+			attribute_values = [attribute_values]
+
+		if not attribute_values:
+			continue
+
+		wheres = []
+		query_values = []
+		for attribute_value in attribute_values:
+			wheres.append('( attribute = %s and attribute_value = %s )')
+			query_values += [attribute, attribute_value]
+
+		attribute_query = ' or '.join(wheres)
+
+		if template_item_code:
+			variant_of_query = 'AND t2.variant_of = %s'
+			query_values.append(template_item_code)
+		else:
+			variant_of_query = ''
+
+		query = '''
+			SELECT
+				t1.parent
+			FROM
+				`tabItem Variant Attribute` t1
+			WHERE
+				1 = 1
+				AND (
+					{attribute_query}
+				)
+				AND EXISTS (
+					SELECT
+						1
+					FROM
+						`tabItem` t2
+					WHERE
+						t2.name = t1.parent
+						{variant_of_query}
+				)
+			GROUP BY
+				t1.parent
+			ORDER BY
+				NULL
+		'''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
+
+		item_codes = set([r[0] for r in frappe.db.sql(query, query_values)]) # nosemgrep
+		items.append(item_codes)
+
+	res = list(set.intersection(*items))
+
+	return res
+
+@frappe.whitelist(allow_guest=True)
+def get_attributes_and_values(item_code):
+	'''Build a list of attributes and their possible values.
+	This will ignore the values upon selection of which there cannot exist one item.
+	'''
+	item_cache = ItemVariantsCacheManager(item_code)
+	item_variants_data = item_cache.get_item_variants_data()
+
+	attributes = get_item_attributes(item_code)
+	attribute_list = [a.attribute for a in attributes]
+
+	valid_options = {}
+	for item_code, attribute, attribute_value in item_variants_data:
+		if attribute in attribute_list:
+			valid_options.setdefault(attribute, set()).add(attribute_value)
+
+	item_attribute_values = frappe.db.get_all('Item Attribute Value',
+		['parent', 'attribute_value', 'idx'], order_by='parent asc, idx asc')
+	ordered_attribute_value_map = frappe._dict()
+	for iv in item_attribute_values:
+		ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value)
+
+	# build attribute values in idx order
+	for attr in attributes:
+		valid_attribute_values = valid_options.get(attr.attribute, [])
+		ordered_values = ordered_attribute_value_map.get(attr.attribute, [])
+		attr['values'] = [v for v in ordered_values if v in valid_attribute_values]
+
+	return attributes
+
+
+@frappe.whitelist(allow_guest=True)
+def get_next_attribute_and_values(item_code, selected_attributes):
+	'''Find the count of Items that match the selected attributes.
+	Also, find the attribute values that are not applicable for further searching.
+	If less than equal to 10 items are found, return item_codes of those items.
+	If one item is matched exactly, return item_code of that item.
+	'''
+	selected_attributes = frappe.parse_json(selected_attributes)
+
+	item_cache = ItemVariantsCacheManager(item_code)
+	item_variants_data = item_cache.get_item_variants_data()
+
+	attributes = get_item_attributes(item_code)
+	attribute_list = [a.attribute for a in attributes]
+	filtered_items = get_items_with_selected_attributes(item_code, selected_attributes)
+
+	next_attribute = None
+
+	for attribute in attribute_list:
+		if attribute not in selected_attributes:
+			next_attribute = attribute
+			break
+
+	valid_options_for_attributes = frappe._dict()
+
+	for a in attribute_list:
+		valid_options_for_attributes[a] = set()
+
+		selected_attribute = selected_attributes.get(a, None)
+		if selected_attribute:
+			# already selected attribute values are valid options
+			valid_options_for_attributes[a].add(selected_attribute)
+
+	for row in item_variants_data:
+		item_code, attribute, attribute_value = row
+		if item_code in filtered_items and attribute not in selected_attributes and attribute in attribute_list:
+			valid_options_for_attributes[attribute].add(attribute_value)
+
+	optional_attributes = item_cache.get_optional_attributes()
+	exact_match = []
+	# search for exact match if all selected attributes are required attributes
+	if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
+		item_attribute_value_map = item_cache.get_item_attribute_value_map()
+		for item_code, attr_dict in item_attribute_value_map.items():
+			if item_code in filtered_items and set(attr_dict.keys()) == set(selected_attributes.keys()):
+				exact_match.append(item_code)
+
+	filtered_items_count = len(filtered_items)
+
+	# get product info if exact match
+	# from erpnext.e_commerce.shopping_cart.product_info import get_product_info_for_website
+	if exact_match:
+		cart_settings = get_shopping_cart_settings()
+		product_info = get_item_variant_price_dict(exact_match[0], cart_settings)
+
+		if product_info:
+			product_info["allow_items_not_in_stock"] = cint(cart_settings.allow_items_not_in_stock)
+	else:
+		product_info = None
+
+	return {
+		'next_attribute': next_attribute,
+		'valid_options_for_attributes': valid_options_for_attributes,
+		'filtered_items_count': filtered_items_count,
+		'filtered_items': filtered_items if filtered_items_count < 10 else [],
+		'exact_match': exact_match,
+		'product_info': product_info
+	}
+
+
+def get_items_with_selected_attributes(item_code, selected_attributes):
+	item_cache = ItemVariantsCacheManager(item_code)
+	attribute_value_item_map = item_cache.get_attribute_value_item_map()
+
+	items = []
+	for attribute, value in selected_attributes.items():
+		filtered_items = attribute_value_item_map.get((attribute, value), [])
+		items.append(set(filtered_items))
+
+	return set.intersection(*items)
+
+# utilities
+
+def get_item_attributes(item_code):
+	attributes = frappe.db.get_all('Item Variant Attribute',
+		fields=['attribute'],
+		filters={
+			'parenttype': 'Item',
+			'parent': item_code
+		},
+		order_by='idx asc'
+	)
+
+	optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes()
+
+	for a in attributes:
+		if a.attribute in optional_attributes:
+			a.optional = True
+
+	return attributes
+
+def get_item_variant_price_dict(item_code, cart_settings):
+	if cart_settings.enabled and cart_settings.show_price:
+		is_guest = frappe.session.user == "Guest"
+		# Show Price if logged in.
+		# If not logged in, check if price is hidden for guest.
+		if not is_guest or not cart_settings.hide_price_for_guest:
+			price_list = _set_price_list(cart_settings, None)
+			price = get_price(
+				item_code,
+				price_list,
+				cart_settings.default_customer_group,
+				cart_settings.company
+			)
+			return {"price": price}
+
+	return None
+
diff --git a/erpnext/demo/__init__.py b/erpnext/e_commerce/web_template/__init__.py
similarity index 100%
copy from erpnext/demo/__init__.py
copy to erpnext/e_commerce/web_template/__init__.py
diff --git a/erpnext/shopping_cart/web_template/hero_slider/__init__.py b/erpnext/e_commerce/web_template/hero_slider/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/hero_slider/__init__.py
rename to erpnext/e_commerce/web_template/hero_slider/__init__.py
diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.html b/erpnext/e_commerce/web_template/hero_slider/hero_slider.html
similarity index 100%
rename from erpnext/shopping_cart/web_template/hero_slider/hero_slider.html
rename to erpnext/e_commerce/web_template/hero_slider/hero_slider.html
diff --git a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
similarity index 98%
rename from erpnext/shopping_cart/web_template/hero_slider/hero_slider.json
rename to erpnext/e_commerce/web_template/hero_slider/hero_slider.json
index 04fb1d2..2b1807c 100644
--- a/erpnext/shopping_cart/web_template/hero_slider/hero_slider.json
+++ b/erpnext/e_commerce/web_template/hero_slider/hero_slider.json
@@ -1,4 +1,5 @@
 {
+ "__unsaved": 1,
  "creation": "2020-11-17 15:21:51.207221",
  "docstatus": 0,
  "doctype": "Web Template",
@@ -273,9 +274,9 @@
   }
  ],
  "idx": 2,
- "modified": "2020-12-29 12:30:02.794994",
+ "modified": "2021-02-24 15:57:05.889709",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Hero Slider",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/shopping_cart/web_template/item_card_group/__init__.py b/erpnext/e_commerce/web_template/item_card_group/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/item_card_group/__init__.py
rename to erpnext/e_commerce/web_template/item_card_group/__init__.py
diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html
similarity index 81%
rename from erpnext/shopping_cart/web_template/item_card_group/item_card_group.html
rename to erpnext/e_commerce/web_template/item_card_group/item_card_group.html
index fe061d5..07952f0 100644
--- a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.html
+++ b/erpnext/e_commerce/web_template/item_card_group/item_card_group.html
@@ -23,11 +23,10 @@
 		{%- for index in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'] -%}
 		{%- set item = values['card_' + index + '_item'] -%}
 			{%- if item -%}
-				{%- set item = frappe.get_doc("Item", item) -%}
+				{%- set web_item = frappe.get_doc("Website Item", item) -%}
 				{{ item_card(
-					item.item_name, item.image, item.route, item.description,
-					None, item.item_group, values['card_' + index + '_featured'],
-					True, "Center"
+					web_item, is_featured=values['card_' + index + '_featured'],
+					is_full_width=True, align="Center"
 				) }}
 			{%- endif -%}
 		{%- endfor -%}
diff --git a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json b/erpnext/e_commerce/web_template/item_card_group/item_card_group.json
similarity index 84%
rename from erpnext/shopping_cart/web_template/item_card_group/item_card_group.json
rename to erpnext/e_commerce/web_template/item_card_group/item_card_group.json
index ad087b0..ad9e2a7 100644
--- a/erpnext/shopping_cart/web_template/item_card_group/item_card_group.json
+++ b/erpnext/e_commerce/web_template/item_card_group/item_card_group.json
@@ -17,15 +17,12 @@
    "reqd": 0
   },
   {
-   "__unsaved": 1,
    "fieldname": "primary_action_label",
    "fieldtype": "Data",
    "label": "Primary Action Label",
    "reqd": 0
   },
   {
-   "__islocal": 1,
-   "__unsaved": 1,
    "fieldname": "primary_action",
    "fieldtype": "Data",
    "label": "Primary Action",
@@ -40,8 +37,8 @@
   {
    "fieldname": "card_1_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -59,8 +56,8 @@
   {
    "fieldname": "card_2_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -79,8 +76,8 @@
   {
    "fieldname": "card_3_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -98,8 +95,8 @@
   {
    "fieldname": "card_4_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -117,8 +114,8 @@
   {
    "fieldname": "card_5_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -136,8 +133,8 @@
   {
    "fieldname": "card_6_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -155,8 +152,8 @@
   {
    "fieldname": "card_7_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -174,8 +171,8 @@
   {
    "fieldname": "card_8_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -193,8 +190,8 @@
   {
    "fieldname": "card_9_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -212,8 +209,8 @@
   {
    "fieldname": "card_10_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -231,8 +228,8 @@
   {
    "fieldname": "card_11_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -250,8 +247,8 @@
   {
    "fieldname": "card_12_item",
    "fieldtype": "Link",
-   "label": "Item",
-   "options": "Item",
+   "label": "Website Item",
+   "options": "Website Item",
    "reqd": 0
   },
   {
@@ -262,9 +259,9 @@
   }
  ],
  "idx": 0,
- "modified": "2020-11-19 18:48:52.633045",
+ "modified": "2021-12-21 14:44:59.821335",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Item Card Group",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/shopping_cart/web_template/product_card/__init__.py b/erpnext/e_commerce/web_template/product_card/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_card/__init__.py
rename to erpnext/e_commerce/web_template/product_card/__init__.py
diff --git a/erpnext/shopping_cart/web_template/product_card/product_card.html b/erpnext/e_commerce/web_template/product_card/product_card.html
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_card/product_card.html
rename to erpnext/e_commerce/web_template/product_card/product_card.html
diff --git a/erpnext/shopping_cart/web_template/product_card/product_card.json b/erpnext/e_commerce/web_template/product_card/product_card.json
similarity index 82%
rename from erpnext/shopping_cart/web_template/product_card/product_card.json
rename to erpnext/e_commerce/web_template/product_card/product_card.json
index 1059c1b..2eb7374 100644
--- a/erpnext/shopping_cart/web_template/product_card/product_card.json
+++ b/erpnext/e_commerce/web_template/product_card/product_card.json
@@ -5,7 +5,6 @@
  "doctype": "Web Template",
  "fields": [
   {
-   "__unsaved": 1,
    "fieldname": "item",
    "fieldtype": "Link",
    "label": "Item",
@@ -13,7 +12,6 @@
    "reqd": 0
   },
   {
-   "__unsaved": 1,
    "fieldname": "featured",
    "fieldtype": "Check",
    "label": "Featured",
@@ -22,9 +20,9 @@
   }
  ],
  "idx": 0,
- "modified": "2020-11-17 15:33:34.982515",
+ "modified": "2021-02-24 16:05:17.926610",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Product Card",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/shopping_cart/web_template/product_category_cards/__init__.py b/erpnext/e_commerce/web_template/product_category_cards/__init__.py
similarity index 100%
rename from erpnext/shopping_cart/web_template/product_category_cards/__init__.py
rename to erpnext/e_commerce/web_template/product_category_cards/__init__.py
diff --git a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
similarity index 81%
rename from erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html
rename to erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
index 06b76af..6d75a8b 100644
--- a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.html
+++ b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.html
@@ -6,8 +6,15 @@
 }) -%}
 <div class="card h-100">
 	{% if image %}
-	<img class="card-img-top" src="{{ image }}" alt="{{ title }}">
+	<img class="card-img-top" src="{{ image }}" alt="{{ title }}" style="max-height: 200px;">
+	{% else %}
+	<div class="placeholder-div" style="max-height: 200px;">
+		<span class="placeholder">
+			{{ frappe.utils.get_abbr(title or '') }}
+		</span>
+	</div>
 	{% endif %}
+
 	<div class="card-body text-center text-muted small">
 		{{ title or '' }}
 	</div>
diff --git a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json
similarity index 95%
rename from erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json
rename to erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json
index ba5f63b..0202165 100644
--- a/erpnext/shopping_cart/web_template/product_category_cards/product_category_cards.json
+++ b/erpnext/e_commerce/web_template/product_category_cards/product_category_cards.json
@@ -74,9 +74,9 @@
   }
  ],
  "idx": 0,
- "modified": "2020-11-18 17:26:28.726260",
+ "modified": "2021-02-24 16:03:33.835635",
  "modified_by": "Administrator",
- "module": "Shopping Cart",
+ "module": "E-commerce",
  "name": "Product Category Cards",
  "owner": "Administrator",
  "standard": 1,
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index a23d492..4d0f3a9 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -6,6 +6,7 @@
 from frappe import _, msgprint
 from frappe.desk.reportview import get_match_cond
 from frappe.model.document import Document
+from frappe.query_builder.functions import Min
 from frappe.utils import comma_and, get_link_to_form, getdate
 
 
@@ -60,8 +61,15 @@
 			frappe.throw(_("Student is already enrolled."))
 
 	def update_student_joining_date(self):
-		date = frappe.db.sql("select min(enrollment_date) from `tabProgram Enrollment` where student= %s", self.student)
-		frappe.db.set_value("Student", self.student, "joining_date", date)
+		table = frappe.qb.DocType('Program Enrollment')
+		date = (
+			frappe.qb.from_(table)
+				.select(Min(table.enrollment_date).as_('enrollment_date'))
+				.where(table.student == self.student)
+		).run(as_dict=True)
+
+		if date:
+			frappe.db.set_value("Student", self.student, "joining_date", date[0].enrollment_date)
 
 	def make_fee_records(self):
 		from erpnext.education.api import get_fee_components
diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js
index 68e7780..4526585 100644
--- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js
+++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.js
@@ -3,6 +3,10 @@
 frappe.provide("education");
 
 frappe.ui.form.on('Student Attendance Tool', {
+	setup: (frm) => {
+		frm.students_area = $('<div>')
+			.appendTo(frm.fields_dict.students_html.wrapper);
+	},
 	onload: function(frm) {
 		frm.set_query("student_group", function() {
 			return {
@@ -34,6 +38,7 @@
 
 	student_group: function(frm) {
 		if ((frm.doc.student_group && frm.doc.date) || frm.doc.course_schedule) {
+			frm.students_area.find('.student-attendance-checks').html(`<div style='padding: 2rem 0'>Fetching...</div>`);
 			var method = "erpnext.education.doctype.student_attendance_tool.student_attendance_tool.get_student_attendance_records";
 
 			frappe.call({
@@ -62,10 +67,6 @@
 	},
 
 	get_students: function(frm, students) {
-		if (!frm.students_area) {
-			frm.students_area = $('<div>')
-				.appendTo(frm.fields_dict.students_html.wrapper);
-		}
 		students = students || [];
 		frm.students_editor = new education.StudentsEditor(frm, frm.students_area, students);
 	}
@@ -163,16 +164,26 @@
 				);
 			});
 
-		var htmls = students.map(function(student) {
-			return frappe.render_template("student_button", {
-				student: student.student,
-				student_name: student.student_name,
-				group_roll_number: student.group_roll_number,
-				status: student.status
-			})
-		});
+		// make html grid of students
+		let student_html = '';
+		for (let student of students) {
+			student_html += `<div class="col-sm-3">
+					<div class="checkbox">
+						<label>
+							<input
+								type="checkbox"
+								data-group_roll_number="${student.group_roll_number}"
+								data-student="${student.student}"
+								data-student-name="${student.student_name}"
+								class="students-check"
+								${student.status==='Present' ? 'checked' : ''}>
+							${student.group_roll_number} - ${student.student_name}
+						</label>
+					</div>
+				</div>`;
+		}
 
-		$(htmls.join("")).appendTo(me.wrapper);
+		$(`<div class='student-attendance-checks'>${student_html}</div>`).appendTo(me.wrapper);
 	}
 
 	show_empty_state() {
diff --git a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py
index 7deb6b1..92bb20c 100644
--- a/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py
+++ b/erpnext/education/doctype/student_attendance_tool/student_attendance_tool.py
@@ -24,24 +24,24 @@
 		student_list = frappe.get_all("Student Group Student", fields=["student", "student_name", "group_roll_number"],
 			filters={"parent": student_group, "active": 1}, order_by= "group_roll_number")
 
-	table = frappe.qb.DocType("Student Attendance")
+	StudentAttendance = frappe.qb.DocType("Student Attendance")
 
 	if course_schedule:
 		student_attendance_list = (
-			frappe.qb.from_(table)
-				.select(table.student, table.status)
+			frappe.qb.from_(StudentAttendance)
+				.select(StudentAttendance.student, StudentAttendance.status)
 				.where(
-					(table.course_schedule == course_schedule)
+					(StudentAttendance.course_schedule == course_schedule)
 				)
 			).run(as_dict=True)
 	else:
 		student_attendance_list = (
-			frappe.qb.from_(table)
-				.select(table.student, table.status)
+			frappe.qb.from_(StudentAttendance)
+				.select(StudentAttendance.student, StudentAttendance.status)
 				.where(
-					(table.student_group == student_group)
-					& (table.date == date)
-					& (table.course_schedule == "") | (table.course_schedule.isnull())
+					(StudentAttendance.student_group == student_group)
+					& (StudentAttendance.date == date)
+					& ((StudentAttendance.course_schedule == "") | (StudentAttendance.course_schedule.isnull()))
 				)
 			).run(as_dict=True)
 
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/__init__.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
deleted file mode 100644
index 66826ba..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_methods.py
+++ /dev/null
@@ -1,525 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies and contributors
-# For license information, please see license.txt
-
-
-import csv
-import math
-import time
-
-import dateutil
-import frappe
-from frappe import _
-from six import StringIO
-
-import erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_api as mws
-
-
-#Get and Create Products
-def get_products_details():
-	products = get_products_instance()
-	reports = get_reports_instance()
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	market_place_list = return_as_list(mws_settings.market_place_id)
-
-	for marketplace in market_place_list:
-		report_id = request_and_fetch_report_id("_GET_FLAT_FILE_OPEN_LISTINGS_DATA_", None, None, market_place_list)
-
-		if report_id:
-			listings_response = reports.get_report(report_id=report_id)
-
-			#Get ASIN Codes
-			string_io = StringIO(frappe.safe_decode(listings_response.original))
-			csv_rows = list(csv.reader(string_io, delimiter='\t'))
-			asin_list = list(set([row[1] for row in csv_rows[1:]]))
-			#break into chunks of 10
-			asin_chunked_list = list(chunks(asin_list, 10))
-
-			#Map ASIN Codes to SKUs
-			sku_asin = [{"asin":row[1],"sku":row[0]} for row in csv_rows[1:]]
-
-			#Fetch Products List from ASIN
-			for asin_list in asin_chunked_list:
-				products_response = call_mws_method(products.get_matching_product,marketplaceid=marketplace,
-					asins=asin_list)
-
-				matching_products_list = products_response.parsed
-				for product in matching_products_list:
-					skus = [row["sku"] for row in sku_asin if row["asin"]==product.ASIN]
-					for sku in skus:
-						create_item_code(product, sku)
-
-def get_products_instance():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	products = mws.Products(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region = mws_settings.region,
-			domain = mws_settings.domain
-			)
-
-	return products
-
-def get_reports_instance():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	reports = mws.Reports(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region = mws_settings.region,
-			domain = mws_settings.domain
-	)
-
-	return reports
-
-#returns list as expected by amazon API
-def return_as_list(input_value):
-	if isinstance(input_value, list):
-		return input_value
-	else:
-		return [input_value]
-
-#function to chunk product data
-def chunks(l, n):
-	for i in range(0, len(l), n):
-		yield l[i:i+n]
-
-def request_and_fetch_report_id(report_type, start_date=None, end_date=None, marketplaceids=None):
-	reports = get_reports_instance()
-	report_response = reports.request_report(report_type=report_type,
-			start_date=start_date,
-			end_date=end_date,
-			marketplaceids=marketplaceids)
-
-	report_request_id = report_response.parsed["ReportRequestInfo"]["ReportRequestId"]["value"]
-	generated_report_id = None
-	#poll to get generated report
-	for x in range(1,10):
-		report_request_list_response = reports.get_report_request_list(requestids=[report_request_id])
-		report_status = report_request_list_response.parsed["ReportRequestInfo"]["ReportProcessingStatus"]["value"]
-
-		if report_status == "_SUBMITTED_" or report_status == "_IN_PROGRESS_":
-			#add time delay to wait for amazon to generate report
-			time.sleep(15)
-			continue
-		elif report_status == "_CANCELLED_":
-			break
-		elif report_status == "_DONE_NO_DATA_":
-			break
-		elif report_status == "_DONE_":
-			generated_report_id =  report_request_list_response.parsed["ReportRequestInfo"]["GeneratedReportId"]["value"]
-			break
-	return generated_report_id
-
-def call_mws_method(mws_method, *args, **kwargs):
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	max_retries = mws_settings.max_retry_limit
-
-	for x in range(0, max_retries):
-		try:
-			response = mws_method(*args, **kwargs)
-			return response
-		except Exception as e:
-			delay = math.pow(4, x) * 125
-			frappe.log_error(message=e, title=f'Method "{mws_method.__name__}" failed')
-			time.sleep(delay)
-			continue
-
-	mws_settings.enable_sync = 0
-	mws_settings.save()
-
-	frappe.throw(_("Sync has been temporarily disabled because maximum retries have been exceeded"))
-
-def create_item_code(amazon_item_json, sku):
-	if frappe.db.get_value("Item", sku):
-		return
-
-	item = frappe.new_doc("Item")
-
-	new_manufacturer = create_manufacturer(amazon_item_json)
-	new_brand = create_brand(amazon_item_json)
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-
-	item.item_code = sku
-	item.amazon_item_code = amazon_item_json.ASIN
-	item.item_group = mws_settings.item_group
-	item.description = amazon_item_json.Product.AttributeSets.ItemAttributes.Title
-	item.brand = new_brand
-	item.manufacturer = new_manufacturer
-	item.web_long_description = amazon_item_json.Product.AttributeSets.ItemAttributes.Title
-
-	item.image = amazon_item_json.Product.AttributeSets.ItemAttributes.SmallImage.URL
-
-	temp_item_group = amazon_item_json.Product.AttributeSets.ItemAttributes.ProductGroup
-
-	item_group = frappe.db.get_value("Item Group",filters={"item_group_name": temp_item_group})
-
-	if not item_group:
-		igroup = frappe.new_doc("Item Group")
-		igroup.item_group_name = temp_item_group
-		igroup.parent_item_group =  mws_settings.item_group
-		igroup.insert()
-
-	item.append("item_defaults", {'company':mws_settings.company})
-
-	item.insert(ignore_permissions=True)
-	create_item_price(amazon_item_json, item.item_code)
-
-	return item.name
-
-def create_manufacturer(amazon_item_json):
-	if not amazon_item_json.Product.AttributeSets.ItemAttributes.Manufacturer:
-		return None
-
-	existing_manufacturer = frappe.db.get_value("Manufacturer",
-		filters={"short_name":amazon_item_json.Product.AttributeSets.ItemAttributes.Manufacturer})
-
-	if not existing_manufacturer:
-		manufacturer = frappe.new_doc("Manufacturer")
-		manufacturer.short_name = amazon_item_json.Product.AttributeSets.ItemAttributes.Manufacturer
-		manufacturer.insert()
-		return manufacturer.short_name
-	else:
-		return existing_manufacturer
-
-def create_brand(amazon_item_json):
-	if not amazon_item_json.Product.AttributeSets.ItemAttributes.Brand:
-		return None
-
-	existing_brand = frappe.db.get_value("Brand",
-		filters={"brand":amazon_item_json.Product.AttributeSets.ItemAttributes.Brand})
-	if not existing_brand:
-		brand = frappe.new_doc("Brand")
-		brand.brand = amazon_item_json.Product.AttributeSets.ItemAttributes.Brand
-		brand.insert()
-		return brand.brand
-	else:
-		return existing_brand
-
-def create_item_price(amazon_item_json, item_code):
-	item_price = frappe.new_doc("Item Price")
-	item_price.price_list = frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "price_list")
-	if not("ListPrice" in amazon_item_json.Product.AttributeSets.ItemAttributes):
-		item_price.price_list_rate = 0
-	else:
-		item_price.price_list_rate = amazon_item_json.Product.AttributeSets.ItemAttributes.ListPrice.Amount
-
-	item_price.item_code = item_code
-	item_price.insert()
-
-#Get and create Orders
-def get_orders(after_date):
-	try:
-		orders = get_orders_instance()
-		statuses = ["PartiallyShipped", "Unshipped", "Shipped", "Canceled"]
-		mws_settings = frappe.get_doc("Amazon MWS Settings")
-		market_place_list = return_as_list(mws_settings.market_place_id)
-
-		orders_response = call_mws_method(orders.list_orders, marketplaceids=market_place_list,
-			fulfillment_channels=["MFN", "AFN"],
-			lastupdatedafter=after_date,
-			orderstatus=statuses,
-			max_results='50')
-
-		while True:
-			orders_list = []
-
-			if "Order" in orders_response.parsed.Orders:
-				orders_list = return_as_list(orders_response.parsed.Orders.Order)
-
-			if len(orders_list) == 0:
-				break
-
-			for order in orders_list:
-				create_sales_order(order, after_date)
-
-			if not "NextToken" in orders_response.parsed:
-				break
-
-			next_token = orders_response.parsed.NextToken
-			orders_response = call_mws_method(orders.list_orders_by_next_token, next_token)
-
-	except Exception as e:
-		frappe.log_error(title="get_orders", message=e)
-
-def get_orders_instance():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	orders = mws.Orders(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region= mws_settings.region,
-			domain= mws_settings.domain,
-			version="2013-09-01"
-		)
-
-	return orders
-
-def create_sales_order(order_json,after_date):
-	customer_name = create_customer(order_json)
-	create_address(order_json, customer_name)
-
-	market_place_order_id = order_json.AmazonOrderId
-
-	so = frappe.db.get_value("Sales Order",
-			filters={"amazon_order_id": market_place_order_id},
-			fieldname="name")
-
-	taxes_and_charges = frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "taxes_charges")
-
-	if so:
-		return
-
-	if not so:
-		items = get_order_items(market_place_order_id)
-		delivery_date = dateutil.parser.parse(order_json.LatestShipDate).strftime("%Y-%m-%d")
-		transaction_date = dateutil.parser.parse(order_json.PurchaseDate).strftime("%Y-%m-%d")
-
-		so = frappe.get_doc({
-				"doctype": "Sales Order",
-				"naming_series": "SO-",
-				"amazon_order_id": market_place_order_id,
-				"marketplace_id": order_json.MarketplaceId,
-				"customer": customer_name,
-				"delivery_date": delivery_date,
-				"transaction_date": transaction_date,
-				"items": items,
-				"company": frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "company")
-			})
-
-		try:
-			if taxes_and_charges:
-				charges_and_fees = get_charges_and_fees(market_place_order_id)
-				for charge in charges_and_fees.get("charges"):
-					so.append('taxes', charge)
-
-				for fee in charges_and_fees.get("fees"):
-					so.append('taxes', fee)
-
-			so.insert(ignore_permissions=True)
-			so.submit()
-
-		except Exception as e:
-			import traceback
-			frappe.log_error(message=traceback.format_exc(), title="Create Sales Order")
-
-def create_customer(order_json):
-	order_customer_name = ""
-
-	if not("BuyerName" in order_json):
-		order_customer_name = "Buyer - " + order_json.AmazonOrderId
-	else:
-		order_customer_name = order_json.BuyerName
-
-	existing_customer_name = frappe.db.get_value("Customer",
-			filters={"name": order_customer_name}, fieldname="name")
-
-	if existing_customer_name:
-		filters = [
-				["Dynamic Link", "link_doctype", "=", "Customer"],
-				["Dynamic Link", "link_name", "=", existing_customer_name],
-				["Dynamic Link", "parenttype", "=", "Contact"]
-			]
-
-		existing_contacts = frappe.get_list("Contact", filters)
-
-		if existing_contacts:
-			pass
-		else:
-			new_contact = frappe.new_doc("Contact")
-			new_contact.first_name = order_customer_name
-			new_contact.append('links', {
-				"link_doctype": "Customer",
-				"link_name": existing_customer_name
-			})
-			new_contact.insert()
-
-		return existing_customer_name
-	else:
-		mws_customer_settings = frappe.get_doc("Amazon MWS Settings")
-		new_customer = frappe.new_doc("Customer")
-		new_customer.customer_name = order_customer_name
-		new_customer.customer_group = mws_customer_settings.customer_group
-		new_customer.territory = mws_customer_settings.territory
-		new_customer.customer_type = mws_customer_settings.customer_type
-		new_customer.save()
-
-		new_contact = frappe.new_doc("Contact")
-		new_contact.first_name = order_customer_name
-		new_contact.append('links', {
-			"link_doctype": "Customer",
-			"link_name": new_customer.name
-		})
-
-		new_contact.insert()
-
-		return new_customer.name
-
-def create_address(amazon_order_item_json, customer_name):
-
-	filters = [
-			["Dynamic Link", "link_doctype", "=", "Customer"],
-			["Dynamic Link", "link_name", "=", customer_name],
-			["Dynamic Link", "parenttype", "=", "Address"]
-		]
-
-	existing_address = frappe.get_list("Address", filters)
-
-	if not("ShippingAddress" in amazon_order_item_json):
-		return None
-	else:
-		make_address = frappe.new_doc("Address")
-
-		if "AddressLine1" in amazon_order_item_json.ShippingAddress:
-			make_address.address_line1 = amazon_order_item_json.ShippingAddress.AddressLine1
-		else:
-			make_address.address_line1 = "Not Provided"
-
-		if "City" in amazon_order_item_json.ShippingAddress:
-			make_address.city = amazon_order_item_json.ShippingAddress.City
-		else:
-			make_address.city = "Not Provided"
-
-		if "StateOrRegion" in amazon_order_item_json.ShippingAddress:
-			make_address.state = amazon_order_item_json.ShippingAddress.StateOrRegion
-
-		if "PostalCode" in amazon_order_item_json.ShippingAddress:
-			make_address.pincode = amazon_order_item_json.ShippingAddress.PostalCode
-
-		for address in existing_address:
-			address_doc = frappe.get_doc("Address", address["name"])
-			if (address_doc.address_line1 == make_address.address_line1 and
-				address_doc.pincode == make_address.pincode):
-				return address
-
-		make_address.append("links", {
-			"link_doctype": "Customer",
-			"link_name": customer_name
-		})
-		make_address.address_type = "Shipping"
-		make_address.insert()
-
-def get_order_items(market_place_order_id):
-	mws_orders = get_orders_instance()
-
-	order_items_response = call_mws_method(mws_orders.list_order_items, amazon_order_id=market_place_order_id)
-	final_order_items = []
-
-	order_items_list = return_as_list(order_items_response.parsed.OrderItems.OrderItem)
-
-	warehouse = frappe.db.get_value("Amazon MWS Settings", "Amazon MWS Settings", "warehouse")
-
-	while True:
-		for order_item in order_items_list:
-
-			if not "ItemPrice" in order_item:
-				price = 0
-			else:
-				price = order_item.ItemPrice.Amount
-
-			final_order_items.append({
-				"item_code": get_item_code(order_item),
-				"item_name": order_item.SellerSKU,
-				"description": order_item.Title,
-				"rate": price,
-				"qty": order_item.QuantityOrdered,
-				"stock_uom": "Nos",
-				"warehouse": warehouse,
-				"conversion_factor": "1.0"
-			})
-
-		if not "NextToken" in order_items_response.parsed:
-			break
-
-		next_token = order_items_response.parsed.NextToken
-
-		order_items_response = call_mws_method(mws_orders.list_order_items_by_next_token, next_token)
-		order_items_list = return_as_list(order_items_response.parsed.OrderItems.OrderItem)
-
-	return final_order_items
-
-def get_item_code(order_item):
-	sku = order_item.SellerSKU
-	item_code = frappe.db.get_value("Item", {"item_code": sku}, "item_code")
-	if item_code:
-		return item_code
-
-def get_charges_and_fees(market_place_order_id):
-	finances = get_finances_instance()
-
-	charges_fees = {"charges":[], "fees":[]}
-
-	response = call_mws_method(finances.list_financial_events, amazon_order_id=market_place_order_id)
-
-	shipment_event_list = return_as_list(response.parsed.FinancialEvents.ShipmentEventList)
-
-	for shipment_event in shipment_event_list:
-		if shipment_event:
-			shipment_item_list = return_as_list(shipment_event.ShipmentEvent.ShipmentItemList.ShipmentItem)
-
-			for shipment_item in shipment_item_list:
-				charges, fees = [], []
-
-				if 'ItemChargeList' in shipment_item.keys():
-					charges = return_as_list(shipment_item.ItemChargeList.ChargeComponent)
-
-				if 'ItemFeeList' in shipment_item.keys():
-					fees = return_as_list(shipment_item.ItemFeeList.FeeComponent)
-
-				for charge in charges:
-					if(charge.ChargeType != "Principal") and float(charge.ChargeAmount.CurrencyAmount) != 0:
-						charge_account = get_account(charge.ChargeType)
-						charges_fees.get("charges").append({
-							"charge_type":"Actual",
-							"account_head": charge_account,
-							"tax_amount": charge.ChargeAmount.CurrencyAmount,
-							"description": charge.ChargeType + " for " + shipment_item.SellerSKU
-							})
-
-				for fee in fees:
-					if float(fee.FeeAmount.CurrencyAmount) != 0:
-						fee_account = get_account(fee.FeeType)
-						charges_fees.get("fees").append({
-							"charge_type":"Actual",
-							"account_head": fee_account,
-							"tax_amount": fee.FeeAmount.CurrencyAmount,
-							"description": fee.FeeType + " for " + shipment_item.SellerSKU
-							})
-
-	return charges_fees
-
-def get_finances_instance():
-
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-
-	finances = mws.Finances(
-			account_id = mws_settings.seller_id,
-			access_key = mws_settings.aws_access_key_id,
-			secret_key = mws_settings.secret_key,
-			region= mws_settings.region,
-			domain= mws_settings.domain,
-			version="2015-05-01"
-		)
-
-	return finances
-
-def get_account(name):
-	existing_account = frappe.db.get_value("Account", {"account_name": "Amazon {0}".format(name)})
-	account_name = existing_account
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-
-	if not existing_account:
-		try:
-			new_account = frappe.new_doc("Account")
-			new_account.account_name = "Amazon {0}".format(name)
-			new_account.company = mws_settings.company
-			new_account.parent_account = mws_settings.market_place_account_group
-			new_account.insert(ignore_permissions=True)
-			account_name = new_account.name
-		except Exception as e:
-			frappe.log_error(message=e, title="Create Account")
-
-	return account_name
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
deleted file mode 100755
index 4caf137..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_api.py
+++ /dev/null
@@ -1,651 +0,0 @@
-#!/usr/bin/env python
-#
-# Basic interface to Amazon MWS
-# Based on http://code.google.com/p/amazon-mws-python
-# Extended to include finances object
-
-import base64
-import hashlib
-import hmac
-import re
-from urllib.parse import quote
-
-from erpnext.erpnext_integrations.doctype.amazon_mws_settings import xml_utils
-
-try:
-	from xml.etree.ElementTree import ParseError as XMLError
-except ImportError:
-	from xml.parsers.expat import ExpatError as XMLError
-
-from time import gmtime, strftime
-
-from requests import request
-from requests.exceptions import HTTPError
-
-__all__ = [
-	'Feeds',
-	'Inventory',
-	'MWSError',
-	'Reports',
-	'Orders',
-	'Products',
-	'Recommendations',
-	'Sellers',
-	'Finances'
-]
-
-# See https://images-na.ssl-images-amazon.com/images/G/01/mwsportal/doc/en_US/bde/MWSDeveloperGuide._V357736853_.pdf page 8
-# for a list of the end points and marketplace IDs
-
-MARKETPLACES = {
-	"CA": "https://mws.amazonservices.ca", #A2EUQ1WTGCTBG2
-	"US": "https://mws.amazonservices.com", #ATVPDKIKX0DER",
-	"DE": "https://mws-eu.amazonservices.com", #A1PA6795UKMFR9
-	"ES": "https://mws-eu.amazonservices.com", #A1RKKUPIHCS9HS
-	"FR": "https://mws-eu.amazonservices.com", #A13V1IB3VIYZZH
-	"IN": "https://mws.amazonservices.in", #A21TJRUUN4KGV
-	"IT": "https://mws-eu.amazonservices.com", #APJ6JRA9NG5V4
-	"UK": "https://mws-eu.amazonservices.com", #A1F83G8C2ARO7P
-	"JP": "https://mws.amazonservices.jp", #A1VC38T7YXB528
-	"CN": "https://mws.amazonservices.com.cn", #AAHKV2X7AFYLW
-	"AE": "	https://mws.amazonservices.ae", #A2VIGQ35RCS4UG
-	"MX": "https://mws.amazonservices.com.mx", #A1AM78C64UM0Y8
-	"BR": "https://mws.amazonservices.com", #A2Q3Y263D00KWC
-}
-
-
-class MWSError(Exception):
-	"""
-		Main MWS Exception class
-	"""
-	# Allows quick access to the response object.
-	# Do not rely on this attribute, always check if its not None.
-	response = None
-
-def calc_md5(string):
-	"""Calculates the MD5 encryption for the given string
-	"""
-	md = hashlib.md5()
-	md.update(string)
-	return base64.encodebytes(md.digest()).decode().strip()
-
-
-
-def remove_empty(d):
-	"""
-		Helper function that removes all keys from a dictionary (d),
-	that have an empty value.
-	"""
-	for key in list(d):
-		if not d[key]:
-			del d[key]
-	return d
-
-def remove_namespace(xml):
-	xml = xml.decode('utf-8')
-	regex = re.compile(' xmlns(:ns2)?="[^"]+"|(ns2:)|(xml:)')
-	return regex.sub('', xml)
-
-class DictWrapper(object):
-	def __init__(self, xml, rootkey=None):
-		self.original = xml
-		self._rootkey = rootkey
-		self._mydict = xml_utils.xml2dict().fromstring(remove_namespace(xml))
-		self._response_dict = self._mydict.get(list(self._mydict)[0], self._mydict)
-
-	@property
-	def parsed(self):
-		if self._rootkey:
-			return self._response_dict.get(self._rootkey)
-		else:
-			return self._response_dict
-
-class DataWrapper(object):
-	"""
-		Text wrapper in charge of validating the hash sent by Amazon.
-	"""
-	def __init__(self, data, header):
-		self.original = data
-		if 'content-md5' in header:
-			hash_ = calc_md5(self.original)
-			if header['content-md5'] != hash_:
-				raise MWSError("Wrong Contentlength, maybe amazon error...")
-
-	@property
-	def parsed(self):
-		return self.original
-
-class MWS(object):
-	""" Base Amazon API class """
-
-	# This is used to post/get to the different uris used by amazon per api
-	# ie. /Orders/2011-01-01
-	# All subclasses must define their own URI only if needed
-	URI = "/"
-
-	# The API version varies in most amazon APIs
-	VERSION = "2009-01-01"
-
-	# There seem to be some xml namespace issues. therefore every api subclass
-	# is recommended to define its namespace, so that it can be referenced
-	# like so AmazonAPISubclass.NS.
-	# For more information see http://stackoverflow.com/a/8719461/389453
-	NS = ''
-
-	# Some APIs are available only to either a "Merchant" or "Seller"
-	# the type of account needs to be sent in every call to the amazon MWS.
-	# This constant defines the exact name of the parameter Amazon expects
-	# for the specific API being used.
-	# All subclasses need to define this if they require another account type
-	# like "Merchant" in which case you define it like so.
-	# ACCOUNT_TYPE = "Merchant"
-	# Which is the name of the parameter for that specific account type.
-	ACCOUNT_TYPE = "SellerId"
-
-	def __init__(self, access_key, secret_key, account_id, region='US', domain='', uri="", version=""):
-		self.access_key = access_key
-		self.secret_key = secret_key
-		self.account_id = account_id
-		self.version = version or self.VERSION
-		self.uri = uri or self.URI
-
-		if domain:
-			self.domain = domain
-		elif region in MARKETPLACES:
-			self.domain = MARKETPLACES[region]
-		else:
-			error_msg = "Incorrect region supplied ('%(region)s'). Must be one of the following: %(marketplaces)s" % {
-				"marketplaces" : ', '.join(MARKETPLACES.keys()),
-				"region" : region,
-			}
-			raise MWSError(error_msg)
-
-	def make_request(self, extra_data, method="GET", **kwargs):
-		"""Make request to Amazon MWS API with these parameters
-		"""
-
-		# Remove all keys with an empty value because
-		# Amazon's MWS does not allow such a thing.
-		extra_data = remove_empty(extra_data)
-
-		params = {
-			'AWSAccessKeyId': self.access_key,
-			self.ACCOUNT_TYPE: self.account_id,
-			'SignatureVersion': '2',
-			'Timestamp': self.get_timestamp(),
-			'Version': self.version,
-			'SignatureMethod': 'HmacSHA256',
-		}
-		params.update(extra_data)
-		request_description = '&'.join(['%s=%s' % (k, quote(params[k], safe='-_.~')) for k in sorted(params)])
-		signature = self.calc_signature(method, request_description)
-		url = '%s%s?%s&Signature=%s' % (self.domain, self.uri, request_description, quote(signature))
-		headers = {'User-Agent': 'python-amazon-mws/0.0.1 (Language=Python)'}
-		headers.update(kwargs.get('extra_headers', {}))
-
-		try:
-			# Some might wonder as to why i don't pass the params dict as the params argument to request.
-			# My answer is, here i have to get the url parsed string of params in order to sign it, so
-			# if i pass the params dict as params to request, request will repeat that step because it will need
-			# to convert the dict to a url parsed string, so why do it twice if i can just pass the full url :).
-			response = request(method, url, data=kwargs.get('body', ''), headers=headers)
-			response.raise_for_status()
-			# When retrieving data from the response object,
-			# be aware that response.content returns the content in bytes while response.text calls
-			# response.content and converts it to unicode.
-			data = response.content
-
-			# I do not check the headers to decide which content structure to server simply because sometimes
-			# Amazon's MWS API returns XML error responses with "text/plain" as the Content-Type.
-			try:
-				parsed_response = DictWrapper(data, extra_data.get("Action") + "Result")
-			except XMLError:
-				parsed_response = DataWrapper(data, response.headers)
-
-		except HTTPError as e:
-			error = MWSError(str(e))
-			error.response = e.response
-			raise error
-
-		# Store the response object in the parsed_response for quick access
-		parsed_response.response = response
-		return parsed_response
-
-	def get_service_status(self):
-		"""
-			Returns a GREEN, GREEN_I, YELLOW or RED status.
-			Depending on the status/availability of the API its being called from.
-		"""
-
-		return self.make_request(extra_data=dict(Action='GetServiceStatus'))
-
-	def calc_signature(self, method, request_description):
-		"""Calculate MWS signature to interface with Amazon
-		"""
-		sig_data = method + '\n' + self.domain.replace('https://', '').lower() + '\n' + self.uri + '\n' + request_description
-		sig_data = sig_data.encode('utf-8')
-		secret_key = self.secret_key.encode('utf-8')
-		digest = hmac.new(secret_key, sig_data, hashlib.sha256).digest()
-		return base64.b64encode(digest).decode('utf-8')
-
-	def get_timestamp(self):
-		"""
-			Returns the current timestamp in proper format.
-		"""
-		return strftime("%Y-%m-%dT%H:%M:%SZ", gmtime())
-
-	def enumerate_param(self, param, values):
-		"""
-			Builds a dictionary of an enumerated parameter.
-			Takes any iterable and returns a dictionary.
-			ie.
-			enumerate_param('MarketplaceIdList.Id', (123, 345, 4343))
-			returns
-			{
-				MarketplaceIdList.Id.1: 123,
-				MarketplaceIdList.Id.2: 345,
-				MarketplaceIdList.Id.3: 4343
-			}
-		"""
-		params = {}
-		if values is not None:
-			if not param.endswith('.'):
-				param = "%s." % param
-			for num, value in enumerate(values):
-				params['%s%d' % (param, (num + 1))] = value
-		return params
-
-
-class Feeds(MWS):
-	""" Amazon MWS Feeds API """
-
-	ACCOUNT_TYPE = "Merchant"
-
-	def submit_feed(self, feed, feed_type, marketplaceids=None,
-					content_type="text/xml", purge='false'):
-		"""
-		Uploads a feed ( xml or .tsv ) to the seller's inventory.
-		Can be used for creating/updating products on Amazon.
-		"""
-		data = dict(Action='SubmitFeed',
-					FeedType=feed_type,
-					PurgeAndReplace=purge)
-		data.update(self.enumerate_param('MarketplaceIdList.Id.', marketplaceids))
-		md = calc_md5(feed)
-		return self.make_request(data, method="POST", body=feed,
-								extra_headers={'Content-MD5': md, 'Content-Type': content_type})
-
-	def get_feed_submission_list(self, feedids=None, max_count=None, feedtypes=None,
-								processingstatuses=None, fromdate=None, todate=None):
-		"""
-		Returns a list of all feed submissions submitted in the previous 90 days.
-		That match the query parameters.
-		"""
-
-		data = dict(Action='GetFeedSubmissionList',
-					MaxCount=max_count,
-					SubmittedFromDate=fromdate,
-					SubmittedToDate=todate,)
-		data.update(self.enumerate_param('FeedSubmissionIdList.Id', feedids))
-		data.update(self.enumerate_param('FeedTypeList.Type.', feedtypes))
-		data.update(self.enumerate_param('FeedProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def get_submission_list_by_next_token(self, token):
-		data = dict(Action='GetFeedSubmissionListByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def get_feed_submission_count(self, feedtypes=None, processingstatuses=None, fromdate=None, todate=None):
-		data = dict(Action='GetFeedSubmissionCount',
-					SubmittedFromDate=fromdate,
-					SubmittedToDate=todate)
-		data.update(self.enumerate_param('FeedTypeList.Type.', feedtypes))
-		data.update(self.enumerate_param('FeedProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def cancel_feed_submissions(self, feedids=None, feedtypes=None, fromdate=None, todate=None):
-		data = dict(Action='CancelFeedSubmissions',
-					SubmittedFromDate=fromdate,
-					SubmittedToDate=todate)
-		data.update(self.enumerate_param('FeedSubmissionIdList.Id.', feedids))
-		data.update(self.enumerate_param('FeedTypeList.Type.', feedtypes))
-		return self.make_request(data)
-
-	def get_feed_submission_result(self, feedid):
-		data = dict(Action='GetFeedSubmissionResult', FeedSubmissionId=feedid)
-		return self.make_request(data)
-
-class Reports(MWS):
-	""" Amazon MWS Reports API """
-
-	ACCOUNT_TYPE = "Merchant"
-
-	## REPORTS ###
-
-	def get_report(self, report_id):
-		data = dict(Action='GetReport', ReportId=report_id)
-		return self.make_request(data)
-
-	def get_report_count(self, report_types=(), acknowledged=None, fromdate=None, todate=None):
-		data = dict(Action='GetReportCount',
-					Acknowledged=acknowledged,
-					AvailableFromDate=fromdate,
-					AvailableToDate=todate)
-		data.update(self.enumerate_param('ReportTypeList.Type.', report_types))
-		return self.make_request(data)
-
-	def get_report_list(self, requestids=(), max_count=None, types=(), acknowledged=None,
-						fromdate=None, todate=None):
-		data = dict(Action='GetReportList',
-					Acknowledged=acknowledged,
-					AvailableFromDate=fromdate,
-					AvailableToDate=todate,
-					MaxCount=max_count)
-		data.update(self.enumerate_param('ReportRequestIdList.Id.', requestids))
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		return self.make_request(data)
-
-	def get_report_list_by_next_token(self, token):
-		data = dict(Action='GetReportListByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def get_report_request_count(self, report_types=(), processingstatuses=(), fromdate=None, todate=None):
-		data = dict(Action='GetReportRequestCount',
-					RequestedFromDate=fromdate,
-					RequestedToDate=todate)
-		data.update(self.enumerate_param('ReportTypeList.Type.', report_types))
-		data.update(self.enumerate_param('ReportProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def get_report_request_list(self, requestids=(), types=(), processingstatuses=(),
-								max_count=None, fromdate=None, todate=None):
-		data = dict(Action='GetReportRequestList',
-					MaxCount=max_count,
-					RequestedFromDate=fromdate,
-					RequestedToDate=todate)
-		data.update(self.enumerate_param('ReportRequestIdList.Id.', requestids))
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		data.update(self.enumerate_param('ReportProcessingStatusList.Status.', processingstatuses))
-		return self.make_request(data)
-
-	def get_report_request_list_by_next_token(self, token):
-		data = dict(Action='GetReportRequestListByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def request_report(self, report_type, start_date=None, end_date=None, marketplaceids=()):
-		data = dict(Action='RequestReport',
-					ReportType=report_type,
-					StartDate=start_date,
-					EndDate=end_date)
-		data.update(self.enumerate_param('MarketplaceIdList.Id.', marketplaceids))
-		return self.make_request(data)
-
-	### ReportSchedule ###
-
-	def get_report_schedule_list(self, types=()):
-		data = dict(Action='GetReportScheduleList')
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		return self.make_request(data)
-
-	def get_report_schedule_count(self, types=()):
-		data = dict(Action='GetReportScheduleCount')
-		data.update(self.enumerate_param('ReportTypeList.Type.', types))
-		return self.make_request(data)
-
-
-class Orders(MWS):
-	""" Amazon Orders API """
-
-	URI = "/Orders/2013-09-01"
-	VERSION = "2013-09-01"
-	NS = '{https://mws.amazonservices.com/Orders/2011-01-01}'
-
-	def list_orders(self, marketplaceids, created_after=None, created_before=None, lastupdatedafter=None,
-					lastupdatedbefore=None, orderstatus=(), fulfillment_channels=(),
-					payment_methods=(), buyer_email=None, seller_orderid=None, max_results='100'):
-
-		data = dict(Action='ListOrders',
-					CreatedAfter=created_after,
-					CreatedBefore=created_before,
-					LastUpdatedAfter=lastupdatedafter,
-					LastUpdatedBefore=lastupdatedbefore,
-					BuyerEmail=buyer_email,
-					SellerOrderId=seller_orderid,
-					MaxResultsPerPage=max_results,
-					)
-		data.update(self.enumerate_param('OrderStatus.Status.', orderstatus))
-		data.update(self.enumerate_param('MarketplaceId.Id.', marketplaceids))
-		data.update(self.enumerate_param('FulfillmentChannel.Channel.', fulfillment_channels))
-		data.update(self.enumerate_param('PaymentMethod.Method.', payment_methods))
-		return self.make_request(data)
-
-	def list_orders_by_next_token(self, token):
-		data = dict(Action='ListOrdersByNextToken', NextToken=token)
-		return self.make_request(data)
-
-	def get_order(self, amazon_order_ids):
-		data = dict(Action='GetOrder')
-		data.update(self.enumerate_param('AmazonOrderId.Id.', amazon_order_ids))
-		return self.make_request(data)
-
-	def list_order_items(self, amazon_order_id):
-		data = dict(Action='ListOrderItems', AmazonOrderId=amazon_order_id)
-		return self.make_request(data)
-
-	def list_order_items_by_next_token(self, token):
-		data = dict(Action='ListOrderItemsByNextToken', NextToken=token)
-		return self.make_request(data)
-
-
-class Products(MWS):
-	""" Amazon MWS Products API """
-
-	URI = '/Products/2011-10-01'
-	VERSION = '2011-10-01'
-	NS = '{http://mws.amazonservices.com/schema/Products/2011-10-01}'
-
-	def list_matching_products(self, marketplaceid, query, contextid=None):
-		""" Returns a list of products and their attributes, ordered by
-			relevancy, based on a search query that you specify.
-			Your search query can be a phrase that describes the product
-			or it can be a product identifier such as a UPC, EAN, ISBN, or JAN.
-		"""
-		data = dict(Action='ListMatchingProducts',
-					MarketplaceId=marketplaceid,
-					Query=query,
-					QueryContextId=contextid)
-		return self.make_request(data)
-
-	def get_matching_product(self, marketplaceid, asins):
-		""" Returns a list of products and their attributes, based on a list of
-			ASIN values that you specify.
-		"""
-		data = dict(Action='GetMatchingProduct', MarketplaceId=marketplaceid)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-	def get_matching_product_for_id(self, marketplaceid, type, id):
-		""" Returns a list of products and their attributes, based on a list of
-			product identifier values (asin, sellersku, upc, ean, isbn and JAN)
-			Added in Fourth Release, API version 2011-10-01
-		"""
-		data = dict(Action='GetMatchingProductForId',
-					MarketplaceId=marketplaceid,
-					IdType=type)
-		data.update(self.enumerate_param('IdList.Id', id))
-		return self.make_request(data)
-
-	def get_competitive_pricing_for_sku(self, marketplaceid, skus):
-		""" Returns the current competitive pricing of a product,
-			based on the SellerSKU and MarketplaceId that you specify.
-		"""
-		data = dict(Action='GetCompetitivePricingForSKU', MarketplaceId=marketplaceid)
-		data.update(self.enumerate_param('SellerSKUList.SellerSKU.', skus))
-		return self.make_request(data)
-
-	def get_competitive_pricing_for_asin(self, marketplaceid, asins):
-		""" Returns the current competitive pricing of a product,
-			based on the ASIN and MarketplaceId that you specify.
-		"""
-		data = dict(Action='GetCompetitivePricingForASIN', MarketplaceId=marketplaceid)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-	def get_lowest_offer_listings_for_sku(self, marketplaceid, skus, condition="Any", excludeme="False"):
-		data = dict(Action='GetLowestOfferListingsForSKU',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition,
-					ExcludeMe=excludeme)
-		data.update(self.enumerate_param('SellerSKUList.SellerSKU.', skus))
-		return self.make_request(data)
-
-	def get_lowest_offer_listings_for_asin(self, marketplaceid, asins, condition="Any", excludeme="False"):
-		data = dict(Action='GetLowestOfferListingsForASIN',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition,
-					ExcludeMe=excludeme)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-	def get_product_categories_for_sku(self, marketplaceid, sku):
-		data = dict(Action='GetProductCategoriesForSKU',
-					MarketplaceId=marketplaceid,
-					SellerSKU=sku)
-		return self.make_request(data)
-
-	def get_product_categories_for_asin(self, marketplaceid, asin):
-		data = dict(Action='GetProductCategoriesForASIN',
-					MarketplaceId=marketplaceid,
-					ASIN=asin)
-		return self.make_request(data)
-
-	def get_my_price_for_sku(self, marketplaceid, skus, condition=None):
-		data = dict(Action='GetMyPriceForSKU',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition)
-		data.update(self.enumerate_param('SellerSKUList.SellerSKU.', skus))
-		return self.make_request(data)
-
-	def get_my_price_for_asin(self, marketplaceid, asins, condition=None):
-		data = dict(Action='GetMyPriceForASIN',
-					MarketplaceId=marketplaceid,
-					ItemCondition=condition)
-		data.update(self.enumerate_param('ASINList.ASIN.', asins))
-		return self.make_request(data)
-
-
-class Sellers(MWS):
-	""" Amazon MWS Sellers API """
-
-	URI = '/Sellers/2011-07-01'
-	VERSION = '2011-07-01'
-	NS = '{http://mws.amazonservices.com/schema/Sellers/2011-07-01}'
-
-	def list_marketplace_participations(self):
-		"""
-			Returns a list of marketplaces a seller can participate in and
-			a list of participations that include seller-specific information in that marketplace.
-			The operation returns only those marketplaces where the seller's account is in an active state.
-		"""
-
-		data = dict(Action='ListMarketplaceParticipations')
-		return self.make_request(data)
-
-	def list_marketplace_participations_by_next_token(self, token):
-		"""
-			Takes a "NextToken" and returns the same information as "list_marketplace_participations".
-			Based on the "NextToken".
-		"""
-		data = dict(Action='ListMarketplaceParticipations', NextToken=token)
-		return self.make_request(data)
-
-#### Fulfillment APIs ####
-
-class InboundShipments(MWS):
-	URI = "/FulfillmentInboundShipment/2010-10-01"
-	VERSION = '2010-10-01'
-
-	# To be completed
-
-
-class Inventory(MWS):
-	""" Amazon MWS Inventory Fulfillment API """
-
-	URI = '/FulfillmentInventory/2010-10-01'
-	VERSION = '2010-10-01'
-	NS = "{http://mws.amazonaws.com/FulfillmentInventory/2010-10-01}"
-
-	def list_inventory_supply(self, skus=(), datetime=None, response_group='Basic'):
-		""" Returns information on available inventory """
-
-		data = dict(Action='ListInventorySupply',
-					QueryStartDateTime=datetime,
-					ResponseGroup=response_group,
-					)
-		data.update(self.enumerate_param('SellerSkus.member.', skus))
-		return self.make_request(data, "POST")
-
-	def list_inventory_supply_by_next_token(self, token):
-		data = dict(Action='ListInventorySupplyByNextToken', NextToken=token)
-		return self.make_request(data, "POST")
-
-
-class OutboundShipments(MWS):
-	URI = "/FulfillmentOutboundShipment/2010-10-01"
-	VERSION = "2010-10-01"
-	# To be completed
-
-
-class Recommendations(MWS):
-
-	""" Amazon MWS Recommendations API """
-
-	URI = '/Recommendations/2013-04-01'
-	VERSION = '2013-04-01'
-	NS = "{https://mws.amazonservices.com/Recommendations/2013-04-01}"
-
-	def get_last_updated_time_for_recommendations(self, marketplaceid):
-		"""
-		Checks whether there are active recommendations for each category for the given marketplace, and if there are,
-		returns the time when recommendations were last updated for each category.
-		"""
-
-		data = dict(Action='GetLastUpdatedTimeForRecommendations',
-					MarketplaceId=marketplaceid)
-		return self.make_request(data, "POST")
-
-	def list_recommendations(self, marketplaceid, recommendationcategory=None):
-		"""
-		Returns your active recommendations for a specific category or for all categories for a specific marketplace.
-		"""
-
-		data = dict(Action="ListRecommendations",
-					MarketplaceId=marketplaceid,
-					RecommendationCategory=recommendationcategory)
-		return self.make_request(data, "POST")
-
-	def list_recommendations_by_next_token(self, token):
-		"""
-		Returns the next page of recommendations using the NextToken parameter.
-		"""
-
-		data = dict(Action="ListRecommendationsByNextToken",
-					NextToken=token)
-		return self.make_request(data, "POST")
-
-class Finances(MWS):
-	""" Amazon Finances API"""
-	URI = '/Finances/2015-05-01'
-	VERSION = '2015-05-01'
-	NS = "{https://mws.amazonservices.com/Finances/2015-05-01}"
-
-	def list_financial_events(self , posted_after=None, posted_before=None,
-		 					amazon_order_id=None, max_results='100'):
-
-		data = dict(Action='ListFinancialEvents',
-					PostedAfter=posted_after,
-					PostedBefore=posted_before,
-					AmazonOrderId=amazon_order_id,
-					MaxResultsPerPage=max_results,
-					)
-		return self.make_request(data)
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js
deleted file mode 100644
index f5ea804..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.js
+++ /dev/null
@@ -1,2 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
deleted file mode 100644
index 5a678e7..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.json
+++ /dev/null
@@ -1,237 +0,0 @@
-{
- "actions": [],
- "creation": "2018-07-31 05:51:41.357047",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "enable_amazon",
-  "mws_credentials",
-  "seller_id",
-  "aws_access_key_id",
-  "mws_auth_token",
-  "secret_key",
-  "column_break_4",
-  "market_place_id",
-  "region",
-  "domain",
-  "section_break_13",
-  "company",
-  "warehouse",
-  "item_group",
-  "price_list",
-  "column_break_17",
-  "customer_group",
-  "territory",
-  "customer_type",
-  "market_place_account_group",
-  "section_break_12",
-  "after_date",
-  "taxes_charges",
-  "sync_products",
-  "sync_orders",
-  "column_break_10",
-  "enable_sync",
-  "max_retry_limit"
- ],
- "fields": [
-  {
-   "default": "0",
-   "fieldname": "enable_amazon",
-   "fieldtype": "Check",
-   "label": "Enable Amazon"
-  },
-  {
-   "fieldname": "mws_credentials",
-   "fieldtype": "Section Break",
-   "label": "MWS Credentials"
-  },
-  {
-   "fieldname": "seller_id",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Seller ID",
-   "reqd": 1
-  },
-  {
-   "fieldname": "aws_access_key_id",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "AWS Access Key ID",
-   "reqd": 1
-  },
-  {
-   "fieldname": "mws_auth_token",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "MWS Auth Token",
-   "reqd": 1
-  },
-  {
-   "fieldname": "secret_key",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Secret Key",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "market_place_id",
-   "fieldtype": "Data",
-   "label": "Market Place ID",
-   "reqd": 1
-  },
-  {
-   "fieldname": "region",
-   "fieldtype": "Select",
-   "label": "Region",
-   "options": "\nAE\nAU\nBR\nCA\nCN\nDE\nES\nFR\nIN\nJP\nIT\nMX\nUK\nUS",
-   "reqd": 1
-  },
-  {
-   "fieldname": "domain",
-   "fieldtype": "Data",
-   "label": "Domain",
-   "reqd": 1
-  },
-  {
-   "fieldname": "section_break_13",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  },
-  {
-   "fieldname": "warehouse",
-   "fieldtype": "Link",
-   "label": "Warehouse",
-   "options": "Warehouse",
-   "reqd": 1
-  },
-  {
-   "fieldname": "item_group",
-   "fieldtype": "Link",
-   "label": "Item Group",
-   "options": "Item Group",
-   "reqd": 1
-  },
-  {
-   "fieldname": "price_list",
-   "fieldtype": "Link",
-   "label": "Price List",
-   "options": "Price List",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_17",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "customer_group",
-   "fieldtype": "Link",
-   "label": "Customer Group",
-   "options": "Customer Group",
-   "reqd": 1
-  },
-  {
-   "fieldname": "territory",
-   "fieldtype": "Link",
-   "label": "Territory",
-   "options": "Territory",
-   "reqd": 1
-  },
-  {
-   "fieldname": "customer_type",
-   "fieldtype": "Select",
-   "label": "Customer Type",
-   "options": "Individual\nCompany",
-   "reqd": 1
-  },
-  {
-   "fieldname": "market_place_account_group",
-   "fieldtype": "Link",
-   "label": "Market Place Account Group",
-   "options": "Account",
-   "reqd": 1
-  },
-  {
-   "fieldname": "section_break_12",
-   "fieldtype": "Section Break"
-  },
-  {
-   "description": "Amazon will synch data updated after this date",
-   "fieldname": "after_date",
-   "fieldtype": "Datetime",
-   "label": "After Date",
-   "reqd": 1
-  },
-  {
-   "default": "0",
-   "description": "Get financial breakup of Taxes and charges data by Amazon ",
-   "fieldname": "taxes_charges",
-   "fieldtype": "Check",
-   "label": "Sync Taxes and Charges"
-  },
-  {
-   "fieldname": "column_break_10",
-   "fieldtype": "Column Break"
-  },
-  {
-   "default": "3",
-   "fieldname": "max_retry_limit",
-   "fieldtype": "Int",
-   "label": "Max Retry Limit"
-  },
-  {
-   "description": "Always sync your products from Amazon MWS before synching the Orders details",
-   "fieldname": "sync_products",
-   "fieldtype": "Button",
-   "label": "Sync Products",
-   "options": "get_products_details"
-  },
-  {
-   "description": "Click this button to pull your Sales Order data from Amazon MWS.",
-   "fieldname": "sync_orders",
-   "fieldtype": "Button",
-   "label": "Sync Orders",
-   "options": "get_order_details"
-  },
-  {
-   "default": "0",
-   "description": "Check this to enable a scheduled Daily synchronization routine via scheduler",
-   "fieldname": "enable_sync",
-   "fieldtype": "Check",
-   "label": "Enable Scheduled Sync"
-  }
- ],
- "issingle": 1,
- "links": [],
- "modified": "2020-04-07 14:26:20.174848",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Amazon MWS Settings",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "print": 1,
-   "read": 1,
-   "role": "System Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "quick_entry": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
deleted file mode 100644
index c1f460f..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/amazon_mws_settings.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies and contributors
-# For license information, please see license.txt
-
-
-import dateutil
-import frappe
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from frappe.model.document import Document
-
-from erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods import get_orders
-
-
-class AmazonMWSSettings(Document):
-	def validate(self):
-		if self.enable_amazon == 1:
-			self.enable_sync = 1
-			setup_custom_fields()
-		else:
-			self.enable_sync = 0
-
-	@frappe.whitelist()
-	def get_products_details(self):
-		if self.enable_amazon == 1:
-			frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_products_details')
-
-	@frappe.whitelist()
-	def get_order_details(self):
-		if self.enable_amazon == 1:
-			after_date = dateutil.parser.parse(self.after_date).strftime("%Y-%m-%d")
-			frappe.enqueue('erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_methods.get_orders', after_date=after_date)
-
-def schedule_get_order_details():
-	mws_settings = frappe.get_doc("Amazon MWS Settings")
-	if mws_settings.enable_sync and mws_settings.enable_amazon:
-		after_date = dateutil.parser.parse(mws_settings.after_date).strftime("%Y-%m-%d")
-		get_orders(after_date = after_date)
-
-def setup_custom_fields():
-	custom_fields = {
-		"Item": [dict(fieldname='amazon_item_code', label='Amazon Item Code',
-			fieldtype='Data', insert_after='series', read_only=1, print_hide=1)],
-		"Sales Order": [dict(fieldname='amazon_order_id', label='Amazon Order ID',
-			fieldtype='Data', insert_after='title', read_only=1, print_hide=1)]
-	}
-
-	create_custom_fields(custom_fields)
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.py
deleted file mode 100644
index 4be7960..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/test_amazon_mws_settings.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestAmazonMWSSettings(unittest.TestCase):
-	pass
diff --git a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py b/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py
deleted file mode 100644
index d9dfc6f..0000000
--- a/erpnext/erpnext_integrations/doctype/amazon_mws_settings/xml_utils.py
+++ /dev/null
@@ -1,104 +0,0 @@
-"""
-Created on Tue Jun 26 15:42:07 2012
-
-Borrowed from https://github.com/timotheus/ebaysdk-python
-
-@author: pierre
-"""
-
-import re
-import xml.etree.ElementTree as ET
-
-
-class object_dict(dict):
-	"""object view of dict, you can
-	>>> a = object_dict()
-	>>> a.fish = 'fish'
-	>>> a['fish']
-	'fish'
-	>>> a['water'] = 'water'
-	>>> a.water
-	'water'
- 	>>> a.test = {'value': 1}
-	>>> a.test2 = object_dict({'name': 'test2', 'value': 2})
-	>>> a.test, a.test2.name, a.test2.value
-	(1, 'test2', 2)
-	"""
-	def __init__(self, initd=None):
-		if initd is None:
-			initd = {}
-		dict.__init__(self, initd)
-
-	def __getattr__(self, item):
-
-		try:
-			d = self.__getitem__(item)
-		except KeyError:
-			return None
-
-		if isinstance(d, dict) and 'value' in d and len(d) == 1:
-			return d['value']
-		else:
-			return d
-
-	# if value is the only key in object, you can omit it
-	def __setstate__(self, item):
-		return False
-
-	def __setattr__(self, item, value):
-		self.__setitem__(item, value)
-
-	def getvalue(self, item, value=None):
-		return self.get(item, {}).get('value', value)
-
-
-class xml2dict(object):
-
-	def __init__(self):
-		pass
-
-	def _parse_node(self, node):
-		node_tree = object_dict()
-		# Save attrs and text, hope there will not be a child with same name
-		if node.text:
-			node_tree.value = node.text
-		for (k, v) in node.attrib.items():
-			k, v = self._namespace_split(k, object_dict({'value':v}))
-			node_tree[k] = v
-		#Save childrens
-		for child in node.getchildren():
-			tag, tree = self._namespace_split(child.tag,
-											self._parse_node(child))
-			if tag not in node_tree:  # the first time, so store it in dict
-				node_tree[tag] = tree
-				continue
-			old = node_tree[tag]
-			if not isinstance(old, list):
-				node_tree.pop(tag)
-				node_tree[tag] = [old]  # multi times, so change old dict to a list
-			node_tree[tag].append(tree)  # add the new one
-
-		return node_tree
-
-	def _namespace_split(self, tag, value):
-		"""
-		Split the tag '{http://cs.sfsu.edu/csc867/myscheduler}patients'
-		ns = http://cs.sfsu.edu/csc867/myscheduler
-		name = patients
-		"""
-		result = re.compile(r"\{(.*)\}(.*)").search(tag)
-		if result:
-			value.namespace, tag = result.groups()
-
-		return (tag, value)
-
-	def parse(self, file):
-		"""parse a xml file to a dict"""
-		f = open(file, 'r')
-		return self.fromstring(f.read())
-
-	def fromstring(self, s):
-		"""parse a string"""
-		t = ET.fromstring(s)
-		root_tag, root_tree = self._namespace_split(t.tag, self._parse_node(t))
-		return object_dict({root_tag: root_tree})
diff --git a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
index e242ace..f02f76e 100644
--- a/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
+++ b/erpnext/erpnext_integrations/doctype/gocardless_settings/gocardless_settings.py
@@ -2,17 +2,18 @@
 # For license information, please see license.txt
 
 
+from urllib.parse import urlencode
+
 import frappe
 import gocardless_pro
 from frappe import _
 from frappe.integrations.utils import create_payment_gateway, create_request_log
 from frappe.model.document import Document
 from frappe.utils import call_hook_method, cint, flt, get_url
-from six.moves.urllib.parse import urlencode
 
 
 class GoCardlessSettings(Document):
-	supported_currencies = ["EUR", "DKK", "GBP", "SEK"]
+	supported_currencies = ["EUR", "DKK", "GBP", "SEK", "AUD", "NZD", "CAD", "USD"]
 
 	def validate(self):
 		self.initialize_client()
@@ -79,7 +80,7 @@
 
 	def validate_transaction_currency(self, currency):
 		if currency not in self.supported_currencies:
-			frappe.throw(_("Please select another payment method. Stripe does not support transactions in currency '{0}'").format(currency))
+			frappe.throw(_("Please select another payment method. Go Cardless does not support transactions in currency '{0}'").format(currency))
 
 	def get_payment_url(self, **kwargs):
 		return get_url("./integrations/gocardless_checkout?{0}".format(urlencode(kwargs)))
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 54ed6f7..26bd19f 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -82,7 +82,7 @@
 				"is_private": True
 			})
 			try:
-				f.insert()
+				f.insert(ignore_if_duplicate=True)
 			except frappe.DuplicateEntryError:
 				pass
 			setattr(self, key, f.file_url)
diff --git a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
index 8da52f4..309d2cb 100644
--- a/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
+++ b/erpnext/erpnext_integrations/doctype/woocommerce_settings/woocommerce_settings.py
@@ -2,12 +2,13 @@
 # For license information, please see license.txt
 
 
+from urllib.parse import urlparse
+
 import frappe
 from frappe import _
 from frappe.custom.doctype.custom_field.custom_field import create_custom_field
 from frappe.model.document import Document
 from frappe.utils.nestedset import get_root_of
-from six.moves.urllib.parse import urlparse
 
 
 class WoocommerceSettings(Document):
diff --git a/erpnext/erpnext_integrations/taxjar_integration.py b/erpnext/erpnext_integrations/taxjar_integration.py
index a4e2157..14c86d5 100644
--- a/erpnext/erpnext_integrations/taxjar_integration.py
+++ b/erpnext/erpnext_integrations/taxjar_integration.py
@@ -8,10 +8,6 @@
 
 from erpnext import get_default_company, get_region
 
-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")
-TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
-TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
 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"]
@@ -35,12 +31,14 @@
 	if api_key and api_url:
 		client = taxjar.Client(api_key=api_key, api_url=api_url)
 		client.set_api_config('headers', {
-				'x-api-version': '2020-08-07'
+				'x-api-version': '2022-01-24'
 			})
 		return client
 
 
 def create_transaction(doc, method):
+	TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
+
 	"""Create an order transaction in TaxJar"""
 
 	if not TAXJAR_CREATE_TRANSACTIONS:
@@ -51,6 +49,7 @@
 	if not client:
 		return
 
+	TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
 	sales_tax = sum([tax.tax_amount for tax in doc.taxes if tax.account_head == TAX_ACCOUNT_HEAD])
 
 	if not sales_tax:
@@ -79,6 +78,7 @@
 
 def delete_transaction(doc, method):
 	"""Delete an existing TaxJar order transaction"""
+	TAXJAR_CREATE_TRANSACTIONS = frappe.db.get_single_value("TaxJar Settings", "taxjar_create_transactions")
 
 	if not TAXJAR_CREATE_TRANSACTIONS:
 		return
@@ -92,6 +92,8 @@
 
 
 def get_tax_data(doc):
+	SHIP_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "shipping_account_head")
+
 	from_address = get_company_address_details(doc)
 	from_shipping_state = from_address.get("state")
 	from_country_code = frappe.db.get_value("Country", from_address.country, "code")
@@ -113,20 +115,20 @@
 		to_shipping_state = get_state_code(to_address, 'Shipping')
 
 	tax_dict = {
-		'from_country': from_country_code,
-		'from_zip': from_address.pincode,
-		'from_state': from_shipping_state,
-		'from_city': from_address.city,
-		'from_street': from_address.address_line1,
-		'to_country': to_country_code,
-		'to_zip': to_address.pincode,
-		'to_city': to_address.city,
-		'to_street': to_address.address_line1,
-		'to_state': to_shipping_state,
-		'shipping': shipping,
-		'amount': doc.net_total,
-		'plugin': 'erpnext',
-		'line_items': line_items
+		"from_country": from_country_code,
+		"from_zip": from_address.pincode,
+		"from_state": from_shipping_state,
+		"from_city": from_address.city,
+		"from_street": from_address.address_line1,
+		"to_country": to_country_code,
+		"to_zip": to_address.pincode,
+		"to_city": to_address.city,
+		"to_street": to_address.address_line1,
+		"to_state": to_shipping_state,
+		"shipping": shipping,
+		"amount": doc.net_total,
+		"plugin": "erpnext",
+		"line_items": line_items
 	}
 	return tax_dict
 
@@ -156,6 +158,9 @@
 	return tax_dict
 
 def set_sales_tax(doc, method):
+	TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
+	TAXJAR_CALCULATE_TAX = frappe.db.get_single_value("TaxJar Settings", "taxjar_calculate_tax")
+
 	if not TAXJAR_CALCULATE_TAX:
 		return
 
@@ -206,6 +211,7 @@
 			doc.run_method("calculate_taxes_and_totals")
 
 def check_for_nexus(doc, tax_dict):
+	TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
 	if not frappe.db.get_value('TaxJar Nexus', {'region_code': tax_dict["to_state"]}):
 		for item in doc.get("items"):
 			item.tax_collectable = flt(0)
@@ -218,6 +224,8 @@
 
 def check_sales_tax_exemption(doc):
 	# if the party is exempt from sales tax, then set all tax account heads to zero
+	TAX_ACCOUNT_HEAD = frappe.db.get_single_value("TaxJar Settings", "tax_account_head")
+
 	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")
diff --git a/erpnext/erpnext_integrations/utils.py b/erpnext/erpnext_integrations/utils.py
index d922d87..30d3948 100644
--- a/erpnext/erpnext_integrations/utils.py
+++ b/erpnext/erpnext_integrations/utils.py
@@ -1,10 +1,10 @@
 import base64
 import hashlib
 import hmac
+from urllib.parse import urlparse
 
 import frappe
 from frappe import _
-from six.moves.urllib.parse import urlparse
 
 from erpnext import get_default_company
 
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index 45077aa..1f2619b 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -30,17 +30,6 @@
    "type": "Link"
   },
   {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Amazon MWS Settings",
-   "link_count": 0,
-   "link_to": "Amazon MWS Settings",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Payments",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index f014b0e..f8c4288 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -51,15 +51,15 @@
 
 on_session_creation = [
 	"erpnext.portal.utils.create_customer_or_supplier",
-	"erpnext.shopping_cart.utils.set_cart_count"
+	"erpnext.e_commerce.shopping_cart.utils.set_cart_count"
 ]
-on_logout = "erpnext.shopping_cart.utils.clear_cart_count"
+on_logout = "erpnext.e_commerce.shopping_cart.utils.clear_cart_count"
 
 treeviews = ['Account', 'Cost Center', 'Warehouse', 'Item Group', 'Customer Group', 'Sales Person', 'Territory', 'Assessment Group', 'Department']
 
 # website
-update_website_context = ["erpnext.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
-my_account_context = "erpnext.shopping_cart.utils.update_my_account_context"
+update_website_context = ["erpnext.e_commerce.shopping_cart.utils.update_website_context", "erpnext.education.doctype.education_settings.education_settings.update_website_context"]
+my_account_context = "erpnext.e_commerce.shopping_cart.utils.update_my_account_context"
 webform_list_context = "erpnext.controllers.website_list_for_contact.get_webform_list_context"
 
 calendars = ["Task", "Work Order", "Leave Application", "Sales Order", "Holiday List", "Course Schedule"]
@@ -67,14 +67,12 @@
 domains = {
 	'Distribution': 'erpnext.domains.distribution',
 	'Education': 'erpnext.domains.education',
-	'Hospitality': 'erpnext.domains.hospitality',
 	'Manufacturing': 'erpnext.domains.manufacturing',
-	'Non Profit': 'erpnext.domains.non_profit',
 	'Retail': 'erpnext.domains.retail',
 	'Services': 'erpnext.domains.services',
 }
 
-website_generators = ["Item Group", "Item", "BOM", "Sales Partner",
+website_generators = ["Item Group", "Website Item", "BOM", "Sales Partner",
 	"Job Opening", "Student Admission"]
 
 website_context = {
@@ -176,7 +174,6 @@
 	{"title": _("Fees"), "route": "/fees", "reference_doctype": "Fees", "role":"Student"},
 	{"title": _("Newsletter"), "route": "/newsletters", "reference_doctype": "Newsletter"},
 	{"title": _("Admission"), "route": "/admissions", "reference_doctype": "Student Admission", "role": "Student"},
-	{"title": _("Certification"), "route": "/certification", "reference_doctype": "Certification Application", "role": "Non Profit Portal User"},
 	{"title": _("Material Request"), "route": "/material-requests", "reference_doctype": "Material Request", "role": "Customer"},
 	{"title": _("Appointment Booking"), "route": "/book_appointment"},
 ]
@@ -238,10 +235,7 @@
 		]
 	},
 	"Sales Taxes and Charges Template": {
-		"on_update": "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.validate_cart_settings"
-	},
-	"Website Settings": {
-		"validate": "erpnext.portal.doctype.products_settings.products_settings.home_page_is_products"
+		"on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings"
 	},
 	"Tax Category": {
 		"validate": "erpnext.regional.india.utils.validate_tax_category"
@@ -337,7 +331,6 @@
 	"hourly": [
 		'erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails',
 		"erpnext.accounts.doctype.subscription.subscription.process_all",
-		"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
 		"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
 		"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
 		"erpnext.projects.doctype.project.project.hourly_reminder",
@@ -345,7 +338,8 @@
 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts"
 	],
 	"hourly_long": [
-		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
+		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
+		"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction"
 	],
 	"daily": [
 		"erpnext.stock.reorder_item.reorder_item",
@@ -373,7 +367,6 @@
 		"erpnext.selling.doctype.quotation.quotation.set_expired_status",
 		"erpnext.buying.doctype.supplier_quotation.supplier_quotation.set_expired_status",
 		"erpnext.accounts.doctype.process_statement_of_accounts.process_statement_of_accounts.send_auto_email",
-		"erpnext.non_profit.doctype.membership.membership.set_expired_status",
 		"erpnext.hr.doctype.interview.interview.send_daily_feedback_reminder"
 	],
 	"daily_long": [
@@ -444,6 +437,7 @@
 		'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
 		'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
 		'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
+		'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields',
 		'erpnext.assets.doctype.asset.asset.get_depreciation_amount': 'erpnext.regional.india.utils.get_depreciation_amount',
 		'erpnext.stock.doctype.item.item.set_item_tax_from_hsn_code': 'erpnext.regional.india.utils.set_item_tax_from_hsn_code'
 	},
@@ -566,26 +560,6 @@
 		{'doctype': 'Assessment Code', 'index': 39},
 		{'doctype': 'Discussion', 'index': 40},
 	],
-	"Non Profit": [
-		{'doctype': 'Certified Consultant', 'index': 1},
-		{'doctype': 'Certification Application', 'index': 2},
-		{'doctype': 'Volunteer', 'index': 3},
-		{'doctype': 'Membership', 'index': 4},
-		{'doctype': 'Member', 'index': 5},
-		{'doctype': 'Donor', 'index': 6},
-		{'doctype': 'Chapter', 'index': 7},
-		{'doctype': 'Grant Application', 'index': 8},
-		{'doctype': 'Volunteer Type', 'index': 9},
-		{'doctype': 'Donor Type', 'index': 10},
-		{'doctype': 'Membership Type', 'index': 11}
-	],
-	"Hospitality": [
-		{'doctype': 'Hotel Room', 'index': 0},
-		{'doctype': 'Hotel Room Reservation', 'index': 1},
-		{'doctype': 'Hotel Room Pricing', 'index': 2},
-		{'doctype': 'Hotel Room Package', 'index': 3},
-		{'doctype': 'Hotel Room Type', 'index': 4}
-	]
 }
 
 additional_timeline_content = {
diff --git a/erpnext/hotels/doctype/hotel_room/__init__.py b/erpnext/hotels/doctype/hotel_room/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.js b/erpnext/hotels/doctype/hotel_room/hotel_room.js
deleted file mode 100644
index 76f22d5..0000000
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_room/hotel_room.json b/erpnext/hotels/doctype/hotel_room/hotel_room.json
deleted file mode 100644
index 2567c07..0000000
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "prompt", 
- "beta": 1, 
- "creation": "2017-12-08 12:33:56.320420", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "hotel_room_type", 
-   "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": "Hotel Room Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Type", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "capacity", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Capacity", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "extra_bed_capacity", 
-   "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": "Extra Bed Capacity", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:10:50.670113", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Hotel Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room/hotel_room.py b/erpnext/hotels/doctype/hotel_room/hotel_room.py
deleted file mode 100644
index e4bd1c8..0000000
--- a/erpnext/hotels/doctype/hotel_room/hotel_room.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.model.document import Document
-
-
-class HotelRoom(Document):
-	def validate(self):
-		if not self.capacity:
-			self.capacity, self.extra_bed_capacity = frappe.db.get_value('Hotel Room Type',
-					self.hotel_room_type, ['capacity', 'extra_bed_capacity'])
diff --git a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py b/erpnext/hotels/doctype/hotel_room/test_hotel_room.py
deleted file mode 100644
index 95efe2c..0000000
--- a/erpnext/hotels/doctype/hotel_room/test_hotel_room.py
+++ /dev/null
@@ -1,23 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_dependencies = ["Hotel Room Package"]
-test_records = [
-	dict(doctype="Hotel Room", name="1001",
-		hotel_room_type="Basic Room"),
-	dict(doctype="Hotel Room", name="1002",
-		hotel_room_type="Basic Room"),
-	dict(doctype="Hotel Room", name="1003",
-		hotel_room_type="Basic Room"),
-	dict(doctype="Hotel Room", name="1004",
-		hotel_room_type="Basic Room"),
-	dict(doctype="Hotel Room", name="1005",
-		hotel_room_type="Basic Room"),
-	dict(doctype="Hotel Room", name="1006",
-		hotel_room_type="Basic Room")
-]
-
-class TestHotelRoom(unittest.TestCase):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/__init__.py b/erpnext/hotels/doctype/hotel_room_amenity/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_amenity/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json
deleted file mode 100644
index 29a0407..0000000
--- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-12-08 12:35:36.572185", 
- "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": "item", 
-   "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", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "billable", 
-   "fieldtype": "Check", 
-   "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": "Billable", 
-   "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
-  }
- ], 
- "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-12-09 12:05:07.125687", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Amenity", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py b/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py
deleted file mode 100644
index 1664931..0000000
--- a/erpnext/hotels/doctype/hotel_room_amenity/hotel_room_amenity.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomAmenity(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_package/__init__.py b/erpnext/hotels/doctype/hotel_room_package/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js
deleted file mode 100644
index 5b09ae5..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.js
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Package', {
-	hotel_room_type: function(frm) {
-		if (frm.doc.hotel_room_type) {
-			frappe.model.with_doc('Hotel Room Type', frm.doc.hotel_room_type, () => {
-				let hotel_room_type = frappe.get_doc('Hotel Room Type', frm.doc.hotel_room_type);
-
-				// reset the amenities
-				frm.doc.amenities = [];
-
-				for (let amenity of hotel_room_type.amenities) {
-					let d = frm.add_child('amenities');
-					d.item = amenity.item;
-					d.billable = amenity.billable;
-				}
-
-				frm.refresh();
-			});
-		}
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json
deleted file mode 100644
index 57dad44..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.json
+++ /dev/null
@@ -1,215 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "prompt", 
- "beta": 1, 
- "creation": "2017-12-08 12:43:17.211064", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "hotel_room_type", 
-   "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": "Hotel Room Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Type", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_2", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "item", 
-   "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": "Item", 
-   "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": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_4", 
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amenities", 
-   "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": "Amenities", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Amenity", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:10:31.111952", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Package", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room_package/hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py
deleted file mode 100644
index aedc83a..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/hotel_room_package.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.model.document import Document
-
-
-class HotelRoomPackage(Document):
-	def validate(self):
-		if not self.item:
-			item = frappe.get_doc(dict(
-				doctype = 'Item',
-				item_code = self.name,
-				item_group = 'Products',
-				is_stock_item = 0,
-				stock_uom = 'Unit'
-			))
-			item.insert()
-			self.item = item.name
diff --git a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py b/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py
deleted file mode 100644
index 749731f..0000000
--- a/erpnext/hotels/doctype/hotel_room_package/test_hotel_room_package.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_records = [
-	dict(doctype='Item', item_code='Breakfast',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Item', item_code='Lunch',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Item', item_code='Dinner',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Item', item_code='WiFi',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Hotel Room Type', name="Delux Room",
-		capacity=4,
-		extra_bed_capacity=2,
-		amenities = [
-			dict(item='WiFi', billable=0)
-		]),
-	dict(doctype='Hotel Room Type', name="Basic Room",
-		capacity=4,
-		extra_bed_capacity=2,
-		amenities = [
-			dict(item='Breakfast', billable=0)
-		]),
-	dict(doctype="Hotel Room Package", name="Basic Room with Breakfast",
-		hotel_room_type="Basic Room",
-		amenities = [
-			dict(item="Breakfast", billable=0)
-		]),
-	dict(doctype="Hotel Room Package", name="Basic Room with Lunch",
-		hotel_room_type="Basic Room",
-		amenities = [
-			dict(item="Breakfast", billable=0),
-			dict(item="Lunch", billable=0)
-		]),
-	dict(doctype="Hotel Room Package", name="Basic Room with Dinner",
-		hotel_room_type="Basic Room",
-		amenities = [
-			dict(item="Breakfast", billable=0),
-			dict(item="Dinner", billable=0)
-		])
-]
-
-class TestHotelRoomPackage(unittest.TestCase):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js
deleted file mode 100644
index 87bb192..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Pricing', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json
deleted file mode 100644
index 0f5a776..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.json
+++ /dev/null
@@ -1,266 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 0, 
- "autoname": "prompt", 
- "beta": 1, 
- "creation": "2017-12-08 12:51:47.088174", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "1", 
-   "fieldname": "enabled", 
-   "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": "Enabled", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "currency", 
-   "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": "Currency", 
-   "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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 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": 0, 
-   "in_standard_filter": 0, 
-   "label": "From Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 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": 0, 
-   "in_standard_filter": 0, 
-   "label": "To Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_5", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "items", 
-   "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": "Items", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Pricing 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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:10:41.559559", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Pricing", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Hotel Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py
deleted file mode 100644
index d28e573..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/hotel_room_pricing.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomPricing(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py b/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py
deleted file mode 100644
index 3455009..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing/test_hotel_room_pricing.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_dependencies = ["Hotel Room Package"]
-test_records = [
-	dict(doctype="Hotel Room Pricing", enabled=1,
-		name="Winter 2017",
-		from_date="2017-01-01", to_date="2017-01-10",
-		items = [
-			dict(item="Basic Room with Breakfast", rate=10000),
-			dict(item="Basic Room with Lunch", rate=11000),
-			dict(item="Basic Room with Dinner", rate=12000)
-		])
-]
-
-class TestHotelRoomPricing(unittest.TestCase):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json
deleted file mode 100644
index d6cd826..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-12-08 12:50:13.486090", 
- "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": "item", 
-   "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", 
-   "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
-  }, 
-  {
-   "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, 
-   "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
-  }
- ], 
- "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-12-09 12:04:58.641703", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Pricing Item", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py b/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py
deleted file mode 100644
index 2e6bb5f..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_item/hotel_room_pricing_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomPricingItem(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py b/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js
deleted file mode 100644
index f6decd9..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Pricing Package', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json
deleted file mode 100644
index 1e52932..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.json
+++ /dev/null
@@ -1,173 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-12-08 12:50:13.486090", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "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 Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "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 Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "hotel_room_package", 
-   "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": "Hotel Room Package", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Package", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "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, 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-11-04 03:34:02.551811", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Pricing Package", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py
deleted file mode 100644
index ebbdb6e..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/hotel_room_pricing_package.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomPricingPackage(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py b/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py
deleted file mode 100644
index 196e650..0000000
--- a/erpnext/hotels/doctype/hotel_room_pricing_package/test_hotel_room_pricing_package.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestHotelRoomPricingPackage(unittest.TestCase):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js
deleted file mode 100644
index e58d763..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.js
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Reservation', {
-	refresh: function(frm) {
-		if(frm.doc.docstatus == 1){
-			frm.add_custom_button(__('Create Invoice'), ()=> {
-				frm.trigger("make_invoice");
-			});
-		}
-	},
-	from_date: function(frm) {
-		frm.trigger("recalculate_rates");
-	},
-	to_date: function(frm) {
-		frm.trigger("recalculate_rates");
-	},
-	recalculate_rates: function(frm) {
-		if (!frm.doc.from_date || !frm.doc.to_date
-			|| !frm.doc.items.length){
-			return;
-		}
-		frappe.call({
-			"method": "erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation.get_room_rate",
-			"args": {"hotel_room_reservation": frm.doc}
-		}).done((r)=> {
-			for (var i = 0; i < r.message.items.length; i++) {
-				frm.doc.items[i].rate = r.message.items[i].rate;
-				frm.doc.items[i].amount = r.message.items[i].amount;
-			}
-			frappe.run_serially([
-				()=> frm.set_value("net_total", r.message.net_total),
-				()=> frm.refresh_field("items")
-			]);
-		});
-	},
-	make_invoice: function(frm) {
-		frappe.model.with_doc("Hotel Settings", "Hotel Settings", ()=>{
-			frappe.model.with_doctype("Sales Invoice", ()=>{
-				let hotel_settings = frappe.get_doc("Hotel Settings", "Hotel Settings");
-				let invoice = frappe.model.get_new_doc("Sales Invoice");
-				invoice.customer = frm.doc.customer || hotel_settings.default_customer;
-				if (hotel_settings.default_invoice_naming_series){
-					invoice.naming_series = hotel_settings.default_invoice_naming_series;
-				}
-				for (let d of frm.doc.items){
-					let invoice_item = frappe.model.add_child(invoice, "items")
-					invoice_item.item_code = d.item;
-					invoice_item.qty = d.qty;
-					invoice_item.rate = d.rate;
-				}
-				if (hotel_settings.default_taxes_and_charges){
-					invoice.taxes_and_charges = hotel_settings.default_taxes_and_charges;
-				}
-				frappe.set_route("Form", invoice.doctype, invoice.name);
-			});
-		});
-	}
-});
-
-frappe.ui.form.on('Hotel Room Reservation Item', {
-	item: function(frm, doctype, name) {
-		frm.trigger("recalculate_rates");
-	},
-	qty: function(frm) {
-		frm.trigger("recalculate_rates");
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json
deleted file mode 100644
index fd20efd..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.json
+++ /dev/null
@@ -1,436 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 0, 
- "autoname": "HTL-RES-.YYYY.-.#####", 
- "beta": 1, 
- "creation": "2017-12-08 13:01:34.829175", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "guest_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": "Guest 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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "customer", 
-   "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": "Customer", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Customer", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "from_date", 
-   "fieldtype": "Date", 
-   "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": "From Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "to_date", 
-   "fieldtype": "Date", 
-   "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": "To Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "late_checkin", 
-   "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": "Late Checkin", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "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, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Booked\nAdvance Paid\nInvoiced\nPaid\nCompleted\nCancelled", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_8", 
-   "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_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "items", 
-   "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": "Items", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Reservation 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, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "net_total", 
-   "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": "Net Total", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amended_from", 
-   "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": "Amended From", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "Hotel Room Reservation", 
-   "permlevel": 0, 
-   "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, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 1, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-08-21 16:15:47.326951", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Reservation", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 1, 
-   "cancel": 1, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Hotel Reservation User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 1, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py
deleted file mode 100644
index 7725955..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import json
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import add_days, date_diff, flt
-
-
-class HotelRoomUnavailableError(frappe.ValidationError): pass
-class HotelRoomPricingNotSetError(frappe.ValidationError): pass
-
-class HotelRoomReservation(Document):
-	def validate(self):
-		self.total_rooms = {}
-		self.set_rates()
-		self.validate_availability()
-
-	def validate_availability(self):
-		for i in range(date_diff(self.to_date, self.from_date)):
-			day = add_days(self.from_date, i)
-			self.rooms_booked = {}
-
-			for d in self.items:
-				if not d.item in self.rooms_booked:
-					self.rooms_booked[d.item] = 0
-
-				room_type = frappe.db.get_value("Hotel Room Package",
-					d.item, 'hotel_room_type')
-				rooms_booked = get_rooms_booked(room_type, day, exclude_reservation=self.name) \
-					+ d.qty + self.rooms_booked.get(d.item)
-				total_rooms = self.get_total_rooms(d.item)
-				if total_rooms < rooms_booked:
-					frappe.throw(_("Hotel Rooms of type {0} are unavailable on {1}").format(d.item,
-						frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomUnavailableError)
-
-				self.rooms_booked[d.item] += rooms_booked
-
-	def get_total_rooms(self, item):
-		if not item in self.total_rooms:
-			self.total_rooms[item] = frappe.db.sql("""
-				select count(*)
-				from
-					`tabHotel Room Package` package
-				inner join
-					`tabHotel Room` room on package.hotel_room_type = room.hotel_room_type
-				where
-					package.item = %s""", item)[0][0] or 0
-
-		return self.total_rooms[item]
-
-	def set_rates(self):
-		self.net_total = 0
-		for d in self.items:
-			net_rate = 0.0
-			for i in range(date_diff(self.to_date, self.from_date)):
-				day = add_days(self.from_date, i)
-				if not d.item:
-					continue
-				day_rate = frappe.db.sql("""
-					select
-						item.rate
-					from
-						`tabHotel Room Pricing Item` item,
-						`tabHotel Room Pricing` pricing
-					where
-						item.parent = pricing.name
-						and item.item = %s
-						and %s between pricing.from_date
-							and pricing.to_date""", (d.item, day))
-
-				if day_rate:
-					net_rate += day_rate[0][0]
-				else:
-					frappe.throw(
-						_("Please set Hotel Room Rate on {}").format(
-							frappe.format(day, dict(fieldtype="Date"))), exc=HotelRoomPricingNotSetError)
-			d.rate = net_rate
-			d.amount = net_rate * flt(d.qty)
-			self.net_total += d.amount
-
-@frappe.whitelist()
-def get_room_rate(hotel_room_reservation):
-	"""Calculate rate for each day as it may belong to different Hotel Room Pricing Item"""
-	doc = frappe.get_doc(json.loads(hotel_room_reservation))
-	doc.set_rates()
-	return doc.as_dict()
-
-def get_rooms_booked(room_type, day, exclude_reservation=None):
-	exclude_condition = ''
-	if exclude_reservation:
-		exclude_condition = 'and reservation.name != {0}'.format(frappe.db.escape(exclude_reservation))
-
-	return frappe.db.sql("""
-		select sum(item.qty)
-		from
-			`tabHotel Room Package` room_package,
-			`tabHotel Room Reservation Item` item,
-			`tabHotel Room Reservation` reservation
-		where
-			item.parent = reservation.name
-			and room_package.item = item.item
-			and room_package.hotel_room_type = %s
-			and reservation.docstatus = 1
-			{exclude_condition}
-			and %s between reservation.from_date
-				and reservation.to_date""".format(exclude_condition=exclude_condition),
-				(room_type, day))[0][0] or 0
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js b/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
deleted file mode 100644
index 7bde292..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/hotel_room_reservation_calendar.js
+++ /dev/null
@@ -1,9 +0,0 @@
-frappe.views.calendar["Hotel Room Reservation"] = {
-	field_map: {
-		"start": "from_date",
-		"end": "to_date",
-		"id": "name",
-		"title": "guest_name",
-		"status": "status"
-	}
-}
diff --git a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py b/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py
deleted file mode 100644
index bb32a27..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation/test_hotel_room_reservation.py
+++ /dev/null
@@ -1,65 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-
-from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import (
-	HotelRoomPricingNotSetError,
-	HotelRoomUnavailableError,
-)
-
-test_dependencies = ["Hotel Room Package", "Hotel Room Pricing", "Hotel Room"]
-
-class TestHotelRoomReservation(unittest.TestCase):
-	def setUp(self):
-		frappe.db.sql("delete from `tabHotel Room Reservation`")
-		frappe.db.sql("delete from `tabHotel Room Reservation Item`")
-
-	def test_reservation(self):
-		reservation = make_reservation(
-			from_date="2017-01-01",
-			to_date="2017-01-03",
-			items=[
-				dict(item="Basic Room with Dinner", qty=2)
-			]
-		)
-		reservation.insert()
-		self.assertEqual(reservation.net_total, 48000)
-
-	def test_price_not_set(self):
-		reservation = make_reservation(
-			from_date="2016-01-01",
-			to_date="2016-01-03",
-			items=[
-				dict(item="Basic Room with Dinner", qty=2)
-			]
-		)
-		self.assertRaises(HotelRoomPricingNotSetError, reservation.insert)
-
-	def test_room_unavailable(self):
-		reservation = make_reservation(
-			from_date="2017-01-01",
-			to_date="2017-01-03",
-			items=[
-				dict(item="Basic Room with Dinner", qty=2),
-			]
-		)
-		reservation.insert()
-
-		reservation = make_reservation(
-			from_date="2017-01-01",
-			to_date="2017-01-03",
-			items=[
-				dict(item="Basic Room with Dinner", qty=20),
-			]
-		)
-		self.assertRaises(HotelRoomUnavailableError, reservation.insert)
-
-def make_reservation(**kwargs):
-	kwargs["doctype"] = "Hotel Room Reservation"
-	if not "guest_name" in kwargs:
-		kwargs["guest_name"] = "Test Guest"
-	doc = frappe.get_doc(kwargs)
-	return doc
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py b/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation_item/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json
deleted file mode 100644
index 2b7931e..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.json
+++ /dev/null
@@ -1,195 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 0, 
- "creation": "2017-12-08 12:58:21.733330", 
- "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": "item", 
-   "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", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "qty", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "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": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "currency", 
-   "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": "Currency", 
-   "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
-  }, 
-  {
-   "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, 
-   "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
-  }, 
-  {
-   "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": 1, 
-   "in_standard_filter": 0, 
-   "label": "Amount", 
-   "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
-  }
- ], 
- "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-12-09 12:04:34.562956", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Reservation Item", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py b/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py
deleted file mode 100644
index 41d86dd..0000000
--- a/erpnext/hotels/doctype/hotel_room_reservation_item/hotel_room_reservation_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomReservationItem(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_type/__init__.py b/erpnext/hotels/doctype/hotel_room_type/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js
deleted file mode 100644
index d73835d..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Room Type', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json
deleted file mode 100644
index 3d26413..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.json
+++ /dev/null
@@ -1,204 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "prompt", 
- "beta": 1, 
- "creation": "2017-12-08 12:38:29.485175", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "capacity", 
-   "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": "Capacity", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "extra_bed_capacity", 
-   "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": "Extra Bed Capacity", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_3", 
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amenities", 
-   "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": "Amenities", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Hotel Room Amenity", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:10:23.355486", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Type", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Hotel Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_room_type/hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py
deleted file mode 100644
index 7ab529f..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/hotel_room_type.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelRoomType(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py b/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py
deleted file mode 100644
index 8d1147d..0000000
--- a/erpnext/hotels/doctype/hotel_room_type/test_hotel_room_type.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestHotelRoomType(unittest.TestCase):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js b/erpnext/hotels/doctype/hotel_settings/hotel_settings.js
deleted file mode 100644
index 0b4a2c3..0000000
--- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Hotel Settings', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json b/erpnext/hotels/doctype/hotel_settings/hotel_settings.json
deleted file mode 100644
index d9f5572..0000000
--- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.json
+++ /dev/null
@@ -1,175 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 1, 
- "creation": "2017-12-08 17:50:24.523107", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_customer", 
-   "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": "Default Customer", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Customer", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_taxes_and_charges", 
-   "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": "Default Taxes and Charges", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Sales Taxes and Charges Template", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_invoice_naming_series", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Default Invoice Naming Series", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 1, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:11:12.857308", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Settings", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 0, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 0, 
-   "role": "Hotel Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/hotels/doctype/hotel_settings/hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/hotel_settings.py
deleted file mode 100644
index 8376d50..0000000
--- a/erpnext/hotels/doctype/hotel_settings/hotel_settings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class HotelSettings(Document):
-	pass
diff --git a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py b/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py
deleted file mode 100644
index e76c00c..0000000
--- a/erpnext/hotels/doctype/hotel_settings/test_hotel_settings.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestHotelSettings(unittest.TestCase):
-	pass
diff --git a/erpnext/hotels/report/hotel_room_occupancy/__init__.py b/erpnext/hotels/report/hotel_room_occupancy/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/__init__.py
+++ /dev/null
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js
deleted file mode 100644
index 81efb2d..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-/* eslint-disable */
-
-frappe.query_reports["Hotel Room Occupancy"] = {
-	"filters": [
-		{
-			"fieldname":"from_date",
-			"label": __("From Date"),
-			"fieldtype": "Date",
-			"default": frappe.datetime.now_date(),
-			"reqd":1
-		},
-		{
-			"fieldname":"to_date",
-			"label": __("To Date"),
-			"fieldtype": "Date",
-			"default": frappe.datetime.now_date(),
-			"reqd":1
-		}
-	]
-}
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json
deleted file mode 100644
index 782a48b..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "add_total_row": 1, 
- "apply_user_permissions": 1, 
- "creation": "2017-12-09 14:31:26.306705", 
- "disabled": 0, 
- "docstatus": 0, 
- "doctype": "Report", 
- "idx": 0, 
- "is_standard": "Yes", 
- "modified": "2017-12-09 14:31:26.306705", 
- "modified_by": "Administrator", 
- "module": "Hotels", 
- "name": "Hotel Room Occupancy", 
- "owner": "Administrator", 
- "ref_doctype": "Hotel Room Reservation", 
- "report_name": "Hotel Room Occupancy", 
- "report_type": "Script Report", 
- "roles": [
-  {
-   "role": "System Manager"
-  }, 
-  {
-   "role": "Hotel Reservation User"
-  }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py b/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
deleted file mode 100644
index c43589d..0000000
--- a/erpnext/hotels/report/hotel_room_occupancy/hotel_room_occupancy.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.utils import add_days, date_diff
-
-from erpnext.hotels.doctype.hotel_room_reservation.hotel_room_reservation import get_rooms_booked
-
-
-def execute(filters=None):
-	columns = get_columns(filters)
-	data = get_data(filters)
-	return columns, data
-
-def get_columns(filters):
-	columns = [
-		dict(label=_("Room Type"), fieldname="room_type"),
-		dict(label=_("Rooms Booked"), fieldtype="Int")
-	]
-	return columns
-
-def get_data(filters):
-	out = []
-	for room_type in frappe.get_all('Hotel Room Type'):
-		total_booked = 0
-		for i in range(date_diff(filters.to_date, filters.from_date)):
-			day = add_days(filters.from_date, i)
-			total_booked += get_rooms_booked(room_type.name, day)
-
-		out.append([room_type.name, total_booked])
-
-	return out
diff --git a/erpnext/hr/doctype/appointment_letter/appointment_letter.json b/erpnext/hr/doctype/appointment_letter/appointment_letter.json
index c81b700..012f6b6 100644
--- a/erpnext/hr/doctype/appointment_letter/appointment_letter.json
+++ b/erpnext/hr/doctype/appointment_letter/appointment_letter.json
@@ -86,11 +86,12 @@
   }
  ],
  "links": [],
- "modified": "2020-01-21 17:30:36.334395",
+ "modified": "2022-01-18 19:27:35.649424",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Appointment Letter",
  "name_case": "Title Case",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -118,7 +119,10 @@
    "write": 1
   }
  ],
+ "search_fields": "applicant_name, company",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "applicant_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json
index c136fb2..5e50fe6 100644
--- a/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json
+++ b/erpnext/hr/doctype/appointment_letter_template/appointment_letter_template.json
@@ -1,11 +1,12 @@
 {
  "actions": [],
- "autoname": "HR-APP-LETTER-TEMP-.#####",
+ "autoname": "field:template_name",
  "creation": "2019-12-26 12:20:14.219578",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "template_name",
   "introduction",
   "terms",
   "closing_notes"
@@ -29,13 +30,21 @@
    "label": "Terms",
    "options": "Appointment Letter content",
    "reqd": 1
+  },
+  {
+   "fieldname": "template_name",
+   "fieldtype": "Data",
+   "label": "Template Name",
+   "reqd": 1,
+   "unique": 1
   }
  ],
  "links": [],
- "modified": "2020-01-21 17:00:46.779420",
+ "modified": "2022-01-18 19:25:14.614616",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Appointment Letter Template",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
@@ -63,7 +72,10 @@
    "write": 1
   }
  ],
+ "search_fields": "template_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "template_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/department/department.py b/erpnext/hr/doctype/department/department.py
index ed0bfcf..71300c4 100644
--- a/erpnext/hr/doctype/department/department.py
+++ b/erpnext/hr/doctype/department/department.py
@@ -32,7 +32,7 @@
 		return new
 
 	def on_update(self):
-		if not frappe.local.flags.ignore_update_nsm:
+		if not (frappe.local.flags.ignore_update_nsm or frappe.flags.in_setup_wizard):
 			super(Department, self).on_update()
 
 	def on_trash(self):
diff --git a/erpnext/hr/doctype/department/test_records.json b/erpnext/hr/doctype/department/test_records.json
index 654925e..e3421f2 100644
--- a/erpnext/hr/doctype/department/test_records.json
+++ b/erpnext/hr/doctype/department/test_records.json
@@ -1,4 +1,4 @@
 [
-	{"doctype":"Department", "department_name":"_Test Department", "company": "_Test Company"},
-	{"doctype":"Department", "department_name":"_Test Department 1", "company": "_Test Company"}
+	{"doctype":"Department", "department_name":"_Test Department", "company": "_Test Company", "parent_department": "All Departments"},
+	{"doctype":"Department", "department_name":"_Test Department 1", "company": "_Test Company", "parent_department": "All Departments"}
 ]
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index a2df26c..6e52eb9 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -142,7 +142,7 @@
 						"file_url": self.image,
 						"attached_to_doctype": "User",
 						"attached_to_name": self.user_id
-					}).insert()
+					}).insert(ignore_if_duplicate=True)
 				except frappe.DuplicateEntryError:
 					# already exists
 					pass
diff --git a/erpnext/hr/doctype/employee/employee_dashboard.py b/erpnext/hr/doctype/employee/employee_dashboard.py
index a1247d9..fb62b04 100644
--- a/erpnext/hr/doctype/employee/employee_dashboard.py
+++ b/erpnext/hr/doctype/employee/employee_dashboard.py
@@ -21,7 +21,7 @@
 			},
 			{
 				'label': _('Lifecycle'),
-				'items': ['Employee Transfer', 'Employee Promotion', 'Employee Grievance']
+				'items': ['Employee Onboarding', 'Employee Transfer', 'Employee Promotion', 'Employee Grievance']
 			},
 			{
 				'label': _('Exit'),
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index 559bd39..0bb6637 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -20,6 +20,7 @@
 
 	send_advance_holiday_reminders("Weekly")
 
+
 def send_reminders_in_advance_monthly():
 	to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders"))
 	frequency = frappe.db.get_single_value("HR Settings", "frequency")
@@ -28,6 +29,7 @@
 
 	send_advance_holiday_reminders("Monthly")
 
+
 def send_advance_holiday_reminders(frequency):
 	"""Send Holiday Reminders in Advance to Employees
 	`frequency` (str): 'Weekly' or 'Monthly'
@@ -42,7 +44,7 @@
 	else:
 		return
 
-	employees = frappe.db.get_all('Employee', pluck='name')
+	employees = frappe.db.get_all('Employee', filters={'status': 'Active'}, pluck='name')
 	for employee in employees:
 		holidays = get_holidays_for_employee(
 			employee,
@@ -51,10 +53,13 @@
 			raise_exception=False
 		)
 
-		if not (holidays is None):
-			send_holidays_reminder_in_advance(employee, holidays)
+		send_holidays_reminder_in_advance(employee, holidays)
+
 
 def send_holidays_reminder_in_advance(employee, holidays):
+	if not holidays:
+		return
+
 	employee_doc = frappe.get_doc('Employee', employee)
 	employee_email = get_employee_email(employee_doc)
 	frequency = frappe.db.get_single_value("HR Settings", "frequency")
@@ -101,6 +106,7 @@
 				reminder_text, message = get_birthday_reminder_text_and_message(others)
 				send_birthday_reminder(person_email, reminder_text, others, message)
 
+
 def get_birthday_reminder_text_and_message(birthday_persons):
 	if len(birthday_persons) == 1:
 		birthday_person_text = birthday_persons[0]['name']
@@ -116,6 +122,7 @@
 
 	return reminder_text, message
 
+
 def send_birthday_reminder(recipients, reminder_text, birthday_persons, message):
 	frappe.sendmail(
 		recipients=recipients,
@@ -129,10 +136,12 @@
 		header=_("Birthday Reminder 🎂")
 	)
 
+
 def get_employees_who_are_born_today():
 	"""Get all employee born today & group them based on their company"""
 	return get_employees_having_an_event_today("birthday")
 
+
 def get_employees_having_an_event_today(event_type):
 	"""Get all employee who have `event_type` today
 	& group them based on their company. `event_type`
@@ -210,13 +219,14 @@
 				reminder_text, message = get_work_anniversary_reminder_text_and_message(others)
 				send_work_anniversary_reminder(person_email, reminder_text, others, message)
 
+
 def get_work_anniversary_reminder_text_and_message(anniversary_persons):
 	if len(anniversary_persons) == 1:
 		anniversary_person = anniversary_persons[0]['name']
 		persons_name = anniversary_person
 		# Number of years completed at the company
 		completed_years = getdate().year - anniversary_persons[0]['date_of_joining'].year
-		anniversary_person += f" completed {completed_years} years"
+		anniversary_person += f" completed {completed_years} year(s)"
 	else:
 		person_names_with_years = []
 		names = []
@@ -225,7 +235,7 @@
 			names.append(person_text)
 			# Number of years completed at the company
 			completed_years = getdate().year - person['date_of_joining'].year
-			person_text += f" completed {completed_years} years"
+			person_text += f" completed {completed_years} year(s)"
 			person_names_with_years.append(person_text)
 
 		# converts ["Jim", "Rim", "Dim"] to Jim, Rim & Dim
@@ -239,6 +249,7 @@
 
 	return reminder_text, message
 
+
 def send_work_anniversary_reminder(recipients, reminder_text, anniversary_persons, message):
 	frappe.sendmail(
 		recipients=recipients,
@@ -249,5 +260,5 @@
 			anniversary_persons=anniversary_persons,
 			message=message,
 		),
-		header=_("🎊️🎊️ Work Anniversary Reminder 🎊️🎊️")
+		header=_("Work Anniversary Reminder")
 	)
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 8a2da08..67cbea6 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -36,7 +36,7 @@
 		employee_doc.reload()
 
 		make_holiday_list()
-		frappe.db.set_value("Company", erpnext.get_default_company(), "default_holiday_list", "Salary Slip Test Holiday List")
+		frappe.db.set_value("Company", employee_doc.company, "default_holiday_list", "Salary Slip Test Holiday List")
 
 		frappe.db.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
 		salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
diff --git a/erpnext/hr/doctype/employee/test_employee_reminders.py b/erpnext/hr/doctype/employee/test_employee_reminders.py
index 52c0098..a4097ab 100644
--- a/erpnext/hr/doctype/employee/test_employee_reminders.py
+++ b/erpnext/hr/doctype/employee/test_employee_reminders.py
@@ -5,10 +5,12 @@
 from datetime import timedelta
 
 import frappe
-from frappe.utils import getdate
+from frappe.utils import add_months, getdate
 
+from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.hr_settings.hr_settings import set_proceed_with_frequency_change
+from erpnext.hr.utils import get_holidays_for_employee
 
 
 class TestEmployeeReminders(unittest.TestCase):
@@ -46,6 +48,24 @@
 		cls.test_employee = test_employee
 		cls.test_holiday_dates = test_holiday_dates
 
+		# Employee without holidays in this month/week
+		test_employee_2 = make_employee('test@empwithoutholiday.io', company="_Test Company")
+		test_employee_2 = frappe.get_doc('Employee', test_employee_2)
+
+		test_holiday_list = make_holiday_list(
+			'TestHolidayRemindersList2',
+			holiday_dates=[
+				{'holiday_date': add_months(getdate(), 1), 'description': 'test holiday1'},
+			],
+			from_date=add_months(getdate(), -2),
+			to_date=add_months(getdate(), 2)
+		)
+		test_employee_2.holiday_list = test_holiday_list.name
+		test_employee_2.save()
+
+		cls.test_employee_2 = test_employee_2
+		cls.holiday_list_2 = test_holiday_list
+
 	@classmethod
 	def get_test_holiday_dates(cls):
 		today_date = getdate()
@@ -61,6 +81,7 @@
 	def setUp(self):
 		# Clear Email Queue
 		frappe.db.sql("delete from `tabEmail Queue`")
+		frappe.db.sql("delete from `tabEmail Queue Recipient`")
 
 	def test_is_holiday(self):
 		from erpnext.hr.doctype.employee.employee import is_holiday
@@ -103,11 +124,10 @@
 		self.assertTrue("Subject: Birthday Reminder" in email_queue[0].message)
 
 	def test_work_anniversary_reminders(self):
-		employee = frappe.get_doc("Employee", frappe.db.sql_list("select name from tabEmployee limit 1")[0])
-		employee.date_of_joining = "1998" + frappe.utils.nowdate()[4:]
-		employee.company_email = "test@example.com"
-		employee.company = "_Test Company"
-		employee.save()
+		make_employee("test_work_anniversary@gmail.com",
+			date_of_joining="1998" + frappe.utils.nowdate()[4:],
+			company="_Test Company",
+		)
 
 		from erpnext.hr.doctype.employee.employee_reminders import (
 			get_employees_having_an_event_today,
@@ -115,7 +135,12 @@
 		)
 
 		employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
-		self.assertTrue(employees_having_work_anniversary.get("_Test Company"))
+		employees = employees_having_work_anniversary.get("_Test Company") or []
+		user_ids = []
+		for entry in employees:
+			user_ids.append(entry.user_id)
+
+		self.assertTrue("test_work_anniversary@gmail.com" in user_ids)
 
 		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
 		hr_settings.send_work_anniversary_reminders = 1
@@ -126,16 +151,24 @@
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue("Subject: Work Anniversary Reminder" in email_queue[0].message)
 
-	def test_send_holidays_reminder_in_advance(self):
-		from erpnext.hr.doctype.employee.employee_reminders import send_holidays_reminder_in_advance
-		from erpnext.hr.utils import get_holidays_for_employee
+	def test_work_anniversary_reminder_not_sent_for_0_years(self):
+		make_employee("test_work_anniversary_2@gmail.com",
+			date_of_joining=getdate(),
+			company="_Test Company",
+		)
 
-		# Get HR settings and enable advance holiday reminders
-		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
-		hr_settings.send_holiday_reminders = 1
-		set_proceed_with_frequency_change()
-		hr_settings.frequency = 'Weekly'
-		hr_settings.save()
+		from erpnext.hr.doctype.employee.employee_reminders import get_employees_having_an_event_today
+
+		employees_having_work_anniversary = get_employees_having_an_event_today('work_anniversary')
+		employees = employees_having_work_anniversary.get("_Test Company") or []
+		user_ids = []
+		for entry in employees:
+			user_ids.append(entry.user_id)
+
+		self.assertTrue("test_work_anniversary_2@gmail.com" not in user_ids)
+
+	def test_send_holidays_reminder_in_advance(self):
+		setup_hr_settings('Weekly')
 
 		holidays = get_holidays_for_employee(
 					self.test_employee.get('name'),
@@ -151,32 +184,80 @@
 
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertEqual(len(email_queue), 1)
+		self.assertTrue("Holidays this Week." in email_queue[0].message)
 
 	def test_advance_holiday_reminders_monthly(self):
 		from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_monthly
 
-		# Get HR settings and enable advance holiday reminders
-		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
-		hr_settings.send_holiday_reminders = 1
-		set_proceed_with_frequency_change()
-		hr_settings.frequency = 'Monthly'
-		hr_settings.save()
+		setup_hr_settings('Monthly')
+
+		# disable emp 2, set same holiday list
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Left',
+			'holiday_list': self.test_employee.holiday_list
+		})
 
 		send_reminders_in_advance_monthly()
-
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue(len(email_queue) > 0)
 
+		# even though emp 2 has holiday, non-active employees should not be recipients
+		recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+		self.assertTrue(self.test_employee_2.user_id not in recipients)
+
+		# teardown: enable emp 2
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Active',
+			'holiday_list': self.holiday_list_2.name
+		})
+
 	def test_advance_holiday_reminders_weekly(self):
 		from erpnext.hr.doctype.employee.employee_reminders import send_reminders_in_advance_weekly
 
-		# Get HR settings and enable advance holiday reminders
-		hr_settings = frappe.get_doc("HR Settings", "HR Settings")
-		hr_settings.send_holiday_reminders = 1
-		hr_settings.frequency = 'Weekly'
-		hr_settings.save()
+		setup_hr_settings('Weekly')
+
+		# disable emp 2, set same holiday list
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Left',
+			'holiday_list': self.test_employee.holiday_list
+		})
 
 		send_reminders_in_advance_weekly()
-
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue(len(email_queue) > 0)
+
+		# even though emp 2 has holiday, non-active employees should not be recipients
+		recipients = frappe.db.get_all('Email Queue Recipient', pluck='recipient')
+		self.assertTrue(self.test_employee_2.user_id not in recipients)
+
+		# teardown: enable emp 2
+		frappe.db.set_value('Employee', self.test_employee_2.name, {
+			'status': 'Active',
+			'holiday_list': self.holiday_list_2.name
+		})
+
+	def test_reminder_not_sent_if_no_holdays(self):
+		setup_hr_settings('Monthly')
+
+		# reminder not sent if there are no holidays
+		holidays = get_holidays_for_employee(
+			self.test_employee_2.get('name'),
+			getdate(), getdate() + timedelta(days=3),
+			only_non_weekly=True,
+			raise_exception=False
+		)
+		send_holidays_reminder_in_advance(
+			self.test_employee_2.get('name'),
+			holidays
+		)
+		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
+		self.assertEqual(len(email_queue), 0)
+
+
+def setup_hr_settings(frequency=None):
+	# Get HR settings and enable advance holiday reminders
+	hr_settings = frappe.get_doc("HR Settings", "HR Settings")
+	hr_settings.send_holiday_reminders = 1
+	set_proceed_with_frequency_change()
+	hr_settings.frequency = frequency or 'Weekly'
+	hr_settings.save()
\ No newline at end of file
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 044a5a9..8474bd0 100644
--- a/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
+++ b/erpnext/hr/doctype/employee_boarding_activity/employee_boarding_activity.json
@@ -62,6 +62,7 @@
   },
   {
    "default": "0",
+   "depends_on": "eval:['Employee Onboarding', 'Employee Onboarding Template'].includes(doc.parenttype)",
    "description": "Applicable in the case of Employee Onboarding",
    "fieldname": "required_for_employee_creation",
    "fieldtype": "Check",
@@ -93,7 +94,7 @@
  ],
  "istable": 1,
  "links": [],
- "modified": "2021-07-30 15:55:22.470102",
+ "modified": "2022-01-29 14:05:00.543122",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Boarding Activity",
@@ -102,5 +103,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_group_table/employee_group_table.json b/erpnext/hr/doctype/employee_group_table/employee_group_table.json
index 4e0045c..54eb8c6 100644
--- a/erpnext/hr/doctype/employee_group_table/employee_group_table.json
+++ b/erpnext/hr/doctype/employee_group_table/employee_group_table.json
@@ -27,12 +27,13 @@
    "fetch_from": "employee.user_id",
    "fieldname": "user_id",
    "fieldtype": "Data",
+   "in_list_view": 1,
    "label": "ERPNext User ID",
    "read_only": 1
   }
  ],
  "istable": 1,
- "modified": "2019-06-06 10:41:20.313756",
+ "modified": "2022-02-13 19:44:21.302938",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Group Table",
@@ -42,4 +43,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
index 5d1a024..6fbb54d 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
@@ -3,12 +3,6 @@
 
 frappe.ui.form.on('Employee Onboarding', {
 	setup: function(frm) {
-		frm.add_fetch("employee_onboarding_template", "company", "company");
-		frm.add_fetch("employee_onboarding_template", "department", "department");
-		frm.add_fetch("employee_onboarding_template", "designation", "designation");
-		frm.add_fetch("employee_onboarding_template", "employee_grade", "employee_grade");
-
-
 		frm.set_query("job_applicant", function () {
 			return {
 				filters:{
@@ -71,5 +65,19 @@
 				}
 			});
 		}
+	},
+
+	job_applicant: function(frm) {
+		if (frm.doc.job_applicant) {
+			frappe.db.get_value('Employee', {'job_applicant': frm.doc.job_applicant}, 'name', (r) => {
+				if (r.name) {
+					frm.set_value('employee', r.name);
+				} else {
+					frm.set_value('employee', '');
+				}
+			});
+		} else {
+			frm.set_value('employee', '');
+		}
 	}
 });
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index fd877a6..1d2ea0c 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -92,6 +92,7 @@
    "options": "Employee Onboarding Template"
   },
   {
+   "fetch_from": "employee_onboarding_template.company",
    "fieldname": "company",
    "fieldtype": "Link",
    "label": "Company",
@@ -99,6 +100,7 @@
    "reqd": 1
   },
   {
+   "fetch_from": "employee_onboarding_template.department",
    "fieldname": "department",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -106,6 +108,7 @@
    "options": "Department"
   },
   {
+   "fetch_from": "employee_onboarding_template.designation",
    "fieldname": "designation",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -113,6 +116,7 @@
    "options": "Designation"
   },
   {
+   "fetch_from": "employee_onboarding_template.employee_grade",
    "fieldname": "employee_grade",
    "fieldtype": "Link",
    "label": "Employee Grade",
@@ -170,10 +174,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-07-30 14:55:04.560683",
+ "modified": "2022-01-29 12:33:57.120384",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Onboarding",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -194,6 +199,7 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index eba2a03..a0939a8 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -14,10 +14,15 @@
 class EmployeeOnboarding(EmployeeBoardingController):
 	def validate(self):
 		super(EmployeeOnboarding, self).validate()
+		self.set_employee()
 		self.validate_duplicate_employee_onboarding()
 
+	def set_employee(self):
+		if not self.employee:
+			self.employee = frappe.db.get_value('Employee', {'job_applicant': self.job_applicant}, 'name')
+
 	def validate_duplicate_employee_onboarding(self):
-		emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant})
+		emp_onboarding = frappe.db.exists("Employee Onboarding", {"job_applicant": self.job_applicant, "docstatus": ("!=", 2)})
 		if emp_onboarding and emp_onboarding != self.name:
 			frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
 
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 2d129c8..0fb821d 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -4,7 +4,7 @@
 import unittest
 
 import frappe
-from frappe.utils import getdate
+from frappe.utils import add_days, getdate
 
 from erpnext.hr.doctype.employee_onboarding.employee_onboarding import (
 	IncompleteTaskError,
@@ -35,6 +35,15 @@
 		# boarding status
 		self.assertEqual(onboarding.boarding_status, 'Pending')
 
+		# start and end dates
+		start_date, end_date = frappe.db.get_value('Task', onboarding.activities[0].task, ['exp_start_date', 'exp_end_date'])
+		self.assertEqual(getdate(start_date), getdate(onboarding.boarding_begins_on))
+		self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[0].duration))
+
+		start_date, end_date = frappe.db.get_value('Task', onboarding.activities[1].task, ['exp_start_date', 'exp_end_date'])
+		self.assertEqual(getdate(start_date), add_days(onboarding.boarding_begins_on, onboarding.activities[0].duration))
+		self.assertEqual(getdate(end_date), add_days(start_date, onboarding.activities[1].duration))
+
 		# complete the task
 		project = frappe.get_doc('Project', onboarding.project)
 		for task in frappe.get_all('Task', dict(project=project.name)):
@@ -57,10 +66,7 @@
 		self.assertEqual(employee.employee_name, 'Test Researcher')
 
 	def tearDown(self):
-		for entry in frappe.get_all('Employee Onboarding'):
-			doc = frappe.get_doc('Employee Onboarding', entry.name)
-			doc.cancel()
-			doc.delete()
+		frappe.db.rollback()
 
 
 def get_job_applicant():
@@ -87,23 +93,31 @@
 def create_employee_onboarding():
 	applicant = get_job_applicant()
 	job_offer = get_job_offer(applicant.name)
-	holiday_list = make_holiday_list()
+
+	holiday_list = make_holiday_list('_Test Employee Boarding')
+	holiday_list = frappe.get_doc('Holiday List', holiday_list)
+	holiday_list.holidays = []
+	holiday_list.save()
 
 	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.holiday_list = holiday_list.name
 	onboarding.designation = 'Researcher'
 	onboarding.append('activities', {
 		'activity_name': 'Assign ID Card',
 		'role': 'HR User',
-		'required_for_employee_creation': 1
+		'required_for_employee_creation': 1,
+		'begin_on': 0,
+		'duration': 1
 	})
 	onboarding.append('activities', {
 		'activity_name': 'Assign a laptop',
-		'role': 'HR User'
+		'role': 'HR User',
+		'begin_on': 1,
+		'duration': 1
 	})
 	onboarding.status = 'Pending'
 	onboarding.insert()
diff --git a/erpnext/hr/doctype/exit_interview/exit_interview.py b/erpnext/hr/doctype/exit_interview/exit_interview.py
index 30e19f1..59fb2fd 100644
--- a/erpnext/hr/doctype/exit_interview/exit_interview.py
+++ b/erpnext/hr/doctype/exit_interview/exit_interview.py
@@ -128,4 +128,4 @@
 		message += _('{0} due to missing email information for employee(s): {1}').format(
 			frappe.bold('Sending Failed'), ', '.join(email_failure))
 
-	frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True)
\ No newline at end of file
+	frappe.msgprint(message, title=_('Exit Questionnaire'), indicator='blue', is_minimizable=True, wide=True)
diff --git a/erpnext/hr/doctype/interview/interview.py b/erpnext/hr/doctype/interview/interview.py
index 4bb003d..a3b111c 100644
--- a/erpnext/hr/doctype/interview/interview.py
+++ b/erpnext/hr/doctype/interview/interview.py
@@ -7,7 +7,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import cstr, get_datetime, get_link_to_form
+from frappe.utils import cstr, flt, get_datetime, get_link_to_form
 
 
 class DuplicateInterviewRoundError(frappe.ValidationError):
@@ -18,6 +18,7 @@
 		self.validate_duplicate_interview()
 		self.validate_designation()
 		self.validate_overlap()
+		self.set_average_rating()
 
 	def on_submit(self):
 		if self.status not in ['Cleared', 'Rejected']:
@@ -67,6 +68,13 @@
 			overlapping_details = _('Interview overlaps with {0}').format(get_link_to_form('Interview', overlaps[0][0]))
 			frappe.throw(overlapping_details, title=_('Overlap'))
 
+	def set_average_rating(self):
+		total_rating = 0
+		for entry in self.interview_details:
+			if entry.average_rating:
+				total_rating += entry.average_rating
+
+		self.average_rating = flt(total_rating / len(self.interview_details) if len(self.interview_details) else 0)
 
 	@frappe.whitelist()
 	def reschedule_interview(self, scheduled_on, from_time, to_time):
diff --git a/erpnext/hr/doctype/interview/test_interview.py b/erpnext/hr/doctype/interview/test_interview.py
index 1a2257a..fdb11af 100644
--- a/erpnext/hr/doctype/interview/test_interview.py
+++ b/erpnext/hr/doctype/interview/test_interview.py
@@ -12,6 +12,7 @@
 
 from erpnext.hr.doctype.designation.test_designation import create_designation
 from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError
+from erpnext.hr.doctype.job_applicant.job_applicant import get_interview_details
 from erpnext.hr.doctype.job_applicant.test_job_applicant import create_job_applicant
 
 
@@ -70,6 +71,20 @@
 		email_queue = frappe.db.sql("""select * from `tabEmail Queue`""", as_dict=True)
 		self.assertTrue("Subject: Interview Feedback Reminder" in email_queue[0].message)
 
+	def test_get_interview_details_for_applicant_dashboard(self):
+		job_applicant = create_job_applicant()
+		interview = create_interview_and_dependencies(job_applicant.name)
+
+		details = get_interview_details(job_applicant.name)
+		self.assertEqual(details.get('stars'), 5)
+		self.assertEqual(details.get('interviews').get(interview.name), {
+			'name': interview.name,
+			'interview_round': interview.interview_round,
+			'expected_average_rating': interview.expected_average_rating * 5,
+			'average_rating': interview.average_rating * 5,
+			'status': 'Pending'
+		})
+
 	def tearDown(self):
 		frappe.db.rollback()
 
@@ -106,7 +121,8 @@
 	interview_round = frappe.new_doc("Interview Round")
 	interview_round.round_name = name
 	interview_round.interview_type = create_interview_type()
-	interview_round.expected_average_rating = 4
+	# average rating = 4
+	interview_round.expected_average_rating = 0.8
 	if designation:
 		interview_round.designation = designation
 
diff --git a/erpnext/hr/doctype/interview_feedback/interview_feedback.py b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
index d046458..2ff00c1 100644
--- a/erpnext/hr/doctype/interview_feedback/interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/interview_feedback.py
@@ -57,7 +57,6 @@
 
 	def update_interview_details(self):
 		doc = frappe.get_doc('Interview', self.interview)
-		total_rating = 0
 
 		if self.docstatus == 2:
 			for entry in doc.interview_details:
@@ -72,10 +71,6 @@
 					entry.comments = self.feedback
 					entry.result = self.result
 
-				if entry.average_rating:
-					total_rating += entry.average_rating
-
-		doc.average_rating = flt(total_rating / len(doc.interview_details) if len(doc.interview_details) else 0)
 		doc.save()
 		doc.notify_update()
 
diff --git a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
index d2ec5b9..19c4642 100644
--- a/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
+++ b/erpnext/hr/doctype/interview_feedback/test_interview_feedback.py
@@ -24,7 +24,7 @@
 		create_skill_set(['Leadership'])
 
 		interview_feedback = create_interview_feedback(interview.name, interviewer, skill_ratings)
-		interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 4})
+		interview_feedback.append("skill_assessment", {"skill": 'Leadership', 'rating': 0.8})
 		frappe.set_user(interviewer)
 
 		self.assertRaises(frappe.ValidationError, interview_feedback.save)
@@ -50,7 +50,7 @@
 
 		avg_rating = flt(total_rating / len(feedback_1.skill_assessment) if len(feedback_1.skill_assessment) else 0)
 
-		self.assertEqual(flt(avg_rating, 3), feedback_1.average_rating)
+		self.assertEqual(flt(avg_rating, 2), flt(feedback_1.average_rating, 2))
 
 		avg_on_interview_detail = frappe.db.get_value('Interview Detail', {
 			'parent': feedback_1.interview,
@@ -59,7 +59,7 @@
 		}, 'average_rating')
 
 		# 1. average should be reflected in Interview Detail.
-		self.assertEqual(avg_on_interview_detail, feedback_1.average_rating)
+		self.assertEqual(flt(avg_on_interview_detail, 2), flt(feedback_1.average_rating, 2))
 
 		'''For Second Interviewer Feedback'''
 		interviewer = interview.interview_details[1].interviewer
@@ -97,5 +97,5 @@
 
 	skills = frappe.get_all("Expected Skill Set", filters={"parent": interview_round}, fields = ["skill"])
 	for d in skills:
-		d["rating"] = random.randint(1, 5)
+		d["rating"] = random.random()
 	return skills
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.js b/erpnext/hr/doctype/job_applicant/job_applicant.js
index d7b1c6c..c1e8257 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.js
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.js
@@ -21,9 +21,9 @@
 
 	create_custom_buttons: function(frm) {
 		if (!frm.doc.__islocal && frm.doc.status !== "Rejected" && frm.doc.status !== "Accepted") {
-			frm.add_custom_button(__("Create Interview"), function() {
+			frm.add_custom_button(__("Interview"), function() {
 				frm.events.create_dialog(frm);
-			});
+			}, __("Create"));
 		}
 
 		if (!frm.doc.__islocal) {
@@ -40,10 +40,10 @@
 					frappe.route_options = {
 						"job_applicant": frm.doc.name,
 						"applicant_name": frm.doc.applicant_name,
-						"designation": frm.doc.job_opening,
+						"designation": frm.doc.job_opening || frm.doc.designation,
 					};
 					frappe.new_doc("Job Offer");
-				});
+				}, __("Create"));
 			}
 		}
 	},
@@ -55,13 +55,16 @@
 				job_applicant: frm.doc.name
 			},
 			callback: function(r) {
-				$("div").remove(".form-dashboard-section.custom");
-				frm.dashboard.add_section(
-					frappe.render_template('job_applicant_dashboard', {
-						data: r.message
-					}),
-					__("Interview Summary")
-				);
+				if (r.message) {
+					$("div").remove(".form-dashboard-section.custom");
+					frm.dashboard.add_section(
+						frappe.render_template("job_applicant_dashboard", {
+							data: r.message.interviews,
+							number_of_stars: r.message.stars
+						}),
+						__("Interview Summary")
+					);
+				}
 			}
 		});
 	},
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant.py b/erpnext/hr/doctype/job_applicant/job_applicant.py
index 5b3d9bf..ccc21ce 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/job_applicant.py
@@ -81,8 +81,13 @@
 		fields=["name", "interview_round", "expected_average_rating", "average_rating", "status"]
 	)
 	interview_detail_map = {}
+	meta = frappe.get_meta("Interview")
+	number_of_stars = meta.get_options("expected_average_rating") or 5
 
 	for detail in interview_details:
+		detail.expected_average_rating = detail.expected_average_rating * number_of_stars if detail.expected_average_rating else 0
+		detail.average_rating = detail.average_rating * number_of_stars if detail.average_rating else 0
+
 		interview_detail_map[detail.name] = detail
 
-	return interview_detail_map
+	return {"interviews": interview_detail_map, "stars": number_of_stars}
diff --git a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html
index c286787..734b2fe 100644
--- a/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html
+++ b/erpnext/hr/doctype/job_applicant/job_applicant_dashboard.html
@@ -17,24 +17,33 @@
 				<td class="text-left"> {%= key %} </td>
 				<td class="text-left"> {%= value["interview_round"] %} </td>
 				<td class="text-left"> {%= value["status"] %} </td>
-				<td class="text-left">
-					{% for (i = 0; i < value["expected_average_rating"]; i++) { %}
-						<span class="fa fa-star " style="color: #F6C35E;"></span>
-					{% } %}
-					{% for (i = 0; i < (5-value["expected_average_rating"]); i++) { %}
-					<span class="fa fa-star " style="color: #E7E9EB;"></span>
-					{% } %}
-				</td>
-				<td class="text-left">
-					{% if(value["average_rating"]){ %}
-						{% for (i = 0; i < value["average_rating"]; i++) { %}
-							<span class="fa fa-star " style="color: #F6C35E;"></span>
-						{% } %}
-						{% for (i = 0; i < (5-value["average_rating"]); i++) { %}
-						<span class="fa fa-star " style="color: #E7E9EB;"></span>
-						{% } %}
-					{% } %}
-				</td>
+				{% let right_class = ''; %}
+				{% let left_class = ''; %}
+
+				{% $.each([value["expected_average_rating"], value["average_rating"]], (_, val) => { %}
+					<td class="text-left">
+						<div class="rating">
+							{% for (let i = 1; i <= number_of_stars; i++) { %}
+								{% if (i <= val) { %}
+									{% right_class = 'star-click'; %}
+								{% } else { %}
+									{% right_class = ''; %}
+								{% } %}
+
+								{% if ((i <= val) || ((i - 0.5) == val)) { %}
+									{% left_class = 'star-click'; %}
+								{% } else { %}
+									{% left_class = ''; %}
+								{% } %}
+
+								<svg class="icon icon-md" data-rating={{i}} viewBox="0 0 24 24" fill="none">
+									<path class="right-half {{ right_class }}" d="M11.9987 3.00011C12.177 3.00011 12.3554 3.09303 12.4471 3.27888L14.8213 8.09112C14.8941 8.23872 15.0349 8.34102 15.1978 8.3647L20.5069 9.13641C20.917 9.19602 21.0807 9.69992 20.7841 9.9892L16.9421 13.7354C16.8243 13.8503 16.7706 14.0157 16.7984 14.1779L17.7053 19.4674C17.7753 19.8759 17.3466 20.1874 16.9798 19.9945L12.2314 17.4973C12.1586 17.459 12.0786 17.4398 11.9987 17.4398V3.00011Z" fill="var(--star-fill)" stroke="var(--star-fill)"/>
+									<path class="left-half {{ left_class }}" d="M11.9987 3.00011C11.8207 3.00011 11.6428 3.09261 11.5509 3.27762L9.15562 8.09836C9.08253 8.24546 8.94185 8.34728 8.77927 8.37075L3.42887 9.14298C3.01771 9.20233 2.85405 9.70811 3.1525 9.99707L7.01978 13.7414C7.13858 13.8564 7.19283 14.0228 7.16469 14.1857L6.25116 19.4762C6.18071 19.8842 6.6083 20.1961 6.97531 20.0045L11.7672 17.5022C11.8397 17.4643 11.9192 17.4454 11.9987 17.4454V3.00011Z" fill="var(--star-fill)" stroke="var(--star-fill)"/>
+								</svg>
+							{% } %}
+						</div>
+					</td>
+				{% }); %}
 			</tr>
 		{% } %}
 	</tbody>
diff --git a/erpnext/hr/doctype/job_offer/job_offer.py b/erpnext/hr/doctype/job_offer/job_offer.py
index 39f4719..072fc73 100644
--- a/erpnext/hr/doctype/job_offer/job_offer.py
+++ b/erpnext/hr/doctype/job_offer/job_offer.py
@@ -78,6 +78,7 @@
 				"doctype": "Employee",
 				"field_map": {
 					"applicant_name": "employee_name",
+					"offer_date": "scheduled_confirmation_date"
 				}}
 		}, target_doc, set_missing_values)
 	return doc
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.json b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
index 52ee463..9ecbe01 100644
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.json
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.json
@@ -237,10 +237,11 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-10-01 15:28:26.335104",
+ "modified": "2022-01-18 19:15:53.262536",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Allocation",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -278,5 +279,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
- "timeline_field": "employee"
-}
+ "states": [],
+ "timeline_field": "employee",
+ "title_field": "employee_name"
+}
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 1dc5b31..70250f5 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -22,6 +22,7 @@
 from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
 from erpnext.hr.utils import (
+	get_holiday_dates_for_employee,
 	get_leave_period,
 	set_employee_name,
 	share_doc_with_approver,
@@ -159,33 +160,57 @@
 				.format(formatdate(future_allocation[0].from_date), future_allocation[0].name))
 
 	def update_attendance(self):
-		if self.status == "Approved":
-			for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
-				date = dt.strftime("%Y-%m-%d")
-				status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
-				attendance_name = frappe.db.exists('Attendance', dict(employee = self.employee,
-					attendance_date = date, docstatus = ('!=', 2)))
+		if self.status != "Approved":
+			return
 
+		holiday_dates = []
+		if not frappe.db.get_value("Leave Type", self.leave_type, "include_holiday"):
+			holiday_dates = get_holiday_dates_for_employee(self.employee, self.from_date, self.to_date)
+
+		for dt in daterange(getdate(self.from_date), getdate(self.to_date)):
+			date = dt.strftime("%Y-%m-%d")
+			attendance_name = frappe.db.exists("Attendance", dict(employee = self.employee,
+				attendance_date = date, docstatus = ('!=', 2)))
+
+			# don't mark attendance for holidays
+			# if leave type does not include holidays within leaves as leaves
+			if date in holiday_dates:
 				if attendance_name:
-					# update existing attendance, change absent to on leave
-					doc = frappe.get_doc('Attendance', attendance_name)
-					if doc.status != status:
-						doc.db_set('status', status)
-						doc.db_set('leave_type', self.leave_type)
-						doc.db_set('leave_application', self.name)
-				else:
-					# make new attendance and submit it
-					doc = frappe.new_doc("Attendance")
-					doc.employee = self.employee
-					doc.employee_name = self.employee_name
-					doc.attendance_date = date
-					doc.company = self.company
-					doc.leave_type = self.leave_type
-					doc.leave_application = self.name
-					doc.status = status
-					doc.flags.ignore_validate = True
-					doc.insert(ignore_permissions=True)
-					doc.submit()
+					# cancel and delete existing attendance for holidays
+					attendance = frappe.get_doc("Attendance", attendance_name)
+					attendance.flags.ignore_permissions = True
+					if attendance.docstatus == 1:
+						attendance.cancel()
+					frappe.delete_doc("Attendance", attendance_name, force=1)
+				continue
+
+			self.create_or_update_attendance(attendance_name, date)
+
+	def create_or_update_attendance(self, attendance_name, date):
+		status = "Half Day" if self.half_day_date and getdate(date) == getdate(self.half_day_date) else "On Leave"
+
+		if attendance_name:
+			# update existing attendance, change absent to on leave
+			doc = frappe.get_doc('Attendance', attendance_name)
+			if doc.status != status:
+				doc.db_set({
+					'status': status,
+					'leave_type': self.leave_type,
+					'leave_application': self.name
+				})
+		else:
+			# make new attendance and submit it
+			doc = frappe.new_doc("Attendance")
+			doc.employee = self.employee
+			doc.employee_name = self.employee_name
+			doc.attendance_date = date
+			doc.company = self.company
+			doc.leave_type = self.leave_type
+			doc.leave_application = self.name
+			doc.status = status
+			doc.flags.ignore_validate = True
+			doc.insert(ignore_permissions=True)
+			doc.submit()
 
 	def cancel_attendance(self):
 		if self.docstatus == 2:
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index f73d3e5..6d27f4a 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -5,7 +5,16 @@
 
 import frappe
 from frappe.permissions import clear_user_permissions_for_doctype
-from frappe.utils import add_days, add_months, getdate, nowdate
+from frappe.utils import (
+	add_days,
+	add_months,
+	get_first_day,
+	get_last_day,
+	get_year_ending,
+	get_year_start,
+	getdate,
+	nowdate,
+)
 
 from erpnext.hr.doctype.employee.test_employee import make_employee
 from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
@@ -19,6 +28,10 @@
 	create_assignment_for_multiple_employees,
 )
 from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
+	make_holiday_list,
+	make_leave_application,
+)
 
 test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
 
@@ -61,13 +74,13 @@
 		for dt in ["Leave Application", "Leave Allocation", "Salary Slip", "Leave Ledger Entry"]:
 			frappe.db.sql("DELETE FROM `tab%s`" % dt) #nosec
 
-	@classmethod
-	def setUpClass(cls):
+		frappe.set_user("Administrator")
 		set_leave_approver()
+
 		frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
 
 	def tearDown(self):
-		frappe.set_user("Administrator")
+		frappe.db.rollback()
 
 	def _clear_roles(self):
 		frappe.db.sql("""delete from `tabHas Role` where parent in
@@ -106,6 +119,76 @@
 		for d in ('2018-01-01', '2018-01-02', '2018-01-03'):
 			self.assertTrue(getdate(d) in dates)
 
+	def test_attendance_for_include_holidays(self):
+		# Case 1: leave type with 'Include holidays within leaves as leaves' enabled
+		frappe.delete_doc_if_exists("Leave Type", "Test Include Holidays", force=1)
+		leave_type = frappe.get_doc(dict(
+			leave_type_name="Test Include Holidays",
+			doctype="Leave Type",
+			include_holiday=True
+		)).insert()
+
+		date = getdate()
+		make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+
+		holiday_list = make_holiday_list()
+		employee = get_employee()
+		frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
+		first_sunday = get_first_sunday(holiday_list)
+
+		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
+		leave_application.reload()
+		self.assertEqual(leave_application.total_leave_days, 4)
+		self.assertEqual(frappe.db.count('Attendance', {'leave_application': leave_application.name}), 4)
+
+		leave_application.cancel()
+
+	def test_attendance_update_for_exclude_holidays(self):
+		# Case 2: leave type with 'Include holidays within leaves as leaves' disabled
+		frappe.delete_doc_if_exists("Leave Type", "Test Do Not Include Holidays", force=1)
+		leave_type = frappe.get_doc(dict(
+			leave_type_name="Test Do Not Include Holidays",
+			doctype="Leave Type",
+			include_holiday=False
+		)).insert()
+
+		date = getdate()
+		make_allocation_record(leave_type=leave_type.name, from_date=get_year_start(date), to_date=get_year_ending(date))
+
+		holiday_list = make_holiday_list()
+		employee = get_employee()
+		frappe.db.set_value("Company", employee.company, "default_holiday_list", holiday_list)
+		first_sunday = get_first_sunday(holiday_list)
+
+		# already marked attendance on a holiday should be deleted in this case
+		config = {
+			"doctype": "Attendance",
+			"employee": employee.name,
+			"status": "Present"
+		}
+		attendance_on_holiday = frappe.get_doc(config)
+		attendance_on_holiday.attendance_date = first_sunday
+		attendance_on_holiday.flags.ignore_validate = True
+		attendance_on_holiday.save()
+
+		# already marked attendance on a non-holiday should be updated
+		attendance = frappe.get_doc(config)
+		attendance.attendance_date = add_days(first_sunday, 3)
+		attendance.flags.ignore_validate = True
+		attendance.save()
+
+		leave_application = make_leave_application(employee.name, first_sunday, add_days(first_sunday, 3), leave_type.name)
+		leave_application.reload()
+		# holiday should be excluded while marking attendance
+		self.assertEqual(leave_application.total_leave_days, 3)
+		self.assertEqual(frappe.db.count("Attendance", {"leave_application": leave_application.name}), 3)
+
+		# attendance on holiday deleted
+		self.assertFalse(frappe.db.exists("Attendance", attendance_on_holiday.name))
+
+		# attendance on non-holiday updated
+		self.assertEqual(frappe.db.get_value("Attendance", attendance.name, "status"), "On Leave")
+
 	def test_block_list(self):
 		self._clear_roles()
 
@@ -241,7 +324,13 @@
 		leave_period = get_leave_period()
 		today = nowdate()
 		holiday_list = 'Test Holiday List for Optional Holiday'
-		optional_leave_date = add_days(today, 7)
+		employee = get_employee()
+
+		default_holiday_list = make_holiday_list()
+		frappe.db.set_value("Company", employee.company, "default_holiday_list", default_holiday_list)
+		first_sunday = get_first_sunday(default_holiday_list)
+
+		optional_leave_date = add_days(first_sunday, 1)
 
 		if not frappe.db.exists('Holiday List', holiday_list):
 			frappe.get_doc(dict(
@@ -253,7 +342,6 @@
 					dict(holiday_date = optional_leave_date, description = 'Test')
 				]
 			)).insert()
-		employee = get_employee()
 
 		frappe.db.set_value('Leave Period', leave_period.name, 'optional_holiday_list', holiday_list)
 		leave_type = 'Test Optional Type'
@@ -266,7 +354,7 @@
 
 		allocate_leaves(employee, leave_period, leave_type, 10)
 
-		date = add_days(today, 6)
+		date = add_days(first_sunday, 2)
 
 		leave_application = frappe.get_doc(dict(
 			doctype = 'Leave Application',
@@ -443,6 +531,7 @@
 
 		leave_policy = frappe.get_doc({
 			"doctype": "Leave Policy",
+			"title": "Test Leave Policy",
 			"leave_policy_details": [{"leave_type": leave_type, "annual_allocation": 6}]
 		}).insert()
 
@@ -457,7 +546,7 @@
 		from erpnext.hr.utils import allocate_earned_leaves
 		i = 0
 		while(i<14):
-			allocate_earned_leaves()
+			allocate_earned_leaves(ignore_duplicates=True)
 			i += 1
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 6)
 
@@ -465,7 +554,7 @@
 		frappe.db.set_value('Leave Type', leave_type, 'max_leaves_allowed', 0)
 		i = 0
 		while(i<6):
-			allocate_earned_leaves()
+			allocate_earned_leaves(ignore_duplicates=True)
 			i += 1
 		self.assertEqual(get_leave_balance_on(employee.name, leave_type, nowdate()), 9)
 
@@ -636,13 +725,13 @@
 			carry_forward=1)
 		leave_allocation.submit()
 
-def make_allocation_record(employee=None, leave_type=None):
+def make_allocation_record(employee=None, leave_type=None, from_date=None, to_date=None):
 	allocation = frappe.get_doc({
 		"doctype": "Leave Allocation",
 		"employee": employee or "_T-Employee-00001",
 		"leave_type": leave_type or "_Test Leave Type",
-		"from_date": "2013-01-01",
-		"to_date": "2019-12-31",
+		"from_date": from_date or "2013-01-01",
+		"to_date": to_date or "2019-12-31",
 		"new_leaves_allocated": 30
 	})
 
@@ -691,3 +780,16 @@
 	}).insert()
 
 	allocate_leave.submit()
+
+
+def get_first_sunday(holiday_list):
+	month_start_date = get_first_day(nowdate())
+	month_end_date = get_last_day(nowdate())
+	first_sunday = frappe.db.sql("""
+		select holiday_date from `tabHoliday`
+		where parent = %s
+			and holiday_date between %s and %s
+		order by holiday_date
+	""", (holiday_list, month_start_date, month_end_date))[0][0]
+
+	return first_sunday
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
index 1f6c03f..cc4e53e 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -154,10 +154,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:32:55.492327",
+ "modified": "2022-01-18 19:16:52.414356",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Encashment",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -218,7 +219,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee,employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy/leave_policy.json b/erpnext/hr/doctype/leave_policy/leave_policy.json
index 373095d..6ac8f20 100644
--- a/erpnext/hr/doctype/leave_policy/leave_policy.json
+++ b/erpnext/hr/doctype/leave_policy/leave_policy.json
@@ -1,131 +1,55 @@
 {
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
  "autoname": "HR-LPOL-.YYYY.-.#####",
- "beta": 0,
  "creation": "2018-04-13 16:06:19.507624",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
  "editable_grid": 1,
  "engine": "InnoDB",
+ "field_order": [
+  "title",
+  "leave_allocations_section",
+  "leave_policy_details",
+  "amended_from"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
    "allow_in_quick_entry": 1,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "leave_allocations_section",
    "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": "Leave Allocations",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Leave Allocations"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "leave_policy_details",
    "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": "Leave Policy Details",
-   "length": 0,
-   "no_copy": 0,
    "options": "Leave Policy Detail",
-   "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
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "amended_from",
    "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": "Amended From",
-   "length": 0,
    "no_copy": 1,
    "options": "Leave Policy",
-   "permlevel": 0,
    "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,
-   "translatable": 0,
-   "unique": 0
+   "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "fieldname": "title",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Title",
+   "reqd": 1
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
  "is_submittable": 1,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-29 08:42:53.363088",
+ "links": [],
+ "modified": "2022-01-19 13:07:40.556500",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Policy",
- "name_case": "",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -135,14 +59,10 @@
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "System Manager",
-   "set_user_permissions": 0,
    "share": 1,
    "submit": 1,
    "write": 1
@@ -154,14 +74,10 @@
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR Manager",
-   "set_user_permissions": 0,
    "share": 1,
    "submit": 1,
    "write": 1
@@ -173,26 +89,19 @@
    "delete": 1,
    "email": 1,
    "export": 1,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
    "report": 1,
    "role": "HR User",
-   "set_user_permissions": 0,
    "share": 1,
    "submit": 1,
    "write": 1
   }
  ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
+ "search_fields": "title",
  "sort_field": "modified",
  "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "title_field": "title",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/leave_policy/test_leave_policy.py b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
index 3dbbef8..a4b8af7 100644
--- a/erpnext/hr/doctype/leave_policy/test_leave_policy.py
+++ b/erpnext/hr/doctype/leave_policy/test_leave_policy.py
@@ -24,6 +24,7 @@
 	args = frappe._dict(args)
 	return frappe.get_doc({
 		"doctype": "Leave Policy",
+		"title": "Test Leave Policy",
 		"leave_policy_details": [{
 			"leave_type": args.leave_type or "_Test Leave Type",
 			"annual_allocation": args.annual_allocation or 10
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index 355370f..c11a821 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -8,11 +8,10 @@
 import frappe
 from frappe import _, bold
 from frappe.model.document import Document
-from frappe.utils import date_diff, flt, formatdate, get_datetime, getdate
+from frappe.utils import date_diff, flt, formatdate, get_last_day, getdate
 
 
 class LeavePolicyAssignment(Document):
-
 	def validate(self):
 		self.validate_policy_assignment_overlap()
 		self.set_dates()
@@ -94,10 +93,12 @@
 			new_leaves_allocated = 0
 
 		elif leave_type_details.get(leave_type).is_earned_leave == 1:
-			if self.assignment_based_on == "Leave Period":
-				new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining)
-			else:
+			if not self.assignment_based_on:
 				new_leaves_allocated = 0
+			else:
+				# get leaves for past months if assignment is based on Leave Period / Joining Date
+				new_leaves_allocated = self.get_leaves_for_passed_months(leave_type, new_leaves_allocated, leave_type_details, date_of_joining)
+
 		# Calculate leaves at pro-rata basis for employees joining after the beginning of the given leave period
 		elif getdate(date_of_joining) > getdate(self.effective_from):
 			remaining_period = ((date_diff(self.effective_to, date_of_joining) + 1) / (date_diff(self.effective_to, self.effective_from) + 1))
@@ -108,21 +109,24 @@
 	def get_leaves_for_passed_months(self, leave_type, new_leaves_allocated, leave_type_details, date_of_joining):
 		from erpnext.hr.utils import get_monthly_earned_leave
 
-		current_month = get_datetime().month
-		current_year = get_datetime().year
+		current_date = frappe.flags.current_date or getdate()
+		if current_date > getdate(self.effective_to):
+			current_date = getdate(self.effective_to)
 
-		from_date = frappe.db.get_value("Leave Period", self.leave_period, "from_date")
-		if getdate(date_of_joining) > getdate(from_date):
-			from_date = date_of_joining
-
-		from_date_month = get_datetime(from_date).month
-		from_date_year = get_datetime(from_date).year
+		from_date = getdate(self.effective_from)
+		if getdate(date_of_joining) > from_date:
+			from_date = getdate(date_of_joining)
 
 		months_passed = 0
-		if current_year == from_date_year and current_month > from_date_month:
-			months_passed = current_month - from_date_month
-		elif current_year > from_date_year:
-			months_passed = (12 - from_date_month) + current_month
+		based_on_doj = leave_type_details.get(leave_type).based_on_date_of_joining
+
+		if current_date.year == from_date.year and current_date.month >= from_date.month:
+			months_passed = current_date.month - from_date.month
+			months_passed = add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj)
+
+		elif current_date.year > from_date.year:
+			months_passed = (12 - from_date.month) + current_date.month
+			months_passed = add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj)
 
 		if months_passed > 0:
 			monthly_earned_leave = get_monthly_earned_leave(new_leaves_allocated,
@@ -134,6 +138,23 @@
 		return new_leaves_allocated
 
 
+def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj):
+	date = getdate(frappe.flags.current_date) or getdate()
+
+	if based_on_doj:
+		# if leave type allocation is based on DOJ, and the date of assignment creation is same as DOJ,
+		# then the month should be considered
+		if date.day == date_of_joining.day:
+			months_passed += 1
+	else:
+		last_day_of_month = get_last_day(date)
+		# if its the last day of the month, then that month should be considered
+		if last_day_of_month == date:
+			months_passed += 1
+
+	return months_passed
+
+
 @frappe.whitelist()
 def create_assignment_for_multiple_employees(employees, data):
 
@@ -168,7 +189,7 @@
 def get_leave_type_details():
 	leave_type_details = frappe._dict()
 	leave_types = frappe.get_all("Leave Type",
-		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory",
+		fields=["name", "is_lwp", "is_earned_leave", "is_compensatory", "based_on_date_of_joining",
 			"is_carry_forward", "expire_carry_forwarded_leaves_after_days", "earned_leave_frequency", "rounding"])
 	for d in leave_types:
 		leave_type_details.setdefault(d.name, d)
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index 8953a51..a19ddce 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -4,7 +4,7 @@
 import unittest
 
 import frappe
-from frappe.utils import add_months, get_first_day, getdate
+from frappe.utils import add_months, get_first_day, get_last_day, getdate
 
 from erpnext.hr.doctype.leave_application.test_leave_application import (
 	get_employee,
@@ -20,36 +20,31 @@
 class TestLeavePolicyAssignment(unittest.TestCase):
 	def setUp(self):
 		for doctype in ["Leave Period", "Leave Application", "Leave Allocation", "Leave Policy Assignment", "Leave Ledger Entry"]:
-			frappe.db.sql("delete from `tab{0}`".format(doctype)) #nosec
+			frappe.db.delete(doctype)
+
+		employee = get_employee()
+		self.original_doj = employee.date_of_joining
+		self.employee = employee
 
 	def test_grant_leaves(self):
 		leave_period = get_leave_period()
-		employee = get_employee()
-
-		# create the leave policy with leave type "_Test Leave Type", allocation = 10
+		# allocation = 10
 		leave_policy = create_leave_policy()
 		leave_policy.submit()
 
-
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
-
-		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
-		leave_policy_assignment_doc.reload()
-
-		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+		self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
 
 		leave_allocation = frappe.get_list("Leave Allocation", filters={
-			"employee": employee.name,
+			"employee": self.employee.name,
 			"leave_policy":leave_policy.name,
 			"leave_policy_assignment": leave_policy_assignments[0],
 			"docstatus": 1})[0]
-
 		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
 
 		self.assertEqual(leave_alloc_doc.new_leaves_allocated, 10)
@@ -61,62 +56,46 @@
 
 	def test_allow_to_grant_all_leave_after_cancellation_of_every_leave_allocation(self):
 		leave_period = get_leave_period()
-		employee = get_employee()
-
 		# create the leave policy with leave type "_Test Leave Type", allocation = 10
 		leave_policy = create_leave_policy()
 		leave_policy.submit()
 
-
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
-
-		leave_policy_assignment_doc = frappe.get_doc("Leave Policy Assignment", leave_policy_assignments[0])
-		leave_policy_assignment_doc.reload()
-
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		# every leave is allocated no more leave can be granted now
-		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 1)
-
+		self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 1)
 		leave_allocation = frappe.get_list("Leave Allocation", filters={
-			"employee": employee.name,
+			"employee": self.employee.name,
 			"leave_policy":leave_policy.name,
 			"leave_policy_assignment": leave_policy_assignments[0],
 			"docstatus": 1})[0]
 
 		leave_alloc_doc = frappe.get_doc("Leave Allocation", leave_allocation)
-
-		# User all allowed to grant leave when there is no allocation against assignment
 		leave_alloc_doc.cancel()
 		leave_alloc_doc.delete()
-
-		leave_policy_assignment_doc.reload()
-
-
-		# User are now allowed to grant leave
-		self.assertEqual(leave_policy_assignment_doc.leaves_allocated, 0)
+		self.assertEqual(frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "leaves_allocated"), 0)
 
 	def test_earned_leave_allocation(self):
 		leave_period = create_leave_period("Test Earned Leave Period")
-		employee = get_employee()
 		leave_type = create_earned_leave_type("Test Earned Leave")
 
 		leave_policy = frappe.get_doc({
 			"doctype": "Leave Policy",
+			"title": "Test Leave Policy",
 			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 6}]
-		}).insert()
+		}).submit()
 
 		data = {
 			"assignment_based_on": "Leave Period",
 			"leave_policy": leave_policy.name,
 			"leave_period": leave_period.name
 		}
-		leave_policy_assignments = create_assignment_for_multiple_employees([employee.name], frappe._dict(data))
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
 
 		# leaves allocated should be 0 since it is an earned leave and allocation happens via scheduler based on set frequency
 		leaves_allocated = frappe.db.get_value("Leave Allocation", {
@@ -124,11 +103,200 @@
 		}, "total_leaves_allocated")
 		self.assertEqual(leaves_allocated, 0)
 
+	def test_earned_leave_alloc_for_passed_months_based_on_leave_period(self):
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -1)))
+
+		# Case 1: assignment created one month after the leave period, should allocate 1 leave
+		frappe.flags.current_date = get_first_day(getdate())
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 1)
+
+	def test_earned_leave_alloc_for_passed_months_on_month_end_based_on_leave_period(self):
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
+		# Case 2: assignment created on the last day of the leave period's latter month
+		# should allocate 1 leave for current month even though the month has not ended
+		# since the daily job might have already executed
+		frappe.flags.current_date = get_last_day(getdate())
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+		# if the daily job is not completed yet, there is another check present
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+	def test_earned_leave_alloc_for_passed_months_with_cf_leaves_based_on_leave_period(self):
+		from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
+
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)))
+		# initial leave allocation = 5
+		leave_allocation = create_leave_allocation(employee=self.employee.name, employee_name=self.employee.employee_name, leave_type="Test Earned Leave",
+			from_date=add_months(getdate(), -12), to_date=add_months(getdate(), -3), new_leaves_allocated=5, carry_forward=0)
+		leave_allocation.submit()
+
+		# Case 3: assignment created on the last day of the leave period's latter month with carry forwarding
+		frappe.flags.current_date = get_last_day(add_months(getdate(), -1))
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name,
+			"carry_forward": 1
+		}
+		# carry forwarded leaves = 5, 3 leaves allocated for passed months
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+
+		details = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, ["total_leaves_allocated", "new_leaves_allocated", "unused_leaves", "name"], as_dict=True)
+		self.assertEqual(details.new_leaves_allocated, 2)
+		self.assertEqual(details.unused_leaves, 5)
+		self.assertEqual(details.total_leaves_allocated, 7)
+
+		# if the daily job is not completed yet, there is another check present
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import is_earned_leave_already_allocated
+		frappe.flags.current_date = get_last_day(getdate())
+
+		allocation = frappe.get_doc("Leave Allocation", details.name)
+		# 1 leave is still pending to be allocated, irrespective of carry forwarded leaves
+		self.assertFalse(is_earned_leave_already_allocated(allocation, leave_policy.leave_policy_details[0].annual_allocation))
+
+	def test_earned_leave_alloc_for_passed_months_based_on_joining_date(self):
+		# tests leave alloc for earned leaves for assignment based on joining date in policy assignment
+		leave_type = create_earned_leave_type("Test Earned Leave")
+		leave_policy = frappe.get_doc({
+			"doctype": "Leave Policy",
+			"title": "Test Leave Policy",
+			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
+		}).submit()
+
+		# joining date set to 2 months back
+		self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
+		self.employee.save()
+
+		# assignment created on the last day of the current month
+		frappe.flags.current_date = get_last_day(getdate())
+		data = {
+			"assignment_based_on": "Joining Date",
+			"leave_policy": leave_policy.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+		self.assertEqual(effective_from, self.employee.date_of_joining)
+		self.assertEqual(leaves_allocated, 3)
+
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		frappe.flags.current_date = get_last_day(getdate())
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+	def test_grant_leaves_on_doj_for_earned_leaves_based_on_leave_period(self):
+		# tests leave alloc based on leave period for earned leaves with "based on doj" configuration in leave type
+		leave_period, leave_policy = setup_leave_period_and_policy(get_first_day(add_months(getdate(), -2)), based_on_doj=True)
+
+		# joining date set to 2 months back
+		self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
+		self.employee.save()
+
+		# assignment created on the same day of the current month, should allocate leaves including the current month
+		frappe.flags.current_date = get_first_day(getdate())
+
+		data = {
+			"assignment_based_on": "Leave Period",
+			"leave_policy": leave_policy.name,
+			"leave_period": leave_period.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+		# if the daily job is not completed yet, there is another check present
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		frappe.flags.current_date = get_first_day(getdate())
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {
+			"leave_policy_assignment": leave_policy_assignments[0]
+		}, "total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
+	def test_grant_leaves_on_doj_for_earned_leaves_based_on_joining_date(self):
+		# tests leave alloc based on joining date for earned leaves with "based on doj" configuration in leave type
+		leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj=True)
+		leave_policy = frappe.get_doc({
+			"doctype": "Leave Policy",
+			"title": "Test Leave Policy",
+			"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
+		}).submit()
+
+		# joining date set to 2 months back
+		# leave should be allocated for current month too since this day is same as the joining day
+		self.employee.date_of_joining = get_first_day(add_months(getdate(), -2))
+		self.employee.save()
+
+		# assignment created on the first day of the current month
+		frappe.flags.current_date = get_first_day(getdate())
+		data = {
+			"assignment_based_on": "Joining Date",
+			"leave_policy": leave_policy.name
+		}
+		leave_policy_assignments = create_assignment_for_multiple_employees([self.employee.name], frappe._dict(data))
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		effective_from = frappe.db.get_value("Leave Policy Assignment", leave_policy_assignments[0], "effective_from")
+		self.assertEqual(effective_from, self.employee.date_of_joining)
+		self.assertEqual(leaves_allocated, 3)
+
+		# to ensure leave is not already allocated to avoid duplication
+		from erpnext.hr.utils import allocate_earned_leaves
+		frappe.flags.current_date = get_first_day(getdate())
+		allocate_earned_leaves()
+
+		leaves_allocated = frappe.db.get_value("Leave Allocation", {"leave_policy_assignment": leave_policy_assignments[0]},
+			"total_leaves_allocated")
+		self.assertEqual(leaves_allocated, 3)
+
 	def tearDown(self):
 		frappe.db.rollback()
+		frappe.db.set_value("Employee", self.employee.name, "date_of_joining", self.original_doj)
+		frappe.flags.current_date = None
 
 
-def create_earned_leave_type(leave_type):
+def create_earned_leave_type(leave_type, based_on_doj=False):
 	frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
 
 	return frappe.get_doc(dict(
@@ -137,13 +305,15 @@
 		is_earned_leave=1,
 		earned_leave_frequency="Monthly",
 		rounding=0.5,
-		max_leaves_allowed=6
+		is_carry_forward=1,
+		based_on_date_of_joining=based_on_doj
 	)).insert()
 
 
-def create_leave_period(name):
+def create_leave_period(name, start_date=None):
 	frappe.delete_doc_if_exists("Leave Period", name, force=1)
-	start_date = get_first_day(getdate())
+	if not start_date:
+		start_date = get_first_day(getdate())
 
 	return frappe.get_doc(dict(
 		name=name,
@@ -152,4 +322,17 @@
 		to_date=add_months(start_date, 12),
 		company="_Test Company",
 		is_active=1
-	)).insert()
\ No newline at end of file
+	)).insert()
+
+
+def setup_leave_period_and_policy(start_date, based_on_doj=False):
+	leave_type = create_earned_leave_type("Test Earned Leave", based_on_doj)
+	leave_period = create_leave_period("Test Earned Leave Period",
+		start_date=start_date)
+	leave_policy = frappe.get_doc({
+		"doctype": "Leave Policy",
+		"title": "Test Leave Policy",
+		"leave_policy_details": [{"leave_type": leave_type.name, "annual_allocation": 12}]
+	}).insert()
+
+	return leave_period, leave_policy
\ No newline at end of file
diff --git a/erpnext/hr/doctype/training_feedback/training_feedback.json b/erpnext/hr/doctype/training_feedback/training_feedback.json
index cd967d5..ebf5a50 100644
--- a/erpnext/hr/doctype/training_feedback/training_feedback.json
+++ b/erpnext/hr/doctype/training_feedback/training_feedback.json
@@ -1,443 +1,144 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "HR-TRF-.YYYY.-.#####", 
- "beta": 0, 
- "creation": "2016-08-08 06:35:34.158568", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
+ "actions": [],
+ "autoname": "HR-TRF-.YYYY.-.#####",
+ "creation": "2016-08-08 06:35:34.158568",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "employee",
+  "employee_name",
+  "department",
+  "course",
+  "column_break_3",
+  "training_event",
+  "event_name",
+  "trainer_name",
+  "section_break_6",
+  "feedback",
+  "amended_from"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "employee", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 1, 
-   "in_list_view": 0, 
-   "in_standard_filter": 1, 
-   "label": "Employee", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Employee", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "employee",
+   "fieldtype": "Link",
+   "in_global_search": 1,
+   "in_standard_filter": 1,
+   "label": "Employee",
+   "options": "Employee",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fetch_from": "employee.employee_name", 
-   "fieldname": "employee_name", 
-   "fieldtype": "Read Only", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 1, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Employee Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "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
-  }, 
+   "fetch_from": "employee.employee_name",
+   "fieldname": "employee_name",
+   "fieldtype": "Read Only",
+   "in_global_search": 1,
+   "label": "Employee Name"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fetch_from": "employee.department", 
-   "fieldname": "department", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Department", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Department", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fetch_from": "employee.department",
+   "fieldname": "department",
+   "fieldtype": "Link",
+   "label": "Department",
+   "options": "Department",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fetch_from": "training_event.course", 
-   "fieldname": "course", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Course", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Course", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fetch_from": "training_event.course",
+   "fieldname": "course",
+   "fieldtype": "Link",
+   "label": "Course",
+   "options": "Course",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_3", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "training_event", 
-   "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": 1, 
-   "label": "Training Event", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Training Event", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "training_event",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Training Event",
+   "options": "Training Event",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fetch_from": "training_event.event_name", 
-   "fieldname": "event_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": "Event Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fetch_from": "training_event.event_name",
+   "fieldname": "event_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Event Name",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fetch_from": "training_event.trainer_name", 
-   "fieldname": "trainer_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": "Trainer Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fetch_from": "training_event.trainer_name",
+   "fieldname": "trainer_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Trainer Name",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_6", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "feedback", 
-   "fieldtype": "Text", 
-   "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": "Feedback", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "feedback",
+   "fieldtype": "Text",
+   "label": "Feedback",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amended_from", 
-   "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": "Amended From", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "Training Feedback", 
-   "permlevel": 0, 
-   "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, 
-   "translatable": 0, 
-   "unique": 0
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Training Feedback",
+   "print_hide": 1,
+   "read_only": 1
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 1, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2019-01-30 11:28:13.849860", 
- "modified_by": "Administrator", 
- "module": "HR", 
- "name": "Training Feedback", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-01-18 19:32:20.805277",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Training Feedback",
+ "naming_rule": "Expression (old style)",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 1, 
-   "cancel": 1, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "HR Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 1, 
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "submit": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Employee", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 1, 
+   "create": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Employee",
+   "share": 1,
+   "submit": 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", 
- "title_field": "employee_name", 
- "track_changes": 0, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "search_fields": "employee_name, training_event, event_name",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name"
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/training_result/training_result.json b/erpnext/hr/doctype/training_result/training_result.json
index dd7abd7..f28669e 100644
--- a/erpnext/hr/doctype/training_result/training_result.json
+++ b/erpnext/hr/doctype/training_result/training_result.json
@@ -1,226 +1,83 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 1, 
- "autoname": "HR-TRR-.YYYY.-.#####", 
- "beta": 0, 
- "creation": "2016-11-04 02:13:48.407576", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "HR-TRR-.YYYY.-.#####",
+ "creation": "2016-11-04 02:13:48.407576",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "training_event",
+  "section_break_3",
+  "employees",
+  "amended_from",
+  "employee_emails"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "training_event", 
-   "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": "Training Event", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Training Event", 
-   "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, 
+   "fieldname": "training_event",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Training Event",
+   "options": "Training Event",
+   "reqd": 1,
    "unique": 1
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_3", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "section_break_3",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "employees", 
-   "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": "Employees", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Training Result Employee", 
-   "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": "employees",
+   "fieldtype": "Table",
+   "label": "Employees",
+   "options": "Training Result Employee"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amended_from", 
-   "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": "Amended From", 
-   "length": 0, 
-   "no_copy": 1, 
-   "options": "Training Result", 
-   "permlevel": 0, 
-   "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, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "Training Result",
+   "print_hide": 1,
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "employee_emails", 
-   "fieldtype": "Small Text", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Employee Emails", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Email", 
-   "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": "employee_emails",
+   "fieldtype": "Small Text",
+   "hidden": 1,
+   "label": "Employee Emails",
+   "options": "Email"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 1, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-08-21 16:15:47.614563", 
- "modified_by": "Administrator", 
- "module": "HR", 
- "name": "Training Result", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "is_submittable": 1,
+ "links": [],
+ "modified": "2022-01-18 19:31:44.900034",
+ "modified_by": "Administrator",
+ "module": "HR",
+ "name": "Training Result",
+ "naming_rule": "Expression (old style)",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 1, 
-   "cancel": 1, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "HR Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 1, 
+   "amend": 1,
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "HR Manager",
+   "share": 1,
+   "submit": 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", 
- "title_field": "training_event", 
- "track_changes": 0, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "search_fields": "training_event",
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "training_event"
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/travel_request/travel_request.json b/erpnext/hr/doctype/travel_request/travel_request.json
index 441907c..7908e1a 100644
--- a/erpnext/hr/doctype/travel_request/travel_request.json
+++ b/erpnext/hr/doctype/travel_request/travel_request.json
@@ -216,10 +216,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2019-12-12 18:42:26.451359",
+ "modified": "2022-01-18 19:19:33.678664",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Travel Request",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -235,7 +236,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
index acd50f2..abb2887 100644
--- a/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
+++ b/erpnext/hr/doctype/vehicle_log/test_vehicle_log.py
@@ -82,7 +82,7 @@
 			"vehicle_value": flt(500000)
 		})
 	try:
-		vehicle.insert()
+		vehicle.insert(ignore_if_duplicate=True)
 	except frappe.DuplicateEntryError:
 		pass
 	return license_plate
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 0febce1..c174047 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -237,7 +237,7 @@
 
 		create_leave_encashment(leave_allocation=leave_allocation)
 
-def allocate_earned_leaves():
+def allocate_earned_leaves(ignore_duplicates=False):
 	'''Allocate earned leaves to Employees'''
 	e_leave_types = get_earned_leaves()
 	today = getdate()
@@ -261,13 +261,13 @@
 
 			from_date=allocation.from_date
 
-			if e_leave_type.based_on_date_of_joining_date:
+			if e_leave_type.based_on_date_of_joining:
 				from_date  = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
 
-			if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining_date):
-				update_previous_leave_allocation(allocation, annual_allocation, e_leave_type)
+			if check_effective_date(from_date, today, e_leave_type.earned_leave_frequency, e_leave_type.based_on_date_of_joining):
+				update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates)
 
-def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type):
+def update_previous_leave_allocation(allocation, annual_allocation, e_leave_type, ignore_duplicates=False):
 		earned_leaves = get_monthly_earned_leave(annual_allocation, e_leave_type.earned_leave_frequency, e_leave_type.rounding)
 
 		allocation = frappe.get_doc('Leave Allocation', allocation.name)
@@ -277,9 +277,12 @@
 			new_allocation = e_leave_type.max_leaves_allowed
 
 		if new_allocation != allocation.total_leaves_allocated:
-			allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
 			today_date = today()
-			create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+
+			if ignore_duplicates or not is_earned_leave_already_allocated(allocation, annual_allocation):
+				allocation.db_set("total_leaves_allocated", new_allocation, update_modified=False)
+				create_additional_leave_ledger_entry(allocation, earned_leaves, today_date)
+
 
 def get_monthly_earned_leave(annual_leaves, frequency, rounding):
 	earned_leaves = 0.0
@@ -297,6 +300,28 @@
 	return earned_leaves
 
 
+def is_earned_leave_already_allocated(allocation, annual_allocation):
+	from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
+		get_leave_type_details,
+	)
+
+	leave_type_details = get_leave_type_details()
+	date_of_joining = frappe.db.get_value("Employee", allocation.employee, "date_of_joining")
+
+	assignment = frappe.get_doc("Leave Policy Assignment", allocation.leave_policy_assignment)
+	leaves_for_passed_months = assignment.get_leaves_for_passed_months(allocation.leave_type,
+		annual_allocation, leave_type_details, date_of_joining)
+
+	# exclude carry-forwarded leaves while checking for leave allocation for passed months
+	num_allocations = allocation.total_leaves_allocated
+	if allocation.unused_leaves:
+		num_allocations -= allocation.unused_leaves
+
+	if num_allocations >= leaves_for_passed_months:
+		return True
+	return False
+
+
 def get_leave_allocations(date, leave_type):
 	return frappe.db.sql("""select name, employee, from_date, to_date, leave_policy_assignment, leave_policy
 		from `tabLeave Allocation`
@@ -318,7 +343,7 @@
 	allocation.unused_leaves = 0
 	allocation.create_leave_ledger_entry()
 
-def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining_date):
+def check_effective_date(from_date, to_date, frequency, based_on_date_of_joining):
 	import calendar
 
 	from dateutil import relativedelta
@@ -329,7 +354,7 @@
 	#last day of month
 	last_day =  calendar.monthrange(to_date.year, to_date.month)[1]
 
-	if (from_date.day == to_date.day and based_on_date_of_joining_date) or (not based_on_date_of_joining_date and to_date.day == last_day):
+	if (from_date.day == to_date.day and based_on_date_of_joining) or (not based_on_date_of_joining and to_date.day == last_day):
 		if frequency == "Monthly":
 			return True
 		elif frequency == "Quarterly" and rd.months % 3:
diff --git a/erpnext/loan_management/doctype/loan/loan.js b/erpnext/loan_management/doctype/loan/loan.js
index f9c201a..940a1bb 100644
--- a/erpnext/loan_management/doctype/loan/loan.js
+++ b/erpnext/loan_management/doctype/loan/loan.js
@@ -46,7 +46,7 @@
 			});
 		});
 
-		$.each(["payment_account", "loan_account"], function (i, field) {
+		$.each(["payment_account", "loan_account", "disbursement_account"], function (i, field) {
 			frm.set_query(field, function () {
 				return {
 					"filters": {
@@ -88,6 +88,10 @@
 				frm.add_custom_button(__('Loan Write Off'), function() {
 					frm.trigger("make_loan_write_off_entry");
 				},__('Create'));
+
+				frm.add_custom_button(__('Loan Refund'), function() {
+					frm.trigger("make_loan_refund");
+				},__('Create'));
 			}
 		}
 		frm.trigger("toggle_fields");
@@ -155,6 +159,21 @@
 		})
 	},
 
+	make_loan_refund: function(frm) {
+		frappe.call({
+			args: {
+				"loan": frm.doc.name
+			},
+			method: "erpnext.loan_management.doctype.loan.loan.make_refund_jv",
+			callback: function (r) {
+				if (r.message) {
+					let doc = frappe.model.sync(r.message)[0];
+					frappe.set_route("Form", doc.doctype, doc.name);
+				}
+			}
+		})
+	},
+
 	request_loan_closure: function(frm) {
 		frappe.confirm(__("Do you really want to close this loan"),
 			function() {
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index af26f7b..196f36f 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -2,7 +2,7 @@
  "actions": [],
  "allow_import": 1,
  "autoname": "ACC-LOAN-.YYYY.-.#####",
- "creation": "2019-08-29 17:29:18.176786",
+ "creation": "2022-01-25 10:30:02.294967",
  "doctype": "DocType",
  "document_type": "Document",
  "editable_grid": 1,
@@ -34,6 +34,7 @@
   "is_term_loan",
   "account_info",
   "mode_of_payment",
+  "disbursement_account",
   "payment_account",
   "column_break_9",
   "loan_account",
@@ -356,12 +357,21 @@
    "fieldtype": "Date",
    "label": "Closure Date",
    "read_only": 1
+  },
+  {
+   "fetch_from": "loan_type.disbursement_account",
+   "fieldname": "disbursement_account",
+   "fieldtype": "Link",
+   "label": "Disbursement Account",
+   "options": "Account",
+   "read_only": 1,
+   "reqd": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-10-12 18:10:32.360818",
+ "modified": "2022-01-25 16:29:16.325501",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
@@ -391,5 +401,6 @@
  "search_fields": "posting_date",
  "sort_field": "creation",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/loan.py b/erpnext/loan_management/doctype/loan/loan.py
index f660a24..b798e08 100644
--- a/erpnext/loan_management/doctype/loan/loan.py
+++ b/erpnext/loan_management/doctype/loan/loan.py
@@ -10,6 +10,7 @@
 from frappe.utils import add_months, flt, get_last_day, getdate, now_datetime, nowdate
 
 import erpnext
+from erpnext.accounts.doctype.journal_entry.journal_entry import get_payment_entry
 from erpnext.controllers.accounts_controller import AccountsController
 from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts
 from erpnext.loan_management.doctype.loan_security_unpledge.loan_security_unpledge import (
@@ -233,17 +234,15 @@
 	loan_type = frappe.get_value('Loan', loan, 'loan_type')
 	write_off_limit = frappe.get_value('Loan Type', loan_type, 'write_off_amount')
 
-	# checking greater than 0 as there may be some minor precision error
-	if not pending_amount:
-		frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
-	elif pending_amount < write_off_limit:
+	if pending_amount and abs(pending_amount) < write_off_limit:
 		# Auto create loan write off and update status as loan closure requested
 		write_off = make_loan_write_off(loan)
 		write_off.submit()
-		frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
-	else:
+	elif pending_amount > 0:
 		frappe.throw(_("Cannot close loan as there is an outstanding of {0}").format(pending_amount))
 
+	frappe.db.set_value('Loan', loan, 'status', 'Loan Closure Requested')
+
 @frappe.whitelist()
 def get_loan_application(loan_application):
 	loan = frappe.get_doc("Loan Application", loan_application)
@@ -400,4 +399,39 @@
 	if getdate(date) == get_last_day(date):
 		return get_last_day(add_months(date, 1))
 	else:
-		return add_months(date, 1)
\ No newline at end of file
+		return add_months(date, 1)
+
+@frappe.whitelist()
+def make_refund_jv(loan, amount=0, reference_number=None, reference_date=None, submit=0):
+	loan_details = frappe.db.get_value('Loan', loan, ['applicant_type', 'applicant',
+		'loan_account', 'payment_account', 'posting_date', 'company', 'name',
+		'total_payment', 'total_principal_paid'], as_dict=1)
+
+	loan_details.doctype = 'Loan'
+	loan_details[loan_details.applicant_type.lower()] = loan_details.applicant
+
+	if not amount:
+		amount = flt(loan_details.total_principal_paid - loan_details.total_payment)
+
+		if amount < 0:
+			frappe.throw(_('No excess amount pending for refund'))
+
+	refund_jv = get_payment_entry(loan_details, {
+		"party_type": loan_details.applicant_type,
+		"party_account": loan_details.loan_account,
+		"amount_field_party": 'debit_in_account_currency',
+		"amount_field_bank": 'credit_in_account_currency',
+		"amount": amount,
+		"bank_account": loan_details.payment_account
+	})
+
+	if reference_number:
+		refund_jv.cheque_no = reference_number
+
+	if reference_date:
+		refund_jv.cheque_date = reference_date
+
+	if submit:
+		refund_jv.submit()
+
+	return refund_jv
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 1676c21..5ebb2e1 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -42,16 +42,17 @@
 		create_loan_type("Personal Loan", 500000, 8.4,
 			is_term_loan=1,
 			mode_of_payment='Cash',
+			disbursement_account='Disbursement Account - _TC',
 			payment_account='Payment Account - _TC',
 			loan_account='Loan Account - _TC',
 			interest_income_account='Interest Income Account - _TC',
 			penalty_income_account='Penalty Income Account - _TC')
 
-		create_loan_type("Stock Loan", 2000000, 13.5, 25, 1, 5, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
-			'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+		create_loan_type("Stock Loan", 2000000, 13.5, 25, 1, 5, 'Cash', 'Disbursement Account - _TC',
+			'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
 
-		create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
-			'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+		create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Disbursement Account - _TC',
+			'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
 
 		create_loan_security_type()
 		create_loan_security()
@@ -679,6 +680,29 @@
 		loan.load_from_db()
 		self.assertEqual(loan.status, "Loan Closure Requested")
 
+	def test_loan_repayment_against_partially_disbursed_loan(self):
+		pledge = [{
+			"loan_security": "Test Security 1",
+			"qty": 4000.00
+		}]
+
+		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
+		create_pledge(loan_application)
+
+		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+		loan.submit()
+
+		first_date = '2019-10-01'
+		last_date = '2019-10-30'
+
+		make_loan_disbursement_entry(loan.name, loan.loan_amount/2, disbursement_date=first_date)
+
+		loan.load_from_db()
+
+		self.assertEqual(loan.status, "Partially Disbursed")
+		create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
+			flt(loan.loan_amount/3))
+
 	def test_loan_amount_write_off(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -790,6 +814,18 @@
 			"account_type": "Bank",
 		}).insert(ignore_permissions=True)
 
+	if not frappe.db.exists("Account", "Disbursement Account - _TC"):
+		frappe.get_doc({
+			"doctype": "Account",
+			"company": "_Test Company",
+			"account_name": "Disbursement Account",
+			"root_type": "Asset",
+			"report_type": "Balance Sheet",
+			"currency": "INR",
+			"parent_account": "Bank Accounts - _TC",
+			"account_type": "Bank",
+		}).insert(ignore_permissions=True)
+
 	if not frappe.db.exists("Account", "Interest Income Account - _TC"):
 		frappe.get_doc({
 			"doctype": "Account",
@@ -815,7 +851,7 @@
 		}).insert(ignore_permissions=True)
 
 def create_loan_type(loan_name, maximum_loan_amount, rate_of_interest, penalty_interest_rate=None, is_term_loan=None, grace_period_in_days=None,
-	mode_of_payment=None, payment_account=None, loan_account=None, interest_income_account=None, penalty_income_account=None,
+	mode_of_payment=None, disbursement_account=None, payment_account=None, loan_account=None, interest_income_account=None, penalty_income_account=None,
 	repayment_method=None, repayment_periods=None):
 
 	if not frappe.db.exists("Loan Type", loan_name):
@@ -829,6 +865,7 @@
 			"penalty_interest_rate": penalty_interest_rate,
 			"grace_period_in_days": grace_period_in_days,
 			"mode_of_payment": mode_of_payment,
+			"disbursement_account": disbursement_account,
 			"payment_account": payment_account,
 			"loan_account": loan_account,
 			"interest_income_account": interest_income_account,
diff --git a/erpnext/loan_management/doctype/loan_application/test_loan_application.py b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
index d367e92..640709c 100644
--- a/erpnext/loan_management/doctype/loan_application/test_loan_application.py
+++ b/erpnext/loan_management/doctype/loan_application/test_loan_application.py
@@ -15,7 +15,7 @@
 class TestLoanApplication(unittest.TestCase):
 	def setUp(self):
 		create_loan_accounts()
-		create_loan_type("Home Loan", 500000, 9.2, 0, 1, 0, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
+		create_loan_type("Home Loan", 500000, 9.2, 0, 1, 0, 'Cash', 'Disbursement Account - _TC', 'Payment Account - _TC', 'Loan Account - _TC',
 			'Interest Income Account - _TC', 'Penalty Income Account - _TC', 'Repay Over Number of Periods', 18)
 		self.applicant = make_employee("kate_loan@loan.com", "_Test Company")
 		make_salary_structure("Test Salary Structure Loan", "Monthly", employee=self.applicant, currency='INR')
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index 7811d56..50926d7 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -14,11 +14,15 @@
   "applicant",
   "section_break_7",
   "disbursement_date",
+  "clearance_date",
   "column_break_8",
   "disbursed_amount",
   "accounting_dimensions_section",
   "cost_center",
-  "customer_details_section",
+  "accounting_details",
+  "disbursement_account",
+  "column_break_16",
+  "loan_account",
   "bank_account",
   "disbursement_references_section",
   "reference_date",
@@ -107,11 +111,6 @@
    "label": "Disbursement Details"
   },
   {
-   "fieldname": "customer_details_section",
-   "fieldtype": "Section Break",
-   "label": "Customer Details"
-  },
-  {
    "fetch_from": "against_loan.applicant_type",
    "fieldname": "applicant_type",
    "fieldtype": "Select",
@@ -149,15 +148,48 @@
    "fieldname": "reference_number",
    "fieldtype": "Data",
    "label": "Reference Number"
+  },
+  {
+   "fieldname": "clearance_date",
+   "fieldtype": "Date",
+   "label": "Clearance Date",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "accounting_details",
+   "fieldtype": "Section Break",
+   "label": "Accounting Details"
+  },
+  {
+   "fetch_from": "against_loan.disbursement_account",
+   "fieldname": "disbursement_account",
+   "fieldtype": "Link",
+   "label": "Disbursement Account",
+   "options": "Account",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_16",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "against_loan.loan_account",
+   "fieldname": "loan_account",
+   "fieldtype": "Link",
+   "label": "Loan Account",
+   "options": "Account",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 18:09:32.175355",
+ "modified": "2022-02-17 18:23:44.157598",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Disbursement",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -194,5 +226,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
index e2d758b..54a03b9 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.py
@@ -42,9 +42,6 @@
 		if not self.posting_date:
 			self.posting_date = self.disbursement_date or nowdate()
 
-		if not self.bank_account and self.applicant_type == "Customer":
-			self.bank_account = frappe.db.get_value("Customer", self.applicant, "default_bank_account")
-
 	def validate_disbursal_amount(self):
 		possible_disbursal_amount = get_disbursal_amount(self.against_loan)
 
@@ -117,12 +114,11 @@
 
 	def make_gl_entries(self, cancel=0, adv_adj=0):
 		gle_map = []
-		loan_details = frappe.get_doc("Loan", self.against_loan)
 
 		gle_map.append(
 			self.get_gl_dict({
-				"account": loan_details.loan_account,
-				"against": loan_details.payment_account,
+				"account": self.loan_account,
+				"against": self.disbursement_account,
 				"debit": self.disbursed_amount,
 				"debit_in_account_currency": self.disbursed_amount,
 				"against_voucher_type": "Loan",
@@ -137,8 +133,8 @@
 
 		gle_map.append(
 			self.get_gl_dict({
-				"account": loan_details.payment_account,
-				"against": loan_details.loan_account,
+				"account": self.disbursement_account,
+				"against": self.loan_account,
 				"credit": self.disbursed_amount,
 				"credit_in_account_currency": self.disbursed_amount,
 				"against_voucher_type": "Loan",
diff --git a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
index 94ec84e..10be750 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
+++ b/erpnext/loan_management/doctype/loan_disbursement/test_loan_disbursement.py
@@ -44,8 +44,8 @@
 	def setUp(self):
 		create_loan_accounts()
 
-		create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
-			'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+		create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Disbursement Account - _TC',
+			'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
 
 		create_loan_security_type()
 		create_loan_security()
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
index 0de073f..1c800a0 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/loan_interest_accrual.py
@@ -74,39 +74,6 @@
 				})
 			)
 
-		if self.payable_principal_amount:
-			gle_map.append(
-				self.get_gl_dict({
-					"account": self.loan_account,
-					"party_type": self.applicant_type,
-					"party": self.applicant,
-					"against": self.interest_income_account,
-					"debit": self.payable_principal_amount,
-					"debit_in_account_currency": self.interest_amount,
-					"against_voucher_type": "Loan",
-					"against_voucher": self.loan,
-					"remarks": _("Interest accrued from {0} to {1} against loan: {2}").format(
-						self.last_accrual_date, self.posting_date, self.loan),
-					"cost_center": erpnext.get_default_cost_center(self.company),
-					"posting_date": self.posting_date
-				})
-			)
-
-			gle_map.append(
-				self.get_gl_dict({
-					"account": self.interest_income_account,
-					"against": self.loan_account,
-					"credit": self.payable_principal_amount,
-					"credit_in_account_currency":  self.interest_amount,
-					"against_voucher_type": "Loan",
-					"against_voucher": self.loan,
-					"remarks": ("Interest accrued from {0} to {1} against loan: {2}").format(
-						self.last_accrual_date, self.posting_date, self.loan),
-					"cost_center": erpnext.get_default_cost_center(self.company),
-					"posting_date": self.posting_date
-				})
-			)
-
 		if gle_map:
 			make_gl_entries(gle_map, cancel=cancel, adv_adj=adv_adj)
 
diff --git a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
index 46aaaad..e8c7750 100644
--- a/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
+++ b/erpnext/loan_management/doctype/loan_interest_accrual/test_loan_interest_accrual.py
@@ -30,8 +30,8 @@
 	def setUp(self):
 		create_loan_accounts()
 
-		create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Payment Account - _TC', 'Loan Account - _TC',
-			'Interest Income Account - _TC', 'Penalty Income Account - _TC')
+		create_loan_type("Demand Loan", 2000000, 13.5, 25, 0, 5, 'Cash', 'Disbursement Account - _TC',
+			'Payment Account - _TC', 'Loan Account - _TC', 'Interest Income Account - _TC', 'Penalty Income Account - _TC')
 
 		create_loan_security_type()
 		create_loan_security()
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 93ef217..480e010 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "autoname": "LM-REP-.####",
- "creation": "2019-09-03 14:44:39.977266",
+ "creation": "2022-01-25 10:30:02.767941",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -13,6 +13,7 @@
   "column_break_3",
   "company",
   "posting_date",
+  "clearance_date",
   "rate_of_interest",
   "payroll_payable_account",
   "is_term_loan",
@@ -37,7 +38,12 @@
   "total_penalty_paid",
   "total_interest_paid",
   "repayment_details",
-  "amended_from"
+  "amended_from",
+  "accounting_details_section",
+  "payment_account",
+  "penalty_income_account",
+  "column_break_36",
+  "loan_account"
  ],
  "fields": [
   {
@@ -260,12 +266,52 @@
    "fieldname": "repay_from_salary",
    "fieldtype": "Check",
    "label": "Repay From Salary"
+  },
+  {
+   "fieldname": "clearance_date",
+   "fieldtype": "Date",
+   "label": "Clearance Date",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "accounting_details_section",
+   "fieldtype": "Section Break",
+   "label": "Accounting Details"
+  },
+  {
+   "fetch_from": "against_loan.payment_account",
+   "fieldname": "payment_account",
+   "fieldtype": "Link",
+   "label": "Repayment Account",
+   "options": "Account",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_36",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "against_loan.loan_account",
+   "fieldname": "loan_account",
+   "fieldtype": "Link",
+   "label": "Loan Account",
+   "options": "Account",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "against_loan.penalty_income_account",
+   "fieldname": "penalty_income_account",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Penalty Income Account",
+   "options": "Account"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-01-06 01:51:06.707782",
+ "modified": "2022-02-18 19:10:07.742298",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index 7e997e8..67c2b1e 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -125,7 +125,7 @@
 
 	def update_paid_amount(self):
 		loan = frappe.get_value("Loan", self.against_loan, ['total_amount_paid', 'total_principal_paid',
-			'status', 'is_secured_loan', 'total_payment', 'loan_amount', 'total_interest_payable',
+			'status', 'is_secured_loan', 'total_payment', 'loan_amount', 'disbursed_amount', 'total_interest_payable',
 			'written_off_amount'], as_dict=1)
 
 		loan.update({
@@ -153,7 +153,7 @@
 
 	def mark_as_unpaid(self):
 		loan = frappe.get_value("Loan", self.against_loan, ['total_amount_paid', 'total_principal_paid',
-			'status', 'is_secured_loan', 'total_payment', 'loan_amount', 'total_interest_payable',
+			'status', 'is_secured_loan', 'total_payment', 'loan_amount', 'disbursed_amount', 'total_interest_payable',
 			'written_off_amount'], as_dict=1)
 
 		no_of_repayments = len(self.repayment_details)
@@ -310,7 +310,6 @@
 
 	def make_gl_entries(self, cancel=0, adv_adj=0):
 		gle_map = []
-		loan_details = frappe.get_doc("Loan", self.against_loan)
 
 		if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
 			remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount,
@@ -323,13 +322,13 @@
 		if self.repay_from_salary:
 			payment_account = self.payroll_payable_account
 		else:
-			payment_account = loan_details.payment_account
+			payment_account = self.payment_account
 
 		if self.total_penalty_paid:
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.loan_account,
-					"against": loan_details.payment_account,
+					"account": self.loan_account,
+					"against": payment_account,
 					"debit": self.total_penalty_paid,
 					"debit_in_account_currency": self.total_penalty_paid,
 					"against_voucher_type": "Loan",
@@ -344,8 +343,8 @@
 
 			gle_map.append(
 				self.get_gl_dict({
-					"account": loan_details.penalty_income_account,
-					"against": payment_account,
+					"account": self.penalty_income_account,
+					"against": self.loan_account,
 					"credit": self.total_penalty_paid,
 					"credit_in_account_currency": self.total_penalty_paid,
 					"against_voucher_type": "Loan",
@@ -359,23 +358,24 @@
 		gle_map.append(
 			self.get_gl_dict({
 				"account": payment_account,
-				"against": loan_details.loan_account + ", " + loan_details.interest_income_account
-						+ ", " + loan_details.penalty_income_account,
+				"against": self.loan_account + ", " + self.penalty_income_account,
 				"debit": self.amount_paid,
 				"debit_in_account_currency": self.amount_paid,
 				"against_voucher_type": "Loan",
 				"against_voucher": self.against_loan,
 				"remarks": remarks,
 				"cost_center": self.cost_center,
-				"posting_date": getdate(self.posting_date)
+				"posting_date": getdate(self.posting_date),
+				"party_type": self.applicant_type if self.repay_from_salary else '',
+				"party": self.applicant if self.repay_from_salary else ''
 			})
 		)
 
 		gle_map.append(
 			self.get_gl_dict({
-				"account": loan_details.loan_account,
-				"party_type": loan_details.applicant_type,
-				"party": loan_details.applicant,
+				"account": self.loan_account,
+				"party_type": self.applicant_type,
+				"party": self.applicant,
 				"against": payment_account,
 				"credit": self.amount_paid,
 				"credit_in_account_currency": self.amount_paid,
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.js b/erpnext/loan_management/doctype/loan_type/loan_type.js
index 04c89c4..9f9137c 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.js
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.js
@@ -15,7 +15,7 @@
 			});
 		});
 
-		$.each(["payment_account", "loan_account"], function (i, field) {
+		$.each(["payment_account", "loan_account", "disbursement_account"], function (i, field) {
 			frm.set_query(field, function () {
 				return {
 					"filters": {
diff --git a/erpnext/loan_management/doctype/loan_type/loan_type.json b/erpnext/loan_management/doctype/loan_type/loan_type.json
index c0a5d2c..00337e4 100644
--- a/erpnext/loan_management/doctype/loan_type/loan_type.json
+++ b/erpnext/loan_management/doctype/loan_type/loan_type.json
@@ -19,9 +19,10 @@
   "description",
   "account_details_section",
   "mode_of_payment",
+  "disbursement_account",
   "payment_account",
-  "loan_account",
   "column_break_12",
+  "loan_account",
   "interest_income_account",
   "penalty_income_account",
   "amended_from"
@@ -79,7 +80,7 @@
   {
    "fieldname": "payment_account",
    "fieldtype": "Link",
-   "label": "Payment Account",
+   "label": "Repayment Account",
    "options": "Account",
    "reqd": 1
   },
@@ -149,15 +150,23 @@
    "fieldtype": "Currency",
    "label": "Auto Write Off Amount ",
    "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "disbursement_account",
+   "fieldtype": "Link",
+   "label": "Disbursement Account",
+   "options": "Account",
+   "reqd": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-19 18:10:57.368490",
+ "modified": "2022-01-25 16:23:57.009349",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Type",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
@@ -181,5 +190,6 @@
   }
  ],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
index 3d07081..b7b20d9 100644
--- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
+++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
@@ -70,7 +70,6 @@
   {
    "fieldname": "loan_repayment_entry",
    "fieldtype": "Link",
-   "hidden": 1,
    "label": "Loan Repayment Entry",
    "no_copy": 1,
    "options": "Loan Repayment",
@@ -88,7 +87,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-14 20:47:11.725818",
+ "modified": "2022-01-31 14:50:14.823213",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Salary Slip Loan",
@@ -97,5 +96,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index 2ffae1a..07d928c 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -1,7 +1,6 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
 from frappe import _, throw
 from frappe.utils import add_days, cint, cstr, date_diff, formatdate, getdate
@@ -306,13 +305,18 @@
 					return schedule.name
 
 @frappe.whitelist()
-def update_serial_nos(s_id):
-	serial_nos = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'serial_no')
+def get_serial_nos_from_schedule(item_code, schedule=None):
+	serial_nos = []
+	if schedule:
+		serial_nos = frappe.db.get_value('Maintenance Schedule Item', {
+			'parent': schedule,
+			'item_code': item_code
+		}, 'serial_no')
+
 	if serial_nos:
 		serial_nos = get_serial_nos(serial_nos)
-		return serial_nos
-	else:
-		return False
+
+	return serial_nos
 
 @frappe.whitelist()
 def make_maintenance_visit(source_name, target_doc=None, item_name=None, s_id=None):
@@ -320,12 +324,9 @@
 
 	def update_status_and_detail(source, target, parent):
 		target.maintenance_type = "Scheduled"
-		target.maintenance_schedule = source.name
 		target.maintenance_schedule_detail = s_id
 
-	def update_sales_and_serial(source, target, parent):
-		sales_person = frappe.db.get_value('Maintenance Schedule Detail', s_id, 'sales_person')
-		target.service_person = sales_person
+	def update_serial(source, target, parent):
 		serial_nos = get_serial_nos(target.serial_no)
 		if len(serial_nos) == 1:
 			target.serial_no = serial_nos[0]
@@ -346,7 +347,10 @@
 		"Maintenance Schedule Item": {
 			"doctype": "Maintenance Visit Purpose",
 			"condition": lambda doc: doc.item_name == item_name,
-			"postprocess": update_sales_and_serial
+			"field_map": {
+				"sales_person": "service_person"
+			},
+			"postprocess": update_serial
 		}
 	}, target_doc)
 
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
index 5017126..6e727e5 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/test_maintenance_schedule.py
@@ -4,11 +4,15 @@
 import unittest
 
 import frappe
+from frappe.utils import format_date
 from frappe.utils.data import add_days, formatdate, today
 
 from erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule import (
+	get_serial_nos_from_schedule,
 	make_maintenance_visit,
 )
+from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
 
 # test_records = frappe.get_test_records('Maintenance Schedule')
 
@@ -79,6 +83,49 @@
 
 		#checks if visit status is back updated in schedule
 		self.assertTrue(ms.schedules[1].completion_status, "Partially Completed")
+		self.assertEqual(format_date(visit.mntc_date), format_date(ms.schedules[1].actual_date))
+
+		#checks if visit status is updated on cancel
+		visit.cancel()
+		ms.reload()
+		self.assertTrue(ms.schedules[1].completion_status, "Pending")
+		self.assertEqual(ms.schedules[1].actual_date, None)
+
+	def test_serial_no_filters(self):
+		# Without serial no. set in schedule -> returns None
+		item_code = "_Test Serial Item"
+		make_serial_item_with_serial(item_code)
+		ms = make_maintenance_schedule(item_code=item_code)
+		ms.submit()
+
+		s_item = ms.schedules[0]
+		mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name)
+		mvi = mv.purposes[0]
+		serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name)
+		self.assertEqual(serial_nos, None)
+
+		# With serial no. set in schedule -> returns serial nos.
+		make_serial_item_with_serial(item_code)
+		ms = make_maintenance_schedule(item_code=item_code, serial_no="TEST001, TEST002")
+		ms.submit()
+
+		s_item = ms.schedules[0]
+		mv = make_maintenance_visit(source_name=ms.name, item_name=item_code, s_id=s_item.name)
+		mvi = mv.purposes[0]
+		serial_nos = get_serial_nos_from_schedule(mvi.item_name, ms.name)
+		self.assertEqual(serial_nos, ["TEST001", "TEST002"])
+
+		frappe.db.rollback()
+
+def make_serial_item_with_serial(item_code):
+	serial_item_doc = create_item(item_code, is_stock_item=1)
+	if not serial_item_doc.has_serial_no or not serial_item_doc.serial_no_series:
+		serial_item_doc.has_serial_no = 1
+		serial_item_doc.serial_no_series = "TEST.###"
+		serial_item_doc.save(ignore_permissions=True)
+	active_serials = frappe.db.get_all('Serial No', {"status": "Active", "item_code": item_code})
+	if len(active_serials) < 2:
+		make_serialized_item(item_code=item_code)
 
 def get_events(ms):
 	return frappe.get_all("Event Participants", filters={
@@ -87,17 +134,18 @@
 			"parenttype": "Event"
 		})
 
-def make_maintenance_schedule():
+def make_maintenance_schedule(**args):
 	ms = frappe.new_doc("Maintenance Schedule")
 	ms.company = "_Test Company"
 	ms.customer = "_Test Customer"
 	ms.transaction_date = today()
 
 	ms.append("items", {
-		"item_code": "_Test Item",
+		"item_code": args.get("item_code") or "_Test Item",
 		"start_date": today(),
 		"periodicity": "Weekly",
 		"no_of_visits": 4,
+		"serial_no": args.get("serial_no"),
 		"sales_person": "Sales Team",
 	})
 	ms.insert(ignore_permissions=True)
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
index d2197a6..72686e7 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.js
@@ -2,52 +2,54 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.provide("erpnext.maintenance");
-var serial_nos = [];
 frappe.ui.form.on('Maintenance Visit', {
-	refresh: function (frm) {
-		//filters for serial_no based on item_code
-		frm.set_query('serial_no', 'purposes', function (frm, cdt, cdn) {
-			let item = locals[cdt][cdn];
-			if (serial_nos) {
-				return {
-					filters: {
-						'item_code': item.item_code,
-						'name': ["in", serial_nos]
-					}
-				};
-			} else {
-				return {
-					filters: {
-						'item_code': item.item_code
-					}
-				};
-			}
-		});
-	},
 	setup: function (frm) {
 		frm.set_query('contact_person', erpnext.queries.contact_query);
 		frm.set_query('customer_address', erpnext.queries.address_query);
 		frm.set_query('customer', erpnext.queries.customer);
 	},
-	onload: function (frm, cdt, cdn) {
-		let item = locals[cdt][cdn];
+	onload: function (frm) {
+		// filters for serial no based on item code
 		if (frm.doc.maintenance_type === "Scheduled") {
-			const schedule_id = item.purposes[0].prevdoc_detail_docname || frm.doc.maintenance_schedule_detail;
+			let item_code = frm.doc.purposes[0].item_code;
 			frappe.call({
-				method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.update_serial_nos",
+				method: "erpnext.maintenance.doctype.maintenance_schedule.maintenance_schedule.get_serial_nos_from_schedule",
 				args: {
-					s_id: schedule_id
-				},
-				callback: function (r) {
-					serial_nos = r.message;
+					schedule: frm.doc.maintenance_schedule,
+					item_code: item_code
 				}
+			}).then((r) => {
+				let serial_nos = r.message;
+				frm.set_query('serial_no', 'purposes', () => {
+					if (serial_nos.length > 0) {
+						return {
+							filters: {
+								'item_code': item_code,
+								'name': ["in", serial_nos]
+							}
+						};
+					}
+					return {
+						filters: {
+							'item_code': item_code
+						}
+					};
+				});
+			});
+		} else {
+			frm.set_query('serial_no', 'purposes', (frm, cdt, cdn) => {
+				let row = locals[cdt][cdn];
+				return {
+					filters: {
+						'item_code': row.item_code
+					}
+				};
 			});
 		}
 		if (!frm.doc.status) {
 			frm.set_value({ status: 'Draft' });
 		}
 		if (frm.doc.__islocal) {
-			frm.doc.maintenance_type == 'Unscheduled' && frm.clear_table("purposes");
 			frm.set_value({ mntc_date: frappe.datetime.get_today() });
 		}
 	},
@@ -60,7 +62,6 @@
 	contact_person: function (frm) {
 		erpnext.utils.get_contact_details(frm);
 	}
-
 })
 
 // TODO commonify this code
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
index ec32239..4a6aa0a 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.json
@@ -179,8 +179,7 @@
    "label": "Purposes",
    "oldfieldname": "maintenance_visit_details",
    "oldfieldtype": "Table",
-   "options": "Maintenance Visit Purpose",
-   "reqd": 1
+   "options": "Maintenance Visit Purpose"
   },
   {
    "fieldname": "more_info",
@@ -294,10 +293,11 @@
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-05-27 16:06:17.352572",
+ "modified": "2021-12-17 03:10:27.608112",
  "modified_by": "Administrator",
  "module": "Maintenance",
  "name": "Maintenance Visit",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
diff --git a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
index 5a87b16..6fe2466 100644
--- a/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
+++ b/erpnext/maintenance/doctype/maintenance_visit/maintenance_visit.py
@@ -4,7 +4,7 @@
 
 import frappe
 from frappe import _
-from frappe.utils import get_datetime
+from frappe.utils import format_date, get_datetime
 
 from erpnext.utilities.transaction_base import TransactionBase
 
@@ -18,25 +18,34 @@
 			if d.serial_no and not frappe.db.exists("Serial No", d.serial_no):
 				frappe.throw(_("Serial No {0} does not exist").format(d.serial_no))
 
+	def validate_purpose_table(self):
+		if not self.purposes:
+			frappe.throw(_("Add Items in the Purpose Table"), title="Purposes Required")
+
 	def validate_maintenance_date(self):
 		if self.maintenance_type == "Scheduled" and self.maintenance_schedule_detail:
 			item_ref = frappe.db.get_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'item_reference')
 			if item_ref:
 				start_date, end_date = frappe.db.get_value('Maintenance Schedule Item', item_ref, ['start_date', 'end_date'])
 				if get_datetime(self.mntc_date) < get_datetime(start_date) or get_datetime(self.mntc_date) > get_datetime(end_date):
-					frappe.throw(_("Date must be between {0} and {1}").format(start_date, end_date))
+					frappe.throw(_("Date must be between {0} and {1}")
+						.format(format_date(start_date), format_date(end_date)))
+
 
 	def validate(self):
 		self.validate_serial_no()
 		self.validate_maintenance_date()
+		self.validate_purpose_table()
 
-	def update_completion_status(self):
+	def update_status_and_actual_date(self, cancel=False):
+		status = "Pending"
+		actual_date = None
+		if not cancel:
+			status = self.completion_status
+			actual_date = self.mntc_date
 		if self.maintenance_schedule_detail:
-			frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', self.completion_status)
-
-	def update_actual_date(self):
-		if self.maintenance_schedule_detail:
-			frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', self.mntc_date)
+			frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'completion_status', status)
+			frappe.db.set_value('Maintenance Schedule Detail', self.maintenance_schedule_detail, 'actual_date', actual_date)
 
 	def update_customer_issue(self, flag):
 		if not self.maintenance_schedule:
@@ -97,12 +106,12 @@
 	def on_submit(self):
 		self.update_customer_issue(1)
 		frappe.db.set(self, 'status', 'Submitted')
-		self.update_completion_status()
-		self.update_actual_date()
+		self.update_status_and_actual_date()
 
 	def on_cancel(self):
 		self.check_if_last_visit()
 		frappe.db.set(self, 'status', 'Cancelled')
+		self.update_status_and_actual_date(cancel=True)
 
 	def on_update(self):
 		pass
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index eff2344..d4d337d 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -1,15 +1,15 @@
 # Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_months, today
 
 from erpnext import get_company_currency
-from erpnext.tests.utils import ERPNextTestCase
 
 from .blanket_order import make_order
 
 
-class TestBlanketOrder(ERPNextTestCase):
+class TestBlanketOrder(FrappeTestCase):
 	def setUp(self):
 		frappe.flags.args = frappe._dict()
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 6d35d65..8a7634e 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -93,7 +93,7 @@
 			});
 		}
 
-		if(frm.doc.docstatus!=0) {
+		if(frm.doc.docstatus==1) {
 			frm.add_custom_button(__("Work Order"), function() {
 				frm.trigger("make_work_order");
 			}, __("Create"));
@@ -331,7 +331,7 @@
 			});
 		});
 
-		if (has_template_rm) {
+		if (has_template_rm && has_template_rm.length) {
 			dialog.fields_dict.items.grid.refresh();
 		}
 	},
@@ -467,7 +467,8 @@
 				"uom": d.uom,
 				"stock_uom": d.stock_uom,
 				"conversion_factor": d.conversion_factor,
-				"sourced_by_supplier": d.sourced_by_supplier
+				"sourced_by_supplier": d.sourced_by_supplier,
+				"do_not_explode": d.do_not_explode
 			},
 			callback: function(r) {
 				d = locals[cdt][cdn];
@@ -640,6 +641,13 @@
 	});
 });
 
+frappe.ui.form.on("BOM Item", {
+	do_not_explode: function(frm, cdt, cdn) {
+		get_bom_material_detail(frm.doc, cdt, cdn, false);
+	}
+})
+
+
 frappe.ui.form.on("BOM Item", "qty", function(frm, cdt, cdn) {
 	var d = locals[cdt][cdn];
 	d.stock_qty = d.qty * d.conversion_factor;
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index 218ac64..0b44196 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -37,7 +37,6 @@
   "inspection_required",
   "quality_inspection_template",
   "column_break_31",
-  "bom_level",
   "section_break_33",
   "items",
   "scrap_section",
@@ -523,13 +522,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "default": "0",
-   "fieldname": "bom_level",
-   "fieldtype": "Int",
-   "label": "BOM Level",
-   "read_only": 1
-  },
-  {
    "fieldname": "section_break_33",
    "fieldtype": "Section Break",
    "hide_border": 1
@@ -540,7 +532,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-18 13:04:16.271975",
+ "modified": "2022-01-30 21:27:54.727298",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
@@ -577,5 +569,6 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 5a60fb7..37d2b9f 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -149,12 +149,12 @@
 		self.set_bom_material_details()
 		self.set_bom_scrap_items_detail()
 		self.validate_materials()
+		self.validate_transfer_against()
 		self.set_routing_operations()
 		self.validate_operations()
 		self.calculate_cost()
 		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):
@@ -203,6 +203,10 @@
 		for item in self.get("items"):
 			self.validate_bom_currency(item)
 
+			item.bom_no = ''
+			if not item.do_not_explode:
+				item.bom_no = item.bom_no
+
 			ret = self.get_bom_material_detail({
 				"company": self.company,
 				"item_code": item.item_code,
@@ -214,8 +218,10 @@
 				"uom": item.uom,
 				"stock_uom": item.stock_uom,
 				"conversion_factor": item.conversion_factor,
-				"sourced_by_supplier": item.sourced_by_supplier
+				"sourced_by_supplier": item.sourced_by_supplier,
+				"do_not_explode": item.do_not_explode
 			})
+
 			for r in ret:
 				if not item.get(r):
 					item.set(r, ret[r])
@@ -267,6 +273,9 @@
 			 'sourced_by_supplier'		: args.get('sourced_by_supplier', 0)
 		}
 
+		if args.get('do_not_explode'):
+			ret_item['bom_no'] = ''
+
 		return ret_item
 
 	def validate_bom_currency(self, item):
@@ -681,6 +690,12 @@
 			if act_pbom and act_pbom[0][0]:
 				frappe.throw(_("Cannot deactivate or cancel BOM as it is linked with other BOMs"))
 
+	def validate_transfer_against(self):
+		if not self.with_operations:
+			self.transfer_material_against = "Work Order"
+		if not self.transfer_material_against and not self.is_new():
+			frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+
 	def set_routing_operations(self):
 		if self.routing and self.with_operations and not self.operations:
 			self.get_routing()
@@ -700,20 +715,6 @@
 		"""Get a complete tree representation preserving order of child items."""
 		return BOMTree(self.name)
 
-	def set_bom_level(self, update=False):
-		levels = []
-
-		self.bom_level = 0
-		for row in self.items:
-			if row.bom_no:
-				levels.append(frappe.get_cached_value("BOM", row.bom_no, "bom_level") or 0)
-
-		if levels:
-			self.bom_level = max(levels) + 1
-
-		if update:
-			self.db_set("bom_level", self.bom_level)
-
 	def validate_scrap_items(self):
 		for item in self.scrap_items:
 			msg = ""
@@ -917,7 +918,7 @@
 			frappe.throw(_("BOM {0} does not belong to Item {1}").format(bom_no, item))
 
 @frappe.whitelist()
-def get_children(doctype, parent=None, is_root=False, **filters):
+def get_children(parent=None, is_root=False, **filters):
 	if not parent or parent=="BOM":
 		frappe.msgprint(_('Please select a BOM'))
 		return
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 178d92c..3cc91b3 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -7,6 +7,7 @@
 
 import frappe
 from frappe.test_runner import make_test_records
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import cstr, flt
 
 from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
@@ -17,11 +18,10 @@
 	create_stock_reconciliation,
 )
 from erpnext.tests.test_subcontracting import set_backflush_based_on
-from erpnext.tests.utils import ERPNextTestCase
 
 test_records = frappe.get_test_records('BOM')
 
-class TestBOM(ERPNextTestCase):
+class TestBOM(FrappeTestCase):
 	def setUp(self):
 		if not frappe.get_value('Item', '_Test Item'):
 			make_test_records('Item')
@@ -385,6 +385,53 @@
 		self.assertNotEqual(len(test_items), len(filtered), msg="Item filtering showing excessive results")
 		self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
 
+	def test_exclude_exploded_items_from_bom(self):
+		bom_no = get_default_bom()
+		new_bom = frappe.copy_doc(frappe.get_doc('BOM', bom_no))
+		for row in new_bom.items:
+			if row.item_code == '_Test Item Home Desktop Manufactured':
+				self.assertTrue(row.bom_no)
+				row.do_not_explode = True
+
+		new_bom.docstatus = 0
+		new_bom.save()
+		new_bom.load_from_db()
+
+		for row in new_bom.items:
+			if row.item_code == '_Test Item Home Desktop Manufactured' and row.do_not_explode:
+				self.assertFalse(row.bom_no)
+
+		new_bom.delete()
+
+	def test_valid_transfer_defaults(self):
+		bom_with_op = frappe.db.get_value("BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1})
+		bom = frappe.copy_doc(frappe.get_doc("BOM", bom_with_op), ignore_no_copy=False)
+
+		# test defaults
+		bom.docstatus = 0
+		bom.transfer_material_against = None
+		bom.insert()
+		self.assertEqual(bom.transfer_material_against, "Work Order")
+
+		bom.reload()
+		bom.transfer_material_against = None
+		with self.assertRaises(frappe.ValidationError):
+			bom.save()
+		bom.reload()
+
+		# test saner default
+		bom.transfer_material_against = "Job Card"
+		bom.with_operations = 0
+		bom.save()
+		self.assertEqual(bom.transfer_material_against, "Work Order")
+
+		# test no value on existing doc
+		bom.transfer_material_against = None
+		bom.with_operations = 0
+		bom.save()
+		self.assertEqual(bom.transfer_material_against, "Work Order")
+		bom.delete()
+
 
 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})
diff --git a/erpnext/manufacturing/doctype/bom_item/bom_item.json b/erpnext/manufacturing/doctype/bom_item/bom_item.json
index 4c9877f..3406215 100644
--- a/erpnext/manufacturing/doctype/bom_item/bom_item.json
+++ b/erpnext/manufacturing/doctype/bom_item/bom_item.json
@@ -10,6 +10,7 @@
   "item_name",
   "operation",
   "column_break_3",
+  "do_not_explode",
   "bom_no",
   "source_warehouse",
   "allow_alternative_item",
@@ -73,6 +74,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:!doc.do_not_explode",
    "fieldname": "bom_no",
    "fieldtype": "Link",
    "in_filter": 1,
@@ -284,18 +286,25 @@
    "fieldname": "sourced_by_supplier",
    "fieldtype": "Check",
    "label": "Sourced by Supplier"
+  },
+  {
+   "default": "0",
+   "fieldname": "do_not_explode",
+   "fieldtype": "Check",
+   "label": "Do Not Explode"
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-10-08 14:19:37.563300",
+ "modified": "2022-01-24 16:57:57.020232",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Item",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index 12576cb..b4c625d 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -2,15 +2,15 @@
 # License: GNU General Public License v3. See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
 from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
 from erpnext.stock.doctype.item.test_item import create_item
-from erpnext.tests.utils import ERPNextTestCase
 
 test_records = frappe.get_test_records('BOM')
 
-class TestBOMUpdateTool(ERPNextTestCase):
+class TestBOMUpdateTool(FrappeTestCase):
 	def test_replace_bom(self):
 		current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
 
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 8d00019..9f4ace2 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -62,7 +62,7 @@
 
 		if self.get('time_logs'):
 			for d in self.get('time_logs'):
-				if get_datetime(d.from_time) > get_datetime(d.to_time):
+				if d.to_time and get_datetime(d.from_time) > get_datetime(d.to_time):
 					frappe.throw(_("Row {0}: From time must be less than to time").format(d.idx))
 
 				data = self.get_overlap_for(d)
diff --git a/erpnext/manufacturing/doctype/job_card/test_job_card.py b/erpnext/manufacturing/doctype/job_card/test_job_card.py
index bb5004b..33425d2 100644
--- a/erpnext/manufacturing/doctype/job_card/test_job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/test_job_card.py
@@ -2,6 +2,7 @@
 # See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import random_string
 
 from erpnext.manufacturing.doctype.job_card.job_card import OperationMismatchError, OverlapError
@@ -11,10 +12,9 @@
 from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 from erpnext.manufacturing.doctype.workstation.test_workstation import make_workstation
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestJobCard(ERPNextTestCase):
+class TestJobCard(FrappeTestCase):
 	def setUp(self):
 		make_bom_for_jc_tests()
 
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 0babf87..e8759f5 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -49,7 +49,7 @@
 			if (d.item_code) {
 				return {
 					query: "erpnext.controllers.queries.bom",
-					filters:{'item': cstr(d.item_code)}
+					filters:{'item': cstr(d.item_code), 'docstatus': 1}
 				}
 			} else frappe.msgprint(__("Please enter Item first"));
 		}
@@ -232,7 +232,7 @@
 		});
 	},
 	combine_items: function (frm) {
-		frm.clear_table('prod_plan_references');
+		frm.clear_table("prod_plan_references");
 
 		frappe.call({
 			method: "get_items",
@@ -247,6 +247,13 @@
 		});
 	},
 
+	combine_sub_items: (frm) => {
+		if (frm.doc.sub_assembly_items.length > 0) {
+			frm.clear_table("sub_assembly_items");
+			frm.trigger("get_sub_assembly_items");
+		}
+	},
+
 	get_sub_assembly_items: function(frm) {
 		frm.dirty();
 
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 56cf2b4..3bfb764 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -36,6 +36,7 @@
   "prod_plan_references",
   "section_break_24",
   "get_sub_assembly_items",
+  "combine_sub_items",
   "sub_assembly_items",
   "material_request_planning",
   "include_non_stock_items",
@@ -340,7 +341,6 @@
   {
    "fieldname": "prod_plan_references",
    "fieldtype": "Table",
-   "hidden": 1,
    "label": "Production Plan Item Reference",
    "options": "Production Plan Item Reference"
   },
@@ -370,16 +370,23 @@
    "fieldname": "to_delivery_date",
    "fieldtype": "Date",
    "label": "To Delivery Date"
+  },
+  {
+   "default": "0",
+   "fieldname": "combine_sub_items",
+   "fieldtype": "Check",
+   "label": "Consolidate Sub Assembly Items"
   }
  ],
  "icon": "fa fa-calendar",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-06 18:35:59.642232",
+ "modified": "2022-02-23 17:16:10.629378",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 7cec7f5..48cd753 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -21,16 +21,32 @@
 )
 from frappe.utils.csvutils import build_csv_response
 
-from erpnext.manufacturing.doctype.bom.bom import get_children, validate_bom_no
+from erpnext.manufacturing.doctype.bom.bom import get_children as get_bom_children
+from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
 from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
 
 
 class ProductionPlan(Document):
 	def validate(self):
+		self.set_pending_qty_in_row_without_reference()
 		self.calculate_total_planned_qty()
 		self.set_status()
 
+	def set_pending_qty_in_row_without_reference(self):
+		"Set Pending Qty in independent rows (not from SO or MR)."
+		if self.docstatus > 0: # set only to initialise value before submit
+			return
+
+		for item in self.po_items:
+			if not item.get("sales_order") or not item.get("material_request"):
+				item.pending_qty = item.planned_qty
+
+	def calculate_total_planned_qty(self):
+		self.total_planned_qty = 0
+		for d in self.po_items:
+			self.total_planned_qty += flt(d.planned_qty)
+
 	def validate_data(self):
 		for d in self.get('po_items'):
 			if not d.bom_no:
@@ -263,11 +279,6 @@
 						'qty': so_detail['qty']
 				})
 
-	def calculate_total_planned_qty(self):
-		self.total_planned_qty = 0
-		for d in self.po_items:
-			self.total_planned_qty += flt(d.planned_qty)
-
 	def calculate_total_produced_qty(self):
 		self.total_produced_qty = 0
 		for d in self.po_items:
@@ -275,10 +286,11 @@
 
 		self.db_set("total_produced_qty", self.total_produced_qty, update_modified=False)
 
-	def update_produced_qty(self, produced_qty, production_plan_item):
+	def update_produced_pending_qty(self, produced_qty, production_plan_item):
 		for data in self.po_items:
 			if data.name == production_plan_item:
 				data.produced_qty = produced_qty
+				data.pending_qty = flt(data.planned_qty - produced_qty)
 				data.db_update()
 
 		self.calculate_total_produced_qty()
@@ -308,7 +320,7 @@
 
 		if self.total_produced_qty > 0:
 			self.status = "In Process"
-			if self.check_have_work_orders_completed():
+			if self.all_items_completed():
 				self.status = "Completed"
 
 		if self.status != 'Completed':
@@ -341,6 +353,7 @@
 
 	def get_production_items(self):
 		item_dict = {}
+
 		for d in self.po_items:
 			item_details = {
 				"production_item"		: d.item_code,
@@ -357,12 +370,12 @@
 				"production_plan"       : self.name,
 				"production_plan_item"  : d.name,
 				"product_bundle_item"	: d.product_bundle_item,
-				"planned_start_date"    : d.planned_start_date
+				"planned_start_date"    : d.planned_start_date,
+				"project"               : self.project
 			}
 
-			item_details.update({
-				"project": self.project or frappe.db.get_value("Sales Order", d.sales_order, "project")
-			})
+			if not item_details['project'] and d.sales_order:
+				item_details['project'] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
 
 			if self.get_items_from == "Material Request":
 				item_details.update({
@@ -380,39 +393,59 @@
 
 	@frappe.whitelist()
 	def make_work_order(self):
+		from erpnext.manufacturing.doctype.work_order.work_order import get_default_warehouse
+
 		wo_list, po_list = [], []
 		subcontracted_po = {}
+		default_warehouses = get_default_warehouse()
 
-		self.validate_data()
-		self.make_work_order_for_finished_goods(wo_list)
-		self.make_work_order_for_subassembly_items(wo_list, subcontracted_po)
+		self.make_work_order_for_finished_goods(wo_list, default_warehouses)
+		self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses)
 		self.make_subcontracted_purchase_order(subcontracted_po, po_list)
 		self.show_list_created_message('Work Order', wo_list)
 		self.show_list_created_message('Purchase Order', po_list)
 
-	def make_work_order_for_finished_goods(self, wo_list):
+	def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
 		items_data = self.get_production_items()
 
 		for key, item in items_data.items():
 			if self.sub_assembly_items:
 				item['use_multi_level_bom'] = 0
 
+			set_default_warehouses(item, default_warehouses)
 			work_order = self.create_work_order(item)
 			if work_order:
 				wo_list.append(work_order)
 
-	def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po):
+	def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
 		for row in self.sub_assembly_items:
 			if row.type_of_manufacturing == 'Subcontract':
 				subcontracted_po.setdefault(row.supplier, []).append(row)
 				continue
 
-			args = {}
-			self.prepare_args_for_sub_assembly_items(row, args)
-			work_order = self.create_work_order(args)
+			work_order_data = {
+				'wip_warehouse': default_warehouses.get('wip_warehouse'),
+				'fg_warehouse': default_warehouses.get('fg_warehouse')
+			}
+
+			self.prepare_data_for_sub_assembly_items(row, work_order_data)
+			work_order = self.create_work_order(work_order_data)
 			if work_order:
 				wo_list.append(work_order)
 
+	def prepare_data_for_sub_assembly_items(self, row, wo_data):
+		for field in ["production_item", "item_name", "qty", "fg_warehouse",
+			"description", "bom_no", "stock_uom", "bom_level",
+			"production_plan_item", "schedule_date"]:
+			if row.get(field):
+				wo_data[field] = row.get(field)
+
+		wo_data.update({
+			"use_multi_level_bom": 0,
+			"production_plan": self.name,
+			"production_plan_sub_assembly_item": row.name
+		})
+
 	def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
 		if not subcontracted_po:
 			return
@@ -423,7 +456,7 @@
 			po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
 			po.is_subcontracted = 'Yes'
 			for row in po_list:
-				args = {
+				po_data = {
 					'item_code': row.production_item,
 					'warehouse': row.fg_warehouse,
 					'production_plan_sub_assembly_item': row.name,
@@ -433,9 +466,9 @@
 
 				for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
 					'description', 'production_plan_item']:
-					args[field] = row.get(field)
+					po_data[field] = row.get(field)
 
-				po.append('items', args)
+				po.append('items', po_data)
 
 			po.set_missing_values()
 			po.flags.ignore_mandatory = True
@@ -452,24 +485,9 @@
 			doc_list = [get_link_to_form(doctype, p) for p in doc_list]
 			msgprint(_("{0} created").format(comma_and(doc_list)))
 
-	def prepare_args_for_sub_assembly_items(self, row, args):
-		for field in ["production_item", "item_name", "qty", "fg_warehouse",
-			"description", "bom_no", "stock_uom", "bom_level",
-			"production_plan_item", "schedule_date"]:
-			args[field] = row.get(field)
-
-		args.update({
-			"use_multi_level_bom": 0,
-			"production_plan": self.name,
-			"production_plan_sub_assembly_item": row.name
-		})
-
 	def create_work_order(self, item):
-		from erpnext.manufacturing.doctype.work_order.work_order import (
-			OverProductionError,
-			get_default_warehouse,
-		)
-		warehouse = get_default_warehouse()
+		from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
+
 		wo = frappe.new_doc("Work Order")
 		wo.update(item)
 		wo.planned_start_date = item.get('planned_start_date') or item.get('schedule_date')
@@ -478,11 +496,11 @@
 			wo.fg_warehouse = item.get("warehouse")
 
 		wo.set_work_order_operations()
+		wo.set_required_items()
 
-		if not wo.fg_warehouse:
-			wo.fg_warehouse = warehouse.get('fg_warehouse')
 		try:
 			wo.flags.ignore_mandatory = True
+			wo.flags.ignore_validate = True
 			wo.insert()
 			return wo.name
 		except OverProductionError:
@@ -553,15 +571,28 @@
 
 	@frappe.whitelist()
 	def get_sub_assembly_items(self, manufacturing_type=None):
+		"Fetch sub assembly items and optionally combine them."
 		self.sub_assembly_items = []
+		sub_assembly_items_store = [] # temporary store to process all subassembly items
+
 		for row in self.po_items:
 			bom_data = []
 			get_sub_assembly_items(row.bom_no, bom_data, row.planned_qty)
 			self.set_sub_assembly_items_based_on_level(row, bom_data, manufacturing_type)
+			sub_assembly_items_store.extend(bom_data)
+
+		if self.combine_sub_items:
+			# Combine subassembly items
+			sub_assembly_items_store = self.combine_subassembly_items(sub_assembly_items_store)
+
+		sub_assembly_items_store.sort(key= lambda d: d.bom_level, reverse=True) # sort by bom level
+
+		for idx, row in enumerate(sub_assembly_items_store):
+			row.idx = idx + 1
+			self.append("sub_assembly_items", row)
 
 	def set_sub_assembly_items_based_on_level(self, row, bom_data, manufacturing_type=None):
-		bom_data = sorted(bom_data, key = lambda i: i.bom_level)
-
+		"Modify bom_data, set additional details."
 		for data in bom_data:
 			data.qty = data.stock_qty
 			data.production_plan_item = row.name
@@ -570,23 +601,59 @@
 			data.type_of_manufacturing = manufacturing_type or ("Subcontract" if data.is_sub_contracted_item
 				else "In House")
 
-			self.append("sub_assembly_items", data)
+	def combine_subassembly_items(self, sub_assembly_items_store):
+		"Aggregate if same: Item, Warehouse, Inhouse/Outhouse Manu.g, BOM No."
+		key_wise_data = {}
+		for row in sub_assembly_items_store:
+			key = (
+				row.get("production_item"), row.get("fg_warehouse"),
+				row.get("bom_no"), row.get("type_of_manufacturing")
+			)
+			if key not in key_wise_data:
+				# intialise (item, wh, bom no, man.g type) wise dict
+				key_wise_data[key] = row
+				continue
 
-	def check_have_work_orders_completed(self):
-		wo_status = frappe.db.get_list(
+			existing_row = key_wise_data[key]
+			if existing_row:
+				# if row with same (item, wh, bom no, man.g type) key, merge
+				existing_row.qty += flt(row.qty)
+				existing_row.stock_qty += flt(row.stock_qty)
+				existing_row.bom_level = max(existing_row.bom_level, row.bom_level)
+				continue
+			else:
+				# add row with key
+				key_wise_data[key] = row
+
+		sub_assembly_items_store = [key_wise_data[key] for key in key_wise_data] # unpack into single level list
+		return sub_assembly_items_store
+
+	def all_items_completed(self):
+		all_items_produced = all(flt(d.planned_qty) - flt(d.produced_qty) < 0.000001
+									for d in self.po_items)
+		if not all_items_produced:
+			return False
+
+		wo_status = frappe.get_all(
 			"Work Order",
-			filters={"production_plan": self.name},
+			filters={
+				"production_plan": self.name,
+				"status": ("not in", ["Closed", "Stopped"]),
+				"docstatus": ("<", 2),
+			},
 			fields="status",
-			pluck="status"
+			pluck="status",
 		)
-		return all(s == "Completed" for s in wo_status)
+		all_work_orders_completed = all(s == "Completed" for s in wo_status)
+		return all_work_orders_completed
 
 @frappe.whitelist()
 def download_raw_materials(doc, warehouses=None):
 	if isinstance(doc, str):
 		doc = frappe._dict(json.loads(doc))
 
-	item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
+	item_list = [['Item Code', 'Item Name', 'Description',
+		'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
 		'Projected Qty', 'Available Qty In Hand', 'Ordered Qty', 'Planned Qty',
 		'Reserved Qty for Production', 'Safety Stock', 'Required Qty']]
 
@@ -595,7 +662,8 @@
 	items = get_items_for_material_requests(doc, warehouses=warehouses, get_parent_warehouse_data=True)
 
 	for d in items:
-		item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'),
+		item_list.append([d.get('item_code'), d.get('item_name'),
+			d.get('description'), d.get('stock_uom'), d.get('warehouse'),
 			d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'),
 			d.get('planned_qty'), d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')])
 
@@ -947,11 +1015,8 @@
 	locations = get_available_item_locations(item.get("item_code"),
 		warehouses, item.get("quantity"), company, ignore_validation=True)
 
-	if not locations:
-		new_mr_items.append(item)
-		return
-
 	required_qty = item.get("quantity")
+	# get available material by transferring to production warehouse
 	for d in locations:
 		if required_qty <=0: return
 
@@ -962,14 +1027,34 @@
 			new_dict.update({
 				"quantity": quantity,
 				"material_request_type": "Material Transfer",
+				"uom": new_dict.get("stock_uom"),  # internal transfer should be in stock UOM
 				"from_warehouse": d.get("warehouse")
 			})
 
 			required_qty -= quantity
 			new_mr_items.append(new_dict)
 
+	# raise purchase request for remaining qty
 	if required_qty:
+		stock_uom, purchase_uom = frappe.db.get_value(
+			'Item',
+			item['item_code'],
+			['stock_uom', 'purchase_uom']
+		)
+
+		if purchase_uom != stock_uom and purchase_uom == item['uom']:
+			conversion_factor = get_uom_conversion_factor(item['item_code'], item['uom'])
+			if not (conversion_factor or frappe.flags.show_qty_in_stock_uom):
+				frappe.throw(_("UOM Conversion factor ({0} -> {1}) not found for item: {2}")
+					.format(purchase_uom, stock_uom, item['item_code']))
+
+			required_qty = required_qty / conversion_factor
+
+		if frappe.db.get_value("UOM", purchase_uom, "must_be_whole_number"):
+			required_qty = ceil(required_qty)
+
 		item["quantity"] = required_qty
+
 		new_mr_items.append(item)
 
 @frappe.whitelist()
@@ -983,13 +1068,10 @@
 	}
 
 def get_sub_assembly_items(bom_no, bom_data, to_produce_qty, indent=0):
-	data = get_children('BOM', parent = bom_no)
+	data = get_bom_children(parent=bom_no)
 	for d in data:
 		if d.expandable:
 			parent_item_code = frappe.get_cached_value("BOM", bom_no, "item")
-			bom_level = (frappe.get_cached_value("BOM", d.value, "bom_level")
-				if d.value else 0)
-
 			stock_qty = (d.stock_qty / d.parent_bom_qty) * flt(to_produce_qty)
 			bom_data.append(frappe._dict({
 				'parent_item_code': parent_item_code,
@@ -1000,10 +1082,15 @@
 				'uom': d.stock_uom,
 				'bom_no': d.value,
 				'is_sub_contracted_item': d.is_sub_contracted_item,
-				'bom_level': bom_level,
+				'bom_level': indent,
 				'indent': indent,
 				'stock_qty': stock_qty
 			}))
 
 			if d.value:
 				get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
+
+def set_default_warehouses(row, default_warehouses):
+	for field in ['wip_warehouse', 'fg_warehouse']:
+		if not row.get(field):
+			row[field] = default_warehouses.get(field)
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 2febc1e..eeab788 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -1,6 +1,7 @@
 # Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_to_date, flt, now_datetime, nowdate
 
 from erpnext.controllers.item_variant import create_variant
@@ -9,15 +10,16 @@
 	get_sales_orders,
 	get_warehouse_list,
 )
+from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
 	create_stock_reconciliation,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestProductionPlan(ERPNextTestCase):
+class TestProductionPlan(FrappeTestCase):
 	def setUp(self):
 		for item in ['Test Production Item 1', 'Subassembly Item 1',
 			'Raw Material Item 1', 'Raw Material Item 2']:
@@ -36,15 +38,24 @@
 			if not frappe.db.get_value('BOM', {'item': item}):
 				make_bom(item = item, raw_materials = raw_materials)
 
-	def test_production_plan(self):
+	def tearDown(self) -> None:
+		frappe.db.rollback()
+
+	def test_production_plan_mr_creation(self):
+		"Test if MRs are created for unavailable raw materials."
 		pln = create_production_plan(item_code='Test Production Item 1')
 		self.assertTrue(len(pln.mr_items), 2)
-		pln.make_material_request()
 
-		pln = frappe.get_doc('Production Plan', pln.name)
+		pln.make_material_request()
+		pln.reload()
 		self.assertTrue(pln.status, 'Material Requested')
-		material_requests = frappe.get_all('Material Request Item', fields = ['distinct parent'],
-			filters = {'production_plan': pln.name}, as_list=1)
+
+		material_requests = frappe.get_all(
+			'Material Request Item',
+			fields = ['distinct parent'],
+			filters = {'production_plan': pln.name},
+			as_list=1
+		)
 
 		self.assertTrue(len(material_requests), 2)
 
@@ -66,28 +77,43 @@
 		pln.cancel()
 
 	def test_production_plan_start_date(self):
+		"Test if Work Order has same Planned Start Date as Prod Plan."
 		planned_date = add_to_date(date=None, days=3)
-		plan = create_production_plan(item_code='Test Production Item 1', planned_start_date=planned_date)
+		plan = create_production_plan(
+			item_code='Test Production Item 1',
+			planned_start_date=planned_date
+		)
 		plan.make_work_order()
 
-		work_orders = frappe.get_all('Work Order', fields = ['name', 'planned_start_date'],
-			filters = {'production_plan': plan.name})
+		work_orders = frappe.get_all(
+			'Work Order',
+			fields = ['name', 'planned_start_date'],
+			filters = {'production_plan': plan.name}
+		)
 
 		self.assertEqual(work_orders[0].planned_start_date, planned_date)
 
 		for wo in work_orders:
 			frappe.delete_doc('Work Order', wo.name)
 
-		frappe.get_doc('Production Plan', plan.name).cancel()
+		plan.reload()
+		plan.cancel()
 
 	def test_production_plan_for_existing_ordered_qty(self):
+		"""
+		- Enable 'ignore_existing_ordered_qty'.
+		- Test if MR Planning table pulls Raw Material Qty even if it is in stock.
+		"""
 		sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
 			target="_Test Warehouse - _TC", qty=1, rate=110)
 		sr2 = create_stock_reconciliation(item_code="Raw Material Item 2",
 			target="_Test Warehouse - _TC", qty=1, rate=120)
 
-		pln = create_production_plan(item_code='Test Production Item 1', ignore_existing_ordered_qty=0)
-		self.assertTrue(len(pln.mr_items), 1)
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			ignore_existing_ordered_qty=1
+		)
+		self.assertTrue(len(pln.mr_items))
 		self.assertTrue(flt(pln.mr_items[0].quantity), 1.0)
 
 		sr1.cancel()
@@ -95,30 +121,47 @@
 		pln.cancel()
 
 	def test_production_plan_with_non_stock_item(self):
-		pln = create_production_plan(item_code='Test Production Item 1', include_non_stock_items=0)
+		"Test if MR Planning table includes Non Stock RM."
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			include_non_stock_items=1
+		)
 		self.assertTrue(len(pln.mr_items), 3)
 		pln.cancel()
 
 	def test_production_plan_without_multi_level(self):
-		pln = create_production_plan(item_code='Test Production Item 1', use_multi_level_bom=0)
+		"Test MR Planning table for non exploded BOM."
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			use_multi_level_bom=0
+		)
 		self.assertTrue(len(pln.mr_items), 2)
 		pln.cancel()
 
 	def test_production_plan_without_multi_level_for_existing_ordered_qty(self):
+		"""
+		- Disable 'ignore_existing_ordered_qty'.
+		- Test if MR Planning table avoids pulling Raw Material Qty as it is in stock for
+		non exploded BOM.
+		"""
 		sr1 = create_stock_reconciliation(item_code="Raw Material Item 1",
 			target="_Test Warehouse - _TC", qty=1, rate=130)
 		sr2 = create_stock_reconciliation(item_code="Subassembly Item 1",
 			target="_Test Warehouse - _TC", qty=1, rate=140)
 
-		pln = create_production_plan(item_code='Test Production Item 1',
-			use_multi_level_bom=0, ignore_existing_ordered_qty=0)
-		self.assertTrue(len(pln.mr_items), 0)
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			use_multi_level_bom=0,
+			ignore_existing_ordered_qty=0
+		)
+		self.assertFalse(len(pln.mr_items))
 
 		sr1.cancel()
 		sr2.cancel()
 		pln.cancel()
 
 	def test_production_plan_sales_orders(self):
+		"Test if previously fulfilled SO (with WO) is pulled into Prod Plan."
 		item = 'Test Production Item 1'
 		so = make_sales_order(item_code=item, qty=1)
 		sales_order = so.name
@@ -166,24 +209,25 @@
 		self.assertEqual(sales_orders, [])
 
 	def test_production_plan_combine_items(self):
+		"Test combining FG items in Production Plan."
 		item = 'Test Production Item 1'
-		so = make_sales_order(item_code=item, qty=1)
+		so1 = make_sales_order(item_code=item, qty=1)
 
 		pln = frappe.new_doc('Production Plan')
-		pln.company = so.company
+		pln.company = so1.company
 		pln.get_items_from = 'Sales Order'
 		pln.append('sales_orders', {
-			'sales_order': so.name,
-			'sales_order_date': so.transaction_date,
-			'customer': so.customer,
-			'grand_total': so.grand_total
+			'sales_order': so1.name,
+			'sales_order_date': so1.transaction_date,
+			'customer': so1.customer,
+			'grand_total': so1.grand_total
 		})
-		so = make_sales_order(item_code=item, qty=2)
+		so2 = make_sales_order(item_code=item, qty=2)
 		pln.append('sales_orders', {
-			'sales_order': so.name,
-			'sales_order_date': so.transaction_date,
-			'customer': so.customer,
-			'grand_total': so.grand_total
+			'sales_order': so2.name,
+			'sales_order_date': so2.transaction_date,
+			'customer': so2.customer,
+			'grand_total': so2.grand_total
 		})
 		pln.combine_items = 1
 		pln.get_items()
@@ -214,28 +258,82 @@
 			so_wo_qty = frappe.db.get_value('Sales Order Item', so_item, 'work_order_qty')
 			self.assertEqual(so_wo_qty, 0.0)
 
-		latest_plan = frappe.get_doc('Production Plan', pln.name)
-		latest_plan.cancel()
+		pln.reload()
+		pln.cancel()
+
+	def test_production_plan_combine_subassembly(self):
+		"""
+		Test combining Sub assembly items belonging to the same BOM in Prod Plan.
+		1) Red-Car -> Wheel (sub assembly) > BOM-WHEEL-001
+		2) Green-Car -> Wheel (sub assembly) > BOM-WHEEL-001
+		"""
+		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+		bom_tree_1 = {
+			"Red-Car": {"Wheel": {"Rubber": {}}}
+		}
+		bom_tree_2 = {
+			"Green-Car": {"Wheel": {"Rubber": {}}}
+		}
+
+		parent_bom_1 = create_nested_bom(bom_tree_1, prefix="")
+		parent_bom_2 = create_nested_bom(bom_tree_2, prefix="")
+
+		# make sure both boms use same subassembly bom
+		subassembly_bom = parent_bom_1.items[0].bom_no
+		frappe.db.set_value("BOM Item", parent_bom_2.items[0].name, "bom_no", subassembly_bom)
+
+		plan = create_production_plan(item_code="Red-Car", use_multi_level_bom=1, do_not_save=True)
+		plan.append("po_items", { # Add Green-Car to Prod Plan
+			'use_multi_level_bom': 1,
+			'item_code': "Green-Car",
+			'bom_no': frappe.db.get_value('Item', "Green-Car", 'default_bom'),
+			'planned_qty': 1,
+			'planned_start_date': now_datetime()
+		})
+		plan.get_sub_assembly_items()
+		self.assertTrue(len(plan.sub_assembly_items), 2)
+
+		plan.combine_sub_items = 1
+		plan.get_sub_assembly_items()
+
+		self.assertTrue(len(plan.sub_assembly_items), 1) # check if sub-assembly items merged
+		self.assertEqual(plan.sub_assembly_items[0].qty, 2.0)
+		self.assertEqual(plan.sub_assembly_items[0].stock_qty, 2.0)
+
+		# change warehouse in one row, sub-assemblies should not merge
+		plan.po_items[0].warehouse = "Finished Goods - _TC"
+		plan.get_sub_assembly_items()
+		self.assertTrue(len(plan.sub_assembly_items), 2)
 
 	def test_pp_to_mr_customer_provided(self):
-		#Material Request from Production Plan for Customer Provided
+		" Test Material Request from Production Plan for Customer Provided Item."
 		create_item('CUST-0987', is_customer_provided_item = 1, customer = '_Test Customer', is_purchase_item = 0)
 		create_item('Production Item CUST')
+
 		for item, raw_materials in {'Production Item CUST': ['Raw Material Item 1', 'CUST-0987']}.items():
 			if not frappe.db.get_value('BOM', {'item': item}):
 				make_bom(item = item, raw_materials = raw_materials)
 		production_plan = create_production_plan(item_code = 'Production Item CUST')
 		production_plan.make_material_request()
-		material_request = frappe.db.get_value('Material Request Item', {'production_plan': production_plan.name, 'item_code': 'CUST-0987'}, 'parent')
+
+		material_request = frappe.db.get_value(
+			'Material Request Item',
+			{'production_plan': production_plan.name, 'item_code': 'CUST-0987'},
+			'parent'
+		)
 		mr = frappe.get_doc('Material Request', material_request)
+
 		self.assertTrue(mr.material_request_type, 'Customer Provided')
 		self.assertTrue(mr.customer, '_Test Customer')
 
 	def test_production_plan_with_multi_level_bom(self):
-		#|Item Code			|	Qty	|
-		#|Test BOM 1	 		|	1	|
-		#|	Test BOM 2		|	2	|
-		#|		Test BOM 3	|	3	|
+		"""
+		Item Code	|	Qty	|
+		|Test BOM 1	|	1	|
+		|Test BOM 2	|	2	|
+		|Test BOM 3	|	3	|
+		"""
 
 		for item_code in ["Test BOM 1", "Test BOM 2", "Test BOM 3", "Test RM BOM 1"]:
 			create_item(item_code, is_stock_item=1)
@@ -264,15 +362,18 @@
 		pln.make_work_order()
 
 		#last level sub-assembly work order produce qty
-		to_produce_qty = frappe.db.get_value("Work Order",
-			{"production_plan": pln.name, "production_item": "Test BOM 3"}, "qty")
+		to_produce_qty = frappe.db.get_value(
+			"Work Order",
+			{"production_plan": pln.name, "production_item": "Test BOM 3"},
+			"qty"
+		)
 
 		self.assertEqual(to_produce_qty, 18.0)
 		pln.cancel()
 		frappe.delete_doc("Production Plan", pln.name)
 
 	def test_get_warehouse_list_group(self):
-		"""Check if required warehouses are returned"""
+		"Check if required child warehouses are returned."
 		warehouse_json = '[{\"warehouse\":\"_Test Warehouse Group - _TC\"}]'
 
 		warehouses = set(get_warehouse_list(warehouse_json))
@@ -284,6 +385,7 @@
 				msg=f"Following warehouses were expected {', '.join(missing_warehouse)}")
 
 	def test_get_warehouse_list_single(self):
+		"Check if same warehouse is returned in absence of child warehouses."
 		warehouse_json = '[{\"warehouse\":\"_Test Scrap Warehouse - _TC\"}]'
 
 		warehouses = set(get_warehouse_list(warehouse_json))
@@ -292,6 +394,7 @@
 		self.assertEqual(warehouses, expected_warehouses)
 
 	def test_get_sales_order_with_variant(self):
+		"Check if Template BOM is fetched in absence of Variant BOM."
 		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)
@@ -347,7 +450,218 @@
 
 		frappe.db.rollback()
 
+	def test_subassmebly_sorting(self):
+		"Test subassembly sorting in case of multiple items with nested BOMs."
+		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+
+		prefix = "_TestLevel_"
+		boms = {
+			"Assembly": {
+				"SubAssembly1": {"ChildPart1": {}, "ChildPart2": {},},
+				"ChildPart6": {},
+				"SubAssembly4": {"SubSubAssy2": {"ChildPart7": {}}},
+			},
+			"MegaDeepAssy": {
+				"SecretSubassy": {"SecretPart": {"VerySecret" : { "SuperSecret": {"Classified": {}}}},},
+																# ^ assert that this is
+																# first item in subassy table
+			}
+		}
+		create_nested_bom(boms, prefix=prefix)
+
+		items = [prefix + item_code for item_code in boms.keys()]
+		plan = create_production_plan(item_code=items[0], do_not_save=True)
+		plan.append("po_items", {
+			'use_multi_level_bom': 1,
+			'item_code': items[1],
+			'bom_no': frappe.db.get_value('Item', items[1], 'default_bom'),
+			'planned_qty': 1,
+			'planned_start_date': now_datetime()
+		})
+		plan.get_sub_assembly_items()
+
+		bom_level_order = [d.bom_level for d in plan.sub_assembly_items]
+		self.assertEqual(bom_level_order, sorted(bom_level_order, reverse=True))
+		# lowest most level of subassembly should be first
+		self.assertIn("SuperSecret", plan.sub_assembly_items[0].production_item)
+
+	def test_multiple_work_order_for_production_plan_item(self):
+		"Test producing Prod Plan (making WO) in parts."
+		def create_work_order(item, pln, qty):
+			# Get Production Items
+			items_data = pln.get_production_items()
+
+			# Update qty
+			items_data[(item, None, None)]["qty"] = qty
+
+			# Create and Submit Work Order for each item in items_data
+			for key, item in items_data.items():
+				if pln.sub_assembly_items:
+					item['use_multi_level_bom'] = 0
+
+				wo_name = pln.create_work_order(item)
+				wo_doc = frappe.get_doc("Work Order", wo_name)
+				wo_doc.update({
+					'wip_warehouse': 'Work In Progress - _TC',
+					'fg_warehouse': 'Finished Goods - _TC'
+				})
+				wo_doc.submit()
+				wo_list.append(wo_name)
+
+		item = "Test Production Item 1"
+		raw_materials = ["Raw Material Item 1", "Raw Material Item 2"]
+
+		# Create BOM
+		bom = make_bom(item=item, raw_materials=raw_materials)
+
+		# Create Production Plan
+		pln = create_production_plan(item_code=bom.item, planned_qty=5)
+
+		# All the created Work Orders
+		wo_list = []
+
+		# Create and Submit 1st Work Order for 3 qty
+		create_work_order(item, pln, 3)
+		pln.reload()
+		self.assertEqual(pln.po_items[0].ordered_qty, 3)
+
+		# Create and Submit 2nd Work Order for 2 qty
+		create_work_order(item, pln, 2)
+		pln.reload()
+		self.assertEqual(pln.po_items[0].ordered_qty, 5)
+
+		# Overproduction
+		self.assertRaises(OverProductionError, create_work_order, item=item, pln=pln, qty=2)
+
+		# Cancel 1st Work Order
+		wo1 = frappe.get_doc("Work Order", wo_list[0])
+		wo1.cancel()
+		pln.reload()
+		self.assertEqual(pln.po_items[0].ordered_qty, 2)
+
+		# Cancel 2nd Work Order
+		wo2 = frappe.get_doc("Work Order", wo_list[1])
+		wo2.cancel()
+		pln.reload()
+		self.assertEqual(pln.po_items[0].ordered_qty, 0)
+
+	def test_production_plan_pending_qty_with_sales_order(self):
+		"""
+		Test Prod Plan impact via: SO -> Prod Plan -> WO -> SE -> SE (cancel)
+		"""
+		from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+		from erpnext.manufacturing.doctype.work_order.work_order import (
+			make_stock_entry as make_se_from_wo,
+		)
+
+		make_stock_entry(item_code="Raw Material Item 1",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+		make_stock_entry(item_code="Raw Material Item 2",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+
+		item = 'Test Production Item 1'
+		so = make_sales_order(item_code=item, qty=1)
+
+		pln = create_production_plan(
+			company=so.company,
+			get_items_from="Sales Order",
+			sales_order=so,
+			skip_getting_mr_items=True
+		)
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+		wo = make_wo_order_test_record(
+			item_code=item, qty=1,
+			company=so.company,
+			wip_warehouse='Work In Progress - _TC',
+			fg_warehouse='Finished Goods - _TC',
+			skip_transfer=1,
+			use_multi_level_bom=1,
+			do_not_submit=True
+		)
+		wo.production_plan = pln.name
+		wo.production_plan_item = pln.po_items[0].name
+		wo.submit()
+
+		se = frappe.get_doc(make_se_from_wo(wo.name, "Manufacture", 1))
+		se.submit()
+
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 0)
+
+		se.cancel()
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+	def test_production_plan_pending_qty_independent_items(self):
+		"Test Prod Plan impact if items are added independently (no from SO or MR)."
+		from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
+		from erpnext.manufacturing.doctype.work_order.work_order import (
+			make_stock_entry as make_se_from_wo,
+		)
+
+		make_stock_entry(item_code="Raw Material Item 1",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+		make_stock_entry(item_code="Raw Material Item 2",
+			target="Work In Progress - _TC",
+			qty=2, basic_rate=100
+		)
+
+		pln = create_production_plan(
+			item_code='Test Production Item 1',
+			skip_getting_mr_items=True
+		)
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+		wo = make_wo_order_test_record(
+			item_code='Test Production Item 1', qty=1,
+			company=pln.company,
+			wip_warehouse='Work In Progress - _TC',
+			fg_warehouse='Finished Goods - _TC',
+			skip_transfer=1,
+			use_multi_level_bom=1,
+			do_not_submit=True
+		)
+		wo.production_plan = pln.name
+		wo.production_plan_item = pln.po_items[0].name
+		wo.submit()
+
+		se = frappe.get_doc(make_se_from_wo(wo.name, "Manufacture", 1))
+		se.submit()
+
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 0)
+
+		se.cancel()
+		pln.reload()
+		self.assertEqual(pln.po_items[0].pending_qty, 1)
+
+	def test_qty_based_status(self):
+		pp = frappe.new_doc("Production Plan")
+		pp.po_items = [
+			frappe._dict(planned_qty=5, produce_qty=4)
+		]
+		self.assertFalse(pp.all_items_completed())
+
+		pp.po_items = [
+			frappe._dict(planned_qty=5, produce_qty=10),
+			frappe._dict(planned_qty=5, produce_qty=4)
+		]
+		self.assertFalse(pp.all_items_completed())
+
+
 def create_production_plan(**args):
+	"""
+	sales_order (obj): Sales Order Doc Object
+	get_items_from (str): Sales Order/Material Request
+	skip_getting_mr_items (bool): Whether or not to plan for new MRs
+	"""
 	args = frappe._dict(args)
 
 	pln = frappe.get_doc({
@@ -355,20 +669,35 @@
 		'company': args.company or '_Test Company',
 		'customer': args.customer or '_Test Customer',
 		'posting_date': nowdate(),
-		'include_non_stock_items': args.include_non_stock_items or 1,
-		'include_subcontracted_items': args.include_subcontracted_items or 1,
-		'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 1,
-		'po_items': [{
+		'include_non_stock_items': args.include_non_stock_items or 0,
+		'include_subcontracted_items': args.include_subcontracted_items or 0,
+		'ignore_existing_ordered_qty': args.ignore_existing_ordered_qty or 0,
+		'get_items_from': 'Sales Order'
+	})
+
+	if not args.get("sales_order"):
+		pln.append('po_items', {
 			'use_multi_level_bom': args.use_multi_level_bom or 1,
 			'item_code': args.item_code,
 			'bom_no': frappe.db.get_value('Item', args.item_code, 'default_bom'),
 			'planned_qty': args.planned_qty or 1,
 			'planned_start_date': args.planned_start_date or now_datetime()
-		}]
-	})
-	mr_items = get_items_for_material_requests(pln.as_dict())
-	for d in mr_items:
-		pln.append('mr_items', d)
+		})
+
+	if args.get("get_items_from") == "Sales Order" and args.get("sales_order"):
+		so = args.get("sales_order")
+		pln.append('sales_orders', {
+			'sales_order': so.name,
+			'sales_order_date': so.transaction_date,
+			'customer': so.customer,
+			'grand_total': so.grand_total
+		})
+		pln.get_items()
+
+	if not args.get("skip_getting_mr_items"):
+		mr_items = get_items_for_material_requests(pln.as_dict())
+		for d in mr_items:
+			pln.append('mr_items', d)
 
 	if not args.do_not_save:
 		pln.insert()
diff --git a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
index 657ee35..45ea26c 100644
--- a/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
+++ b/erpnext/manufacturing/doctype/production_plan_sub_assembly_item/production_plan_sub_assembly_item.json
@@ -102,7 +102,6 @@
   },
   {
    "columns": 1,
-   "fetch_from": "bom_no.bom_level",
    "fieldname": "bom_level",
    "fieldtype": "Int",
    "in_list_view": 1,
@@ -189,7 +188,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-28 20:10:56.296410",
+ "modified": "2022-01-30 21:31:10.527559",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan Sub Assembly Item",
@@ -198,5 +197,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 8bd60ea..696d9bc 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -2,14 +2,14 @@
 # See license.txt
 import frappe
 from frappe.test_runner import make_test_records
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.manufacturing.doctype.job_card.job_card import OperationSequenceError
 from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 from erpnext.stock.doctype.item.test_item import make_item
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestRouting(ERPNextTestCase):
+class TestRouting(FrappeTestCase):
 	@classmethod
 	def setUpClass(cls):
 		cls.item_code = "Test Routing Item - A"
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index e7eb9c6..bc07d22 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -2,6 +2,7 @@
 # License: GNU General Public License v3. See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase, timeout
 from frappe.utils import add_days, add_months, cint, flt, now, today
 
 from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
@@ -21,10 +22,9 @@
 from erpnext.stock.doctype.stock_entry import test_stock_entry
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.utils import get_bin
-from erpnext.tests.utils import ERPNextTestCase, timeout
 
 
-class TestWorkOrder(ERPNextTestCase):
+class TestWorkOrder(FrappeTestCase):
 	def setUp(self):
 		self.warehouse = '_Test Warehouse 2 - _TC'
 		self.item = '_Test Item'
@@ -201,6 +201,21 @@
 		self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production),
 			cint(bin1_on_start_production.reserved_qty_for_production))
 
+	def test_reserved_qty_for_production_closed(self):
+
+		wo1 = make_wo_order_test_record(item="_Test FG Item", qty=2,
+			source_warehouse=self.warehouse)
+		item = wo1.required_items[0].item_code
+		bin_before = get_bin(item, self.warehouse)
+		bin_before.update_reserved_qty_for_production()
+
+		make_wo_order_test_record(item="_Test FG Item", qty=2,
+			source_warehouse=self.warehouse)
+		close_work_order(wo1.name, "Closed")
+
+		bin_after = get_bin(item, self.warehouse)
+		self.assertEqual(bin_before.reserved_qty_for_production, bin_after.reserved_qty_for_production)
+
 	def test_backflush_qty_for_overpduction_manufacture(self):
 		cancel_stock_entry = []
 		allow_overproduction("overproduction_percentage_for_work_order", 30)
@@ -703,7 +718,8 @@
 		wo = make_wo_order_test_record(item=item_name, qty=1, source_warehouse=source_warehouse,
 			company=company)
 
-		self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture')
+		stock_entry = frappe.get_doc(make_stock_entry(wo.name, 'Material Transfer for Manufacture'))
+		self.assertRaises(frappe.ValidationError, stock_entry.save)
 
 	def test_wo_completion_with_pl_bom(self):
 		from erpnext.manufacturing.doctype.bom.test_bom import (
@@ -911,6 +927,54 @@
 
 		self.assertEqual(wo1.operations[0].time_in_mins, wo2.operations[0].time_in_mins)
 
+	def test_partial_manufacture_entries(self):
+		cancel_stock_entry = []
+
+		frappe.db.set_value("Manufacturing Settings", None,
+			"backflush_raw_materials_based_on", "Material Transferred for Manufacture")
+
+		wo_order = make_wo_order_test_record(planned_start_date=now(), qty=100)
+		ste1 = test_stock_entry.make_stock_entry(item_code="_Test Item",
+			target="_Test Warehouse - _TC", qty=120, basic_rate=5000.0)
+		ste2 = test_stock_entry.make_stock_entry(item_code="_Test Item Home Desktop 100",
+			target="_Test Warehouse - _TC", qty=240, basic_rate=1000.0)
+
+		cancel_stock_entry.extend([ste1.name, ste2.name])
+
+		sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
+		for row in sm.get('items'):
+			if row.get('item_code') == '_Test Item':
+				row.qty = 110
+
+		sm.submit()
+		cancel_stock_entry.append(sm.name)
+
+		s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
+		for row in s.get('items'):
+			if row.get('item_code') == '_Test Item':
+				self.assertEqual(row.get('qty'), 100)
+		s.submit()
+		cancel_stock_entry.append(s.name)
+
+		s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
+		for row in s1.get('items'):
+			if row.get('item_code') == '_Test Item':
+				self.assertEqual(row.get('qty'), 5)
+		s1.submit()
+		cancel_stock_entry.append(s1.name)
+
+		s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
+		for row in s2.get('items'):
+			if row.get('item_code') == '_Test Item':
+				self.assertEqual(row.get('qty'), 5)
+
+		cancel_stock_entry.reverse()
+		for ste in cancel_stock_entry:
+			doc = frappe.get_doc("Stock Entry", ste)
+			doc.cancel()
+
+		frappe.db.set_value("Manufacturing Settings", None,
+			"backflush_raw_materials_based_on", "BOM")
 
 def update_job_card(job_card, jc_qty=None):
 	employee = frappe.db.get_value('Employee', {'status': 'Active'}, 'name')
@@ -976,7 +1040,7 @@
 	wo_order.scrap_warehouse = args.fg_warehouse or "_Test Scrap Warehouse - _TC"
 	wo_order.company = args.company or "_Test Company"
 	wo_order.stock_uom = args.stock_uom or "_Test UOM"
-	wo_order.use_multi_level_bom=0
+	wo_order.use_multi_level_bom= args.use_multi_level_bom or 0
 	wo_order.skip_transfer=args.skip_transfer or 0
 	wo_order.get_items_and_operations_from_bom()
 	wo_order.sales_order = args.sales_order or None
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 12cd58f..9452a63 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -333,12 +333,13 @@
    "options": "fa fa-wrench"
   },
   {
-   "default": "Work Order",
    "depends_on": "operations",
+   "fetch_from": "bom_no.transfer_material_against",
+   "fetch_if_empty": 1,
    "fieldname": "transfer_material_against",
    "fieldtype": "Select",
    "label": "Transfer Material Against",
-   "options": "Work Order\nJob Card"
+   "options": "\nWork Order\nJob Card"
   },
   {
    "fieldname": "operations",
@@ -574,7 +575,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-08 17:36:07.016300",
+ "modified": "2022-01-24 21:18:12.160114",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
@@ -607,6 +608,7 @@
  ],
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "title_field": "production_item",
  "track_changes": 1,
  "track_seen": 1
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 170454c..374ab86 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -8,6 +8,8 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder import Case
+from frappe.query_builder.functions import Sum
 from frappe.utils import (
 	cint,
 	date_diff,
@@ -31,6 +33,7 @@
 from erpnext.stock.doctype.item.item import get_item_defaults, validate_end_of_life
 from erpnext.stock.doctype.serial_no.serial_no import (
 	auto_make_serial_nos,
+	clean_serial_no_string,
 	get_auto_serial_nos,
 	get_serial_nos,
 )
@@ -65,6 +68,7 @@
 		self.validate_warehouse_belongs_to_company()
 		self.calculate_operating_cost()
 		self.validate_qty()
+		self.validate_transfer_against()
 		self.validate_operation_time()
 		self.status = self.get_status()
 
@@ -268,7 +272,7 @@
 
 			produced_qty = total_qty[0][0] if total_qty else 0
 
-		production_plan.run_method("update_produced_qty", produced_qty, self.production_plan_item)
+		production_plan.run_method("update_produced_pending_qty", produced_qty, self.production_plan_item)
 
 	def before_submit(self):
 		self.create_serial_no_batch_no()
@@ -356,6 +360,7 @@
 			frappe.delete_doc("Batch", row.name)
 
 	def make_serial_nos(self, args):
+		self.serial_no = clean_serial_no_string(self.serial_no)
 		serial_no_series = frappe.get_cached_value("Item", self.production_item, "serial_no_series")
 		if serial_no_series:
 			self.serial_no = get_auto_serial_nos(serial_no_series, self.qty)
@@ -445,7 +450,13 @@
 
 	def update_ordered_qty(self):
 		if self.production_plan and self.production_plan_item:
-			qty = self.qty if self.docstatus == 1 else 0
+			qty = frappe.get_value("Production Plan Item", self.production_plan_item, "ordered_qty") or 0.0
+
+			if self.docstatus == 1:
+				qty += self.qty
+			elif self.docstatus == 2:
+				qty -= self.qty
+
 			frappe.db.set_value('Production Plan Item',
 				self.production_plan_item, 'ordered_qty', qty)
 
@@ -534,7 +545,7 @@
 				if node.is_bom:
 					operations.extend(_get_operations(node.name, qty=node.exploded_qty))
 
-		bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
+		bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
 		operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
 
 		for correct_index, operation in enumerate(operations, start=1):
@@ -615,7 +626,7 @@
 			frappe.delete_doc("Job Card", d.name)
 
 	def validate_production_item(self):
-		if frappe.db.get_value("Item", self.production_item, "has_variants"):
+		if frappe.get_cached_value("Item", self.production_item, "has_variants"):
 			frappe.throw(_("Work Order cannot be raised against a Item Template"), ItemHasVariantError)
 
 		if self.production_item:
@@ -625,6 +636,31 @@
 		if not self.qty > 0:
 			frappe.throw(_("Quantity to Manufacture must be greater than 0."))
 
+		if self.production_plan and self.production_plan_item:
+			qty_dict = frappe.db.get_value("Production Plan Item", self.production_plan_item, ["planned_qty", "ordered_qty"], as_dict=1)
+
+			allowance_qty =flt(frappe.db.get_single_value("Manufacturing Settings",
+			"overproduction_percentage_for_work_order"))/100 * qty_dict.get("planned_qty", 0)
+
+			max_qty = qty_dict.get("planned_qty", 0) + allowance_qty - qty_dict.get("ordered_qty", 0)
+
+			if max_qty < 1:
+				frappe.throw(_("Cannot produce more item for {0}")
+				.format(self.production_item), OverProductionError)
+			elif self.qty > max_qty:
+				frappe.throw(_("Cannot produce more than {0} items for {1}")
+				.format(max_qty, self.production_item), OverProductionError)
+
+	def validate_transfer_against(self):
+		if not self.docstatus == 1:
+			# let user configure operations until they're ready to submit
+			return
+		if not self.operations:
+			self.transfer_material_against = "Work Order"
+		if not self.transfer_material_against:
+			frappe.throw(_("Setting {} is required").format(self.meta.get_label("transfer_material_against")), title=_("Missing value"))
+
+
 	def validate_operation_time(self):
 		for d in self.operations:
 			if not d.time_in_mins > 0:
@@ -818,7 +854,7 @@
 	res = res[0]
 	if skip_bom_info: return res
 
-	filters = {"item": item, "is_default": 1}
+	filters = {"item": item, "is_default": 1, "docstatus": 1}
 
 	if project:
 		filters = {"item": item, "project": project}
@@ -1155,3 +1191,27 @@
 	doc.set_item_locations()
 
 	return doc
+
+def get_reserved_qty_for_production(item_code: str, warehouse: str) -> float:
+	"""Get total reserved quantity for any item in specified warehouse"""
+	wo = frappe.qb.DocType("Work Order")
+	wo_item = frappe.qb.DocType("Work Order Item")
+
+	return (
+	frappe.qb
+		.from_(wo)
+		.from_(wo_item)
+		.select(Sum(Case()
+				.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
+				.else_(wo_item.required_qty - wo_item.consumed_qty))
+			)
+		.where(
+			(wo_item.item_code == item_code)
+			& (wo_item.parent == wo.name)
+			& (wo.docstatus == 1)
+			& (wo_item.source_warehouse == warehouse)
+			& (wo.status.notin(["Stopped", "Completed", "Closed"]))
+			& ((wo_item.required_qty > wo_item.transferred_qty)
+				| (wo_item.required_qty > wo_item.consumed_qty))
+		)
+	).run()[0][0] or 0.0
diff --git a/erpnext/manufacturing/doctype/workstation/test_workstation.py b/erpnext/manufacturing/doctype/workstation/test_workstation.py
index c298c0a..dd51017 100644
--- a/erpnext/manufacturing/doctype/workstation/test_workstation.py
+++ b/erpnext/manufacturing/doctype/workstation/test_workstation.py
@@ -2,6 +2,7 @@
 # See license.txt
 import frappe
 from frappe.test_runner import make_test_records
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.manufacturing.doctype.operation.test_operation import make_operation
 from erpnext.manufacturing.doctype.routing.test_routing import create_routing, setup_bom
@@ -10,13 +11,12 @@
 	WorkstationHolidayError,
 	check_if_within_operating_hours,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 test_dependencies = ["Warehouse"]
 test_records = frappe.get_test_records('Workstation')
 make_test_records('Workstation')
 
-class TestWorkstation(ERPNextTestCase):
+class TestWorkstation(FrappeTestCase):
 	def test_validate_timings(self):
 		check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 11:00:00", "2013-02-02 19:00:00")
 		check_if_within_operating_hours("_Test Workstation 1", "Operation 1", "2013-02-02 10:00:00", "2013-02-02 20:00:00")
diff --git a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
index 25de2e0..19a80ab 100644
--- a/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
+++ b/erpnext/manufacturing/report/bom_explorer/bom_explorer.py
@@ -26,8 +26,7 @@
 			'item_code': item.item_code,
 			'item_name': item.item_name,
 			'indent': indent,
-			'bom_level': (frappe.get_cached_value("BOM", item.bom_no, "bom_level")
-				if item.bom_no else ""),
+			'bom_level': indent,
 			'bom': item.bom_no,
 			'qty': item.qty * qty,
 			'uom': item.uom,
@@ -73,7 +72,7 @@
 		},
 		{
 			"label": "BOM Level",
-			"fieldtype": "Data",
+			"fieldtype": "Int",
 			"fieldname": "bom_level",
 			"width": 100
 		},
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
index 7468e34..0eb22a2 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.js
@@ -4,6 +4,39 @@
 
 frappe.query_reports["BOM Operations Time"] = {
 	"filters": [
-
+		{
+			"fieldname": "item_code",
+			"label": __("Item Code"),
+			"fieldtype": "Link",
+			"width": "100",
+			"options": "Item",
+			"get_query": () =>{
+				return {
+					filters: { "disabled": 0, "is_stock_item": 1 }
+				}
+			}
+		},
+		{
+			"fieldname": "bom_id",
+			"label": __("BOM ID"),
+			"fieldtype": "MultiSelectList",
+			"width": "100",
+			"options": "BOM",
+			"get_data": function(txt) {
+				return frappe.db.get_link_options("BOM", txt);
+			},
+			"get_query": () =>{
+				return {
+					filters: { "docstatus": 1, "is_active": 1, "with_operations": 1 }
+				}
+			}
+		},
+		{
+			"fieldname": "workstation",
+			"label": __("Workstation"),
+			"fieldtype": "Link",
+			"width": "100",
+			"options": "Workstation"
+		},
 	]
 };
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
index 665c5b9..8162017 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.json
@@ -1,14 +1,16 @@
 {
- "add_total_row": 0,
+ "add_total_row": 1,
+ "columns": [],
  "creation": "2020-03-03 01:41:20.862521",
  "disable_prepared_report": 0,
  "disabled": 0,
  "docstatus": 0,
  "doctype": "Report",
+ "filters": [],
  "idx": 0,
  "is_standard": "Yes",
  "letter_head": "",
- "modified": "2020-03-03 01:41:20.862521",
+ "modified": "2022-01-20 14:21:47.771591",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Operations Time",
diff --git a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
index e7a818a..eda9eb9 100644
--- a/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
+++ b/erpnext/manufacturing/report/bom_operations_time/bom_operations_time.py
@@ -12,19 +12,15 @@
 	return columns, data
 
 def get_data(filters):
-	data = []
+	bom_wise_data = {}
+	bom_data, report_data = [], []
 
-	bom_data = []
-	for d in frappe.db.sql("""
-		SELECT
-			bom.name, bom.item, bom.item_name, bom.uom,
-			bomps.operation, bomps.workstation, bomps.time_in_mins
-		FROM `tabBOM` bom, `tabBOM Operation` bomps
-		WHERE
-			bom.docstatus = 1 and bom.is_active = 1 and bom.name = bomps.parent
-		""", as_dict=1):
+	bom_operation_data = get_filtered_data(filters)
+
+	for d in bom_operation_data:
 		row = get_args()
 		if d.name not in bom_data:
+			bom_wise_data[d.name] = []
 			bom_data.append(d.name)
 			row.update(d)
 		else:
@@ -34,14 +30,49 @@
 				"time_in_mins": d.time_in_mins
 			})
 
-		data.append(row)
+		# maintain BOM wise data for grouping such as:
+		# {"BOM A": [{Row1}, {Row2}], "BOM B": ...}
+		bom_wise_data[d.name].append(row)
 
 	used_as_subassembly_items = get_bom_count(bom_data)
 
-	for d in data:
-		d.used_as_subassembly_items = used_as_subassembly_items.get(d.name, 0)
+	for d in bom_wise_data:
+		for row in bom_wise_data[d]:
+			row.used_as_subassembly_items = used_as_subassembly_items.get(row.name, 0)
+			report_data.append(row)
 
-	return data
+	return report_data
+
+def get_filtered_data(filters):
+	bom = frappe.qb.DocType("BOM")
+	bom_ops = frappe.qb.DocType("BOM Operation")
+
+	bom_ops_query = (
+		frappe.qb.from_(bom)
+		.join(bom_ops).on(bom.name == bom_ops.parent)
+		.select(
+			bom.name, bom.item, bom.item_name, bom.uom,
+			bom_ops.operation, bom_ops.workstation, bom_ops.time_in_mins
+		).where(
+			(bom.docstatus == 1)
+			& (bom.is_active == 1)
+		)
+	)
+
+	if filters.get("item_code"):
+		bom_ops_query = bom_ops_query.where(bom.item == filters.get("item_code"))
+
+	if filters.get("bom_id"):
+		bom_ops_query = bom_ops_query.where(bom.name.isin(filters.get("bom_id")))
+
+	if filters.get("workstation"):
+		bom_ops_query = bom_ops_query.where(
+			bom_ops.workstation == filters.get("workstation")
+		)
+
+	bom_operation_data = bom_ops_query.run(as_dict=True)
+
+	return bom_operation_data
 
 def get_bom_count(bom_data):
 	data = frappe.get_all("BOM Item",
@@ -68,13 +99,13 @@
 		"options": "BOM",
 		"fieldname": "name",
 		"fieldtype": "Link",
-		"width": 140
+		"width": 220
 	}, {
-		"label": _("BOM Item Code"),
+		"label": _("Item Code"),
 		"options": "Item",
 		"fieldname": "item",
 		"fieldtype": "Link",
-		"width": 140
+		"width": 150
 	}, {
 		"label": _("Item Name"),
 		"fieldname": "item_name",
@@ -85,13 +116,13 @@
 		"options": "UOM",
 		"fieldname": "uom",
 		"fieldtype": "Link",
-		"width": 140
+		"width": 100
 	}, {
 		"label": _("Operation"),
 		"options": "Operation",
 		"fieldname": "operation",
 		"fieldtype": "Link",
-		"width": 120
+		"width": 140
 	}, {
 		"label": _("Workstation"),
 		"options": "Workstation",
@@ -101,11 +132,11 @@
 	}, {
 		"label": _("Time (In Mins)"),
 		"fieldname": "time_in_mins",
-		"fieldtype": "Int",
-		"width": 140
+		"fieldtype": "Float",
+		"width": 120
 	}, {
 		"label": _("Sub-assembly BOM Count"),
 		"fieldname": "used_as_subassembly_items",
 		"fieldtype": "Int",
-		"width": 180
+		"width": 200
 	}]
diff --git a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
index 090a3e7..2693352 100644
--- a/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
+++ b/erpnext/manufacturing/report/bom_stock_calculated/bom_stock_calculated.py
@@ -89,10 +89,10 @@
 			GROUP BY bom_item.item_code""".format(qty_field=qty_field, table=table, conditions=conditions, bom=bom), as_dict=1)
 
 def get_manufacturer_records():
-	details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "parent"])
+	details = frappe.get_all('Item Manufacturer', fields = ["manufacturer", "manufacturer_part_no", "item_code"])
 	manufacture_details = frappe._dict()
 	for detail in details:
-		dic = manufacture_details.setdefault(detail.get('parent'), {})
+		dic = manufacture_details.setdefault(detail.get('item_code'), {})
 		dic.setdefault('manufacturer', []).append(detail.get('manufacturer'))
 		dic.setdefault('manufacturer_part', []).append(detail.get('manufacturer_part_no'))
 
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
index 97e7e0a..72eed5e 100644
--- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.js
@@ -17,14 +17,12 @@
 			fieldname:"from_date",
 			fieldtype: "Datetime",
 			default: frappe.datetime.convert_to_system_tz(frappe.datetime.add_months(frappe.datetime.now_datetime(), -1)),
-			reqd: 1
 		},
 		{
 			label: __("To Date"),
 			fieldname:"to_date",
 			fieldtype: "Datetime",
 			default: frappe.datetime.now_datetime(),
-			reqd: 1,
 		},
 		{
 			label: __("Job Card"),
diff --git a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
index 7741823..88b2117 100644
--- a/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
+++ b/erpnext/manufacturing/report/cost_of_poor_quality_report/cost_of_poor_quality_report.py
@@ -3,46 +3,65 @@
 
 import frappe
 from frappe import _
-from frappe.utils import flt
 
 
 def execute(filters=None):
-	columns, data = [], []
+	return get_columns(filters), get_data(filters)
 
-	columns = get_columns(filters)
-	data = get_data(filters)
-
-	return columns, data
 
 def get_data(report_filters):
 	data = []
 	operations = frappe.get_all("Operation", filters = {"is_corrective_operation": 1})
 	if operations:
-		operations = [d.name for d in operations]
-		fields = ["production_item as item_code", "item_name", "work_order", "operation",
-			"workstation", "total_time_in_mins", "name", "hour_rate", "serial_no", "batch_no"]
+		if report_filters.get('operation'):
+			operations = [report_filters.get('operation')]
+		else:
+			operations = [d.name for d in operations]
 
-		filters = get_filters(report_filters, operations)
+		job_card = frappe.qb.DocType("Job Card")
 
-		job_cards = frappe.get_all("Job Card", fields = fields,
-			filters = filters)
+		operating_cost = ((job_card.hour_rate) * (job_card.total_time_in_mins) / 60.0).as_('operating_cost')
+		item_code = (job_card.production_item).as_('item_code')
 
-		for row in job_cards:
-			row.operating_cost = flt(row.hour_rate) * (flt(row.total_time_in_mins) / 60.0)
-			data.append(row)
+		query = (frappe.qb
+					.from_(job_card)
+					.select(job_card.name, job_card.work_order, item_code, job_card.item_name,
+						job_card.operation, job_card.serial_no, job_card.batch_no,
+						job_card.workstation, job_card.total_time_in_mins, job_card.hour_rate,
+						operating_cost)
+					.where(
+						(job_card.docstatus == 1)
+						& (job_card.is_corrective_job_card == 1))
+					.groupby(job_card.name)
+				)
 
+		query = append_filters(query, report_filters, operations, job_card)
+		data = query.run(as_dict=True)
 	return data
 
-def get_filters(report_filters, operations):
-	filters = {"docstatus": 1, "operation": ("in", operations), "is_corrective_job_card": 1}
-	for field in ["name", "work_order", "operation", "workstation", "company", "serial_no", "batch_no", "production_item"]:
-		if report_filters.get(field):
-			if field != 'serial_no':
-				filters[field] = report_filters.get(field)
-			else:
-				filters[field] = ('like', '% {} %'.format(report_filters.get(field)))
+def append_filters(query, report_filters, operations, job_card):
+	"""Append optional filters to query builder. """
 
-	return filters
+	for field in ("name", "work_order", "operation", "workstation",
+			"company", "serial_no", "batch_no", "production_item"):
+		if report_filters.get(field):
+			if field == 'serial_no':
+				query = query.where(job_card[field].like('%{}%'.format(report_filters.get(field))))
+			elif field == 'operation':
+				query = query.where(job_card[field].isin(operations))
+			else:
+				query = query.where(job_card[field] == report_filters.get(field))
+
+	if report_filters.get('from_date') or report_filters.get('to_date'):
+		job_card_time_log = frappe.qb.DocType("Job Card Time Log")
+
+		query = query.join(job_card_time_log).on(job_card.name == job_card_time_log.parent)
+		if report_filters.get('from_date'):
+			query = query.where(job_card_time_log.from_time >= report_filters.get('from_date'))
+		if report_filters.get('to_date'):
+			query = query.where(job_card_time_log.to_time <= report_filters.get('to_date'))
+
+	return query
 
 def get_columns(filters):
 	return [
diff --git a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
index 55b1a3f..aaa2314 100644
--- a/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
+++ b/erpnext/manufacturing/report/production_plan_summary/production_plan_summary.py
@@ -48,7 +48,7 @@
 			"qty": row.planned_qty,
 			"document_type": "Work Order",
 			"document_name": work_order or "",
-			"bom_level": frappe.get_cached_value("BOM", row.bom_no, "bom_level"),
+			"bom_level": 0,
 			"produced_qty": order_details.get((work_order, row.item_code), {}).get("produced_qty", 0),
 			"pending_qty": flt(row.planned_qty) - flt(order_details.get((work_order, row.item_code), {}).get("produced_qty", 0))
 		})
diff --git a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
index 8368db6..e1e7225 100644
--- a/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
+++ b/erpnext/manufacturing/report/production_planning_report/production_planning_report.py
@@ -172,10 +172,15 @@
 
 		self.purchase_details = {}
 
-		for d in frappe.get_all("Purchase Order Item",
+		purchased_items = frappe.get_all("Purchase Order Item",
 			fields=["item_code", "min(schedule_date) as arrival_date", "qty as arrival_qty", "warehouse"],
-			filters = {"item_code": ("in", self.item_codes), "warehouse": ("in", self.warehouses)},
-			group_by = "item_code, warehouse"):
+			filters={
+				"item_code": ("in", self.item_codes),
+				"warehouse": ("in", self.warehouses),
+				"docstatus": 1,
+			},
+			group_by = "item_code, warehouse")
+		for d in purchased_items:
 			key = (d.item_code, d.warehouse)
 			if key not in self.purchase_details:
 				self.purchase_details.setdefault(key, d)
diff --git a/erpnext/manufacturing/report/test_reports.py b/erpnext/manufacturing/report/test_reports.py
index 1de4726..e436fdc 100644
--- a/erpnext/manufacturing/report/test_reports.py
+++ b/erpnext/manufacturing/report/test_reports.py
@@ -18,7 +18,7 @@
 	("BOM Operations Time", {}),
 	("BOM Stock Calculated", {"bom": frappe.get_last_doc("BOM").name, "qty_to_make": 2}),
 	("BOM Stock Report", {"bom": frappe.get_last_doc("BOM").name, "qty_to_produce": 2}),
-	("Cost of Poor Quality Report", {}),
+	("Cost of Poor Quality Report", {"item": "_Test Item", "serial_no": "00"}),
 	("Downtime Analysis", {}),
 	(
 		"Exponential Smoothing Forecasting",
@@ -55,10 +55,11 @@
 	def test_execute_all_manufacturing_reports(self):
 		"""Test that all script report in manufacturing modules are executable with supported filters"""
 		for report, filter in REPORT_FILTER_TEST_CASES:
-			execute_script_report(
-				report_name=report,
-				module="Manufacturing",
-				filters=filter,
-				default_filters=DEFAULT_FILTERS,
-				optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
-			)
+			with self.subTest(report=report):
+				execute_script_report(
+					report_name=report,
+					module="Manufacturing",
+					filters=filter,
+					default_filters=DEFAULT_FILTERS,
+					optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
+				)
diff --git a/erpnext/modules.txt b/erpnext/modules.txt
index ae0bb2d..c6b3159 100644
--- a/erpnext/modules.txt
+++ b/erpnext/modules.txt
@@ -9,18 +9,16 @@
 Stock
 Support
 Utilities
-Shopping Cart
 Assets
 Portal
 Maintenance
 Education
 Regional
-Restaurant
 ERPNext Integrations
-Non Profit
-Hotels
 Quality Management
 Communication
 Loan Management
 Payroll
-Telephony
\ No newline at end of file
+Telephony
+Bulk Transaction
+E-commerce
diff --git a/erpnext/non_profit/__init__.py b/erpnext/non_profit/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/__init__.py b/erpnext/non_profit/doctype/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/certification_application/__init__.py b/erpnext/non_profit/doctype/certification_application/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/certification_application/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/certification_application/certification_application.js b/erpnext/non_profit/doctype/certification_application/certification_application.js
deleted file mode 100644
index 1e6a9a4..0000000
--- a/erpnext/non_profit/doctype/certification_application/certification_application.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Certification Application', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/non_profit/doctype/certification_application/certification_application.json b/erpnext/non_profit/doctype/certification_application/certification_application.json
deleted file mode 100644
index f562fa6..0000000
--- a/erpnext/non_profit/doctype/certification_application/certification_application.json
+++ /dev/null
@@ -1,323 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "NPO-CAPP-.YYYY.-.#####", 
- "beta": 0, 
- "creation": "2018-06-08 16:12:42.091729", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "name_of_applicant", 
-   "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": "Name of Applicant", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "email", 
-   "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": "Email", 
-   "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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_1", 
-   "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_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "certification_status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Certification Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Yet to appear\nCertified\nNot Certified", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "payment_details", 
-   "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": "Payment Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "paid", 
-   "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": "Paid", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "currency", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Currency", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "USD\nINR", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "amount", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Amount", 
-   "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": 0, 
- "max_attachments": 0, 
- "modified": "2018-11-04 03:36:35.337403", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Certification Application", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/certification_application/certification_application.py b/erpnext/non_profit/doctype/certification_application/certification_application.py
deleted file mode 100644
index cbbe191..0000000
--- a/erpnext/non_profit/doctype/certification_application/certification_application.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class CertificationApplication(Document):
-	pass
diff --git a/erpnext/non_profit/doctype/certification_application/test_certification_application.py b/erpnext/non_profit/doctype/certification_application/test_certification_application.py
deleted file mode 100644
index 8687b4d..0000000
--- a/erpnext/non_profit/doctype/certification_application/test_certification_application.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestCertificationApplication(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/certified_consultant/__init__.py b/erpnext/non_profit/doctype/certified_consultant/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/certified_consultant/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.js b/erpnext/non_profit/doctype/certified_consultant/certified_consultant.js
deleted file mode 100644
index cd004c3..0000000
--- a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Certified Consultant', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.json b/erpnext/non_profit/doctype/certified_consultant/certified_consultant.json
deleted file mode 100644
index d77f1b2..0000000
--- a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.json
+++ /dev/null
@@ -1,724 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "NPO-CONS-.YYYY.-.#####", 
- "beta": 0, 
- "creation": "2018-06-13 17:27:19.838334", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "name_of_consultant", 
-   "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": "Name of Consultant", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "country", 
-   "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": "Country", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Country", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "email", 
-   "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": "Email", 
-   "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_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "phone", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Phone", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "website_url", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Website", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "address", 
-   "fieldtype": "Small Text", 
-   "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": "Address", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_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, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "image", 
-   "fieldtype": "Attach Image", 
-   "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": "Image", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "certification_application", 
-   "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": "Certification Application", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Certification Application", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_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": "Certification Validity", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "from_date", 
-   "fieldtype": "Date", 
-   "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": "From", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_beak2", 
-   "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_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": 0, 
-   "in_standard_filter": 0, 
-   "label": "To", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_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": "", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "introduction", 
-   "fieldtype": "Small Text", 
-   "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": "Introduction", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "details", 
-   "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": "Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break3", 
-   "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_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "discuss_id", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Discuss ID", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "github_id", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "GitHub ID", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "show_in_website", 
-   "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": "Show in Website", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-11-04 03:36:47.386618", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Certified Consultant", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Non Profit Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py b/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py
deleted file mode 100644
index 47361cc..0000000
--- a/erpnext/non_profit/doctype/certified_consultant/certified_consultant.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class CertifiedConsultant(Document):
-	pass
diff --git a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py b/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py
deleted file mode 100644
index d10353c..0000000
--- a/erpnext/non_profit/doctype/certified_consultant/test_certified_consultant.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestCertifiedConsultant(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/chapter/__init__.py b/erpnext/non_profit/doctype/chapter/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/chapter/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/chapter/chapter.js b/erpnext/non_profit/doctype/chapter/chapter.js
deleted file mode 100644
index c8b6d4a..0000000
--- a/erpnext/non_profit/doctype/chapter/chapter.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Chapter', {
-	refresh: function() {
-
-	}
-});
diff --git a/erpnext/non_profit/doctype/chapter/chapter.json b/erpnext/non_profit/doctype/chapter/chapter.json
deleted file mode 100644
index 86cba9a..0000000
--- a/erpnext/non_profit/doctype/chapter/chapter.json
+++ /dev/null
@@ -1,397 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 1, 
- "allow_import": 0, 
- "allow_rename": 1, 
- "autoname": "prompt", 
- "beta": 0, 
- "creation": "2017-09-14 13:36:03.904702", 
- "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": "chapter_head", 
-   "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": "Chapter Head", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Member", 
-   "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
-  }, 
-  {
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "region", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Region", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_5", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "introduction", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Introduction", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "meetup_embed_html", 
-   "fieldtype": "Code", 
-   "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": "Meetup Embed HTML", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "address", 
-   "fieldtype": "Text", 
-   "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": "Address", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "chapters/chapter_name\nleave blank automatically set after saving chapter.", 
-   "fieldname": "route", 
-   "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": "Route", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "published", 
-   "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": "Published", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 1, 
-   "columns": 0, 
-   "fieldname": "chapter_members", 
-   "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": "Chapter Members", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "members", 
-   "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": "Members", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Chapter Member", 
-   "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
-  }
- ], 
- "has_web_view": 1, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_published_field": "published", 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-14 12:59:31.424240", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Chapter", 
- "name_case": "Title Case", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Non Profit Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "route": "chapters", 
- "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/non_profit/doctype/chapter/chapter.py b/erpnext/non_profit/doctype/chapter/chapter.py
deleted file mode 100644
index c01b1ef..0000000
--- a/erpnext/non_profit/doctype/chapter/chapter.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.website.website_generator import WebsiteGenerator
-
-
-class Chapter(WebsiteGenerator):
-	_website = frappe._dict(
-		condition_field = "published",
-	)
-
-	def get_context(self, context):
-		context.no_cache = True
-		context.show_sidebar = True
-		context.parents = [dict(label='View All Chapters',
-			route='chapters', title='View Chapters')]
-
-	def validate(self):
-		if not self.route:		#pylint: disable=E0203
-			self.route = 'chapters/' + self.scrub(self.name)
-
-	def enable(self):
-		chapter = frappe.get_doc('Chapter', frappe.form_dict.name)
-		chapter.append('members', dict(enable=self.value))
-		chapter.save(ignore_permissions=1)
-		frappe.db.commit()
-
-
-def get_list_context(context):
-	context.allow_guest = True
-	context.no_cache = True
-	context.show_sidebar = True
-	context.title = 'All Chapters'
-	context.no_breadcrumbs = True
-	context.order_by = 'creation desc'
-
-
-@frappe.whitelist()
-def leave(title, user_id, leave_reason):
-	chapter = frappe.get_doc("Chapter", title)
-	for member in chapter.members:
-		if member.user == user_id:
-			member.enabled = 0
-			member.leave_reason = leave_reason
-	chapter.save(ignore_permissions=1)
-	frappe.db.commit()
-	return "Thank you for Feedback"
diff --git a/erpnext/non_profit/doctype/chapter/templates/chapter.html b/erpnext/non_profit/doctype/chapter/templates/chapter.html
deleted file mode 100644
index 321828f..0000000
--- a/erpnext/non_profit/doctype/chapter/templates/chapter.html
+++ /dev/null
@@ -1,79 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block page_content %}
-<h1>{{ title }}</h1>
-<p>{{ introduction }}</p>
-{% if meetup_embed_html %}
-	{{ meetup_embed_html }}
-{% endif %}
-<h3>Member Details</h3>
-
-{% if members  %}
-	<table class="table" style="max-width: 600px;">
-		{% set index = [1] %}
-		{% for user in members %}
-			{% if user.enabled == 1 %}
-				<tr>
-					<td>
-						<div style="margin-bottom: 30px; max-width: 600px" class="with-border">
-						<div class="row">
-							<div class="col-lg-6 col-md-6 col-sm-6">
-             <div class="pull-left">
-								<b>{{ index|length }}. {{ frappe.db.get_value('User', user.user, 'full_name') }}</b></div>
-							</div>
-								<div class="pull-right">
-								{% if user.website_url %}
-									<a href="{{ user.website_url }}">{{ user.website_url | truncate (50) or '' }}</a>
-								{% endif %}
-							</div>
-							<div class="clearfix"></div>
-							</div>
-							<br><br>
-							<div class="col-lg-12">
-							{% if user.introduction %}
-								{{ user.introduction }}
-							{% endif %}
-							</div>
-						</div>
-					</div>
-					</td>
-				</tr>
-				{% set __ = index.append(1) %}
-			{% endif %}
-		{% endfor %}
-	</table>
-{% else %}
-	<p>No member yet.</p>
-{% endif %}
-
-<h3>Chapter Head</h3>
-<div style="margin-bottom: 30px; max-width: 600px" class="with-border">
-
-<table class="table table-bordered small" style="max-width: 600px;">
-	{% set doc = frappe.get_doc('Member',chapter_head) %}
-	<tr>
-		<td style='width: 15%'>Name</td>
-		<td><b>{{ doc.member_name }}<b></td>
-	</tr>
-	<tr>
-		<td>Email</td>
-		<td>{{ frappe.db.get_value('User', doc.email, 'email') or '' }}</td>
-	</tr>
-	<tr>
-		<td>Phone</td>
-		<td>{{ frappe.db.get_value('User', doc.email, 'phone') or '' }}</td>
-	</tr>
-</table>
-</div>
-
-{% if address  %}
-<h3>Address</h3>
-<div style="margin-bottom: 30px; max-width: 600px" class="with-border">
-<p>{{ address or ''}}</p>
-</div>
-{% endif %}
-
-<p style="margin: 20px 0 30px;"><a href="/non_profit/join-chapter?name={{ name }}" class='btn btn-primary'>Join this Chapter</a></p>
-<p style="margin: 20px 0 30px;"><a href="/non_profit/leave-chapter?name={{ name }}" class=''>Leave this Chapter</a></p>
-
-{% endblock %}
diff --git a/erpnext/non_profit/doctype/chapter/templates/chapter_row.html b/erpnext/non_profit/doctype/chapter/templates/chapter_row.html
deleted file mode 100644
index cad34fa..0000000
--- a/erpnext/non_profit/doctype/chapter/templates/chapter_row.html
+++ /dev/null
@@ -1,25 +0,0 @@
-{% if doc.published %}
-	<div style="margin-bottom: 30px; max-width: 600px" class="with-border clickable">
-		<a href={{ doc.route }}>
-			<h3>{{ doc.name }}</h3>
-			<p>
-				<span class="label"> Chapter Head : {{ frappe.db.get_value('User', chapter_head, 'full_name') }} </span>
-				<span class="label">
-					{% if members %}
-						{% set index = [] %}
-						{% for user in members %}
-							{% if user.enabled == 1 %}
-								{% set __ = index.append(1) %}
-							{% endif %}
-						{% endfor %}
-						Members: {{ index|length }}
-					{% else %}
-						Members: 0
-					{% endif %}
-					</span>
-				<!-- Assignment of value to global variable not working in jinja -->
-			</p>
-			<p>{{ html2text(doc.introduction) | truncate (200) }}</p>
-		</a>
-	</div>
-{% endif %}
diff --git a/erpnext/non_profit/doctype/chapter/test_chapter.py b/erpnext/non_profit/doctype/chapter/test_chapter.py
deleted file mode 100644
index 98601ef..0000000
--- a/erpnext/non_profit/doctype/chapter/test_chapter.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestChapter(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/chapter_member/__init__.py b/erpnext/non_profit/doctype/chapter_member/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/chapter_member/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/chapter_member/chapter_member.json b/erpnext/non_profit/doctype/chapter_member/chapter_member.json
deleted file mode 100644
index 478bfd9..0000000
--- a/erpnext/non_profit/doctype/chapter_member/chapter_member.json
+++ /dev/null
@@ -1,199 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-09-14 13:38:04.296375", 
- "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": "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": 1, 
-   "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": "introduction", 
-   "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": "Introduction", 
-   "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": "website_url", 
-   "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": "Website URL", 
-   "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": 2, 
-   "default": "1", 
-   "fieldname": "enabled", 
-   "fieldtype": "Check", 
-   "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": "Enabled", 
-   "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": "leave_reason", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Leave Reason", 
-   "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-03-07 05:36:51.664816", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Chapter Member", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "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/non_profit/doctype/chapter_member/chapter_member.py b/erpnext/non_profit/doctype/chapter_member/chapter_member.py
deleted file mode 100644
index 80c0446..0000000
--- a/erpnext/non_profit/doctype/chapter_member/chapter_member.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class ChapterMember(Document):
-	pass
diff --git a/erpnext/non_profit/doctype/donation/__init__.py b/erpnext/non_profit/doctype/donation/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/donation/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/donation/donation.js b/erpnext/non_profit/doctype/donation/donation.js
deleted file mode 100644
index 10e8220..0000000
--- a/erpnext/non_profit/doctype/donation/donation.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Donation', {
-	refresh: function(frm) {
-		if (frm.doc.docstatus === 1 && !frm.doc.paid) {
-			frm.add_custom_button(__('Create Payment Entry'), function() {
-				frm.events.make_payment_entry(frm);
-			});
-		}
-	},
-
-	make_payment_entry: function(frm) {
-		return frappe.call({
-			method: 'erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry',
-			args: {
-				'dt': frm.doc.doctype,
-				'dn': frm.doc.name
-			},
-			callback: function(r) {
-				var doc = frappe.model.sync(r.message);
-				frappe.set_route('Form', doc[0].doctype, doc[0].name);
-			}
-		});
-	},
-});
diff --git a/erpnext/non_profit/doctype/donation/donation.json b/erpnext/non_profit/doctype/donation/donation.json
deleted file mode 100644
index 6759569..0000000
--- a/erpnext/non_profit/doctype/donation/donation.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "actions": [],
- "autoname": "naming_series:",
- "creation": "2021-02-17 10:28:52.645731",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "naming_series",
-  "donor",
-  "donor_name",
-  "email",
-  "column_break_4",
-  "company",
-  "date",
-  "payment_details_section",
-  "paid",
-  "amount",
-  "mode_of_payment",
-  "razorpay_payment_id",
-  "amended_from"
- ],
- "fields": [
-  {
-   "fieldname": "donor",
-   "fieldtype": "Link",
-   "label": "Donor",
-   "options": "Donor",
-   "reqd": 1
-  },
-  {
-   "fetch_from": "donor.donor_name",
-   "fieldname": "donor_name",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Donor Name",
-   "read_only": 1
-  },
-  {
-   "fetch_from": "donor.email",
-   "fieldname": "email",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Email",
-   "read_only": 1
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "date",
-   "fieldtype": "Date",
-   "label": "Date",
-   "reqd": 1
-  },
-  {
-   "fieldname": "payment_details_section",
-   "fieldtype": "Section Break",
-   "label": "Payment Details"
-  },
-  {
-   "fieldname": "amount",
-   "fieldtype": "Currency",
-   "label": "Amount",
-   "reqd": 1
-  },
-  {
-   "fieldname": "mode_of_payment",
-   "fieldtype": "Link",
-   "label": "Mode of Payment",
-   "options": "Mode of Payment"
-  },
-  {
-   "fieldname": "razorpay_payment_id",
-   "fieldtype": "Data",
-   "label": "Razorpay Payment ID",
-   "read_only": 1
-  },
-  {
-   "fieldname": "naming_series",
-   "fieldtype": "Select",
-   "label": "Naming Series",
-   "options": "NPO-DTN-.YYYY.-"
-  },
-  {
-   "default": "0",
-   "fieldname": "paid",
-   "fieldtype": "Check",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Paid"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  },
-  {
-   "fieldname": "amended_from",
-   "fieldtype": "Link",
-   "label": "Amended From",
-   "no_copy": 1,
-   "options": "Donation",
-   "print_hide": 1,
-   "read_only": 1
-  }
- ],
- "index_web_pages_for_search": 1,
- "is_submittable": 1,
- "links": [],
- "modified": "2021-03-11 10:53:11.269005",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Donation",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "System Manager",
-   "select": 1,
-   "share": 1,
-   "submit": 1,
-   "write": 1
-  },
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Manager",
-   "select": 1,
-   "share": 1,
-   "submit": 1,
-   "write": 1
-  }
- ],
- "search_fields": "donor_name, email",
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "donor_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py
deleted file mode 100644
index 54bc94b..0000000
--- a/erpnext/non_profit/doctype/donation/donation.py
+++ /dev/null
@@ -1,220 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import json
-
-import frappe
-from frappe import _
-from frappe.email import sendmail_to_system_managers
-from frappe.model.document import Document
-from frappe.utils import flt, get_link_to_form, getdate
-
-from erpnext.non_profit.doctype.membership.membership import verify_signature
-
-
-class Donation(Document):
-	def validate(self):
-		if not self.donor or not frappe.db.exists('Donor', self.donor):
-			# for web forms
-			user_type = frappe.db.get_value('User', frappe.session.user, 'user_type')
-			if user_type == 'Website User':
-				self.create_donor_for_website_user()
-			else:
-				frappe.throw(_('Please select a Member'))
-
-	def create_donor_for_website_user(self):
-		donor_name = frappe.get_value('Donor', dict(email=frappe.session.user))
-
-		if not donor_name:
-			user = frappe.get_doc('User', frappe.session.user)
-			donor = frappe.get_doc(dict(
-				doctype='Donor',
-				donor_type=self.get('donor_type'),
-				email=frappe.session.user,
-				member_name=user.get_fullname()
-			)).insert(ignore_permissions=True)
-			donor_name = donor.name
-
-		if self.get('__islocal'):
-			self.donor = donor_name
-
-	def on_payment_authorized(self, *args, **kwargs):
-		self.load_from_db()
-		self.create_payment_entry()
-
-	def create_payment_entry(self, date=None):
-		settings = frappe.get_doc('Non Profit Settings')
-		if not settings.automate_donation_payment_entries:
-			return
-
-		if not settings.donation_payment_account:
-			frappe.throw(_('You need to set <b>Payment Account</b> for Donation in {0}').format(
-				get_link_to_form('Non Profit Settings', 'Non Profit Settings')))
-
-		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-
-		frappe.flags.ignore_account_permission = True
-		pe = get_payment_entry(dt=self.doctype, dn=self.name)
-		frappe.flags.ignore_account_permission = False
-		pe.paid_from = settings.donation_debit_account
-		pe.paid_to = settings.donation_payment_account
-		pe.posting_date = date or getdate()
-		pe.reference_no = self.name
-		pe.reference_date = date or getdate()
-		pe.flags.ignore_mandatory = True
-		pe.insert()
-		pe.submit()
-
-
-@frappe.whitelist(allow_guest=True)
-def capture_razorpay_donations(*args, **kwargs):
-	"""
-		Creates Donation from Razorpay Webhook Request Data on payment.captured event
-		Creates Donor from email if not found
-	"""
-	data = frappe.request.get_data(as_text=True)
-
-	try:
-		verify_signature(data, endpoint='Donation')
-	except Exception as e:
-		log = frappe.log_error(e, 'Donation Webhook Verification Error')
-		notify_failure(log)
-		return { 'status': 'Failed', 'reason': e }
-
-	if isinstance(data, str):
-		data = json.loads(data)
-	data = frappe._dict(data)
-
-	payment = data.payload.get('payment', {}).get('entity', {})
-	payment = frappe._dict(payment)
-
-	try:
-		if not data.event == 'payment.captured':
-			return
-
-		# to avoid capturing subscription payments as donations
-		if payment.description and 'subscription' in str(payment.description).lower():
-			return
-
-		donor = get_donor(payment.email)
-		if not donor:
-			donor = create_donor(payment)
-
-		donation = create_donation(donor, payment)
-		donation.run_method('create_payment_entry')
-
-	except Exception as e:
-		message = '{0}\n\n{1}\n\n{2}: {3}'.format(e, frappe.get_traceback(), _('Payment ID'), payment.id)
-		log = frappe.log_error(message, _('Error creating donation entry for {0}').format(donor.name))
-		notify_failure(log)
-		return { 'status': 'Failed', 'reason': e }
-
-	return { 'status': 'Success' }
-
-
-def create_donation(donor, payment):
-	if not frappe.db.exists('Mode of Payment', payment.method):
-		create_mode_of_payment(payment.method)
-
-	company = get_company_for_donations()
-	donation = frappe.get_doc({
-		'doctype': 'Donation',
-		'company': company,
-		'donor': donor.name,
-		'donor_name': donor.donor_name,
-		'email': donor.email,
-		'date': getdate(),
-		'amount': flt(payment.amount) / 100, # Convert to rupees from paise
-		'mode_of_payment': payment.method,
-		'razorpay_payment_id': payment.id
-	}).insert(ignore_mandatory=True)
-
-	donation.submit()
-	return donation
-
-
-def get_donor(email):
-	donors = frappe.get_all('Donor',
-		filters={'email': email},
-		order_by='creation desc')
-
-	try:
-		return frappe.get_doc('Donor', donors[0]['name'])
-	except Exception:
-		return None
-
-
-@frappe.whitelist()
-def create_donor(payment):
-	donor_details = frappe._dict(payment)
-	donor_type = frappe.db.get_single_value('Non Profit Settings', 'default_donor_type')
-
-	donor = frappe.new_doc('Donor')
-	donor.update({
-		'donor_name': donor_details.email,
-		'donor_type': donor_type,
-		'email': donor_details.email,
-		'contact': donor_details.contact
-	})
-
-	if donor_details.get('notes'):
-		donor = get_additional_notes(donor, donor_details)
-
-	donor.insert(ignore_mandatory=True)
-	return donor
-
-
-def get_company_for_donations():
-	company = frappe.db.get_single_value('Non Profit Settings', 'donation_company')
-	if not company:
-		from erpnext.non_profit.utils import get_company
-		company = get_company()
-	return company
-
-
-def get_additional_notes(donor, donor_details):
-	if type(donor_details.notes) == dict:
-		for k, v in donor_details.notes.items():
-			notes = '\n'.join('{}: {}'.format(k, v))
-
-			# extract donor name from notes
-			if 'name' in k.lower():
-				donor.update({
-					'donor_name': donor_details.notes.get(k)
-				})
-
-			# extract pan from notes
-			if 'pan' in k.lower():
-				donor.update({
-					'pan_number': donor_details.notes.get(k)
-				})
-
-		donor.add_comment('Comment', notes)
-
-	elif type(donor_details.notes) == str:
-		donor.add_comment('Comment', donor_details.notes)
-
-	return donor
-
-
-def create_mode_of_payment(method):
-	frappe.get_doc({
-		'doctype': 'Mode of Payment',
-		'mode_of_payment': method
-	}).insert(ignore_mandatory=True)
-
-
-def notify_failure(log):
-	try:
-		content = '''
-			Dear System Manager,
-			Razorpay webhook for creating donation failed due to some reason.
-			Please check the error log linked below
-			Error Log: {0}
-			Regards, Administrator
-		'''.format(get_link_to_form('Error Log', log.name))
-
-		sendmail_to_system_managers(_('[Important] [ERPNext] Razorpay donation webhook failed, please check.'), content)
-	except Exception:
-		pass
diff --git a/erpnext/non_profit/doctype/donation/donation_dashboard.py b/erpnext/non_profit/doctype/donation/donation_dashboard.py
deleted file mode 100644
index 492ad62..0000000
--- a/erpnext/non_profit/doctype/donation/donation_dashboard.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from frappe import _
-
-
-def get_data():
-	return {
-		'fieldname': 'donation',
-		'non_standard_fieldnames': {
-			'Payment Entry': 'reference_name'
-		},
-		'transactions': [
-			{
-				'label': _('Payment'),
-				'items': ['Payment Entry']
-			}
-		]
-	}
diff --git a/erpnext/non_profit/doctype/donation/test_donation.py b/erpnext/non_profit/doctype/donation/test_donation.py
deleted file mode 100644
index 5fa731a..0000000
--- a/erpnext/non_profit/doctype/donation/test_donation.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-
-from erpnext.non_profit.doctype.donation.donation import create_donation
-
-
-class TestDonation(unittest.TestCase):
-	def setUp(self):
-		create_donor_type()
-		settings = frappe.get_doc('Non Profit Settings')
-		settings.company = '_Test Company'
-		settings.donation_company = '_Test Company'
-		settings.default_donor_type = '_Test Donor'
-		settings.automate_donation_payment_entries = 1
-		settings.donation_debit_account = 'Debtors - _TC'
-		settings.donation_payment_account =  'Cash - _TC'
-		settings.creation_user = 'Administrator'
-		settings.flags.ignore_permissions = True
-		settings.save()
-
-	def test_payment_entry_for_donations(self):
-		donor = create_donor()
-		create_mode_of_payment()
-		payment = frappe._dict({
-			'amount': 100,
-			'method': 'Debit Card',
-			'id': 'pay_MeXAmsgeKOhq7O'
-		})
-		donation = create_donation(donor, payment)
-
-		self.assertTrue(donation.name)
-
-		# Naive test to check if at all payment entry is generated
-		# This method is actually triggered from Payment Gateway
-		# In any case if details were missing, this would throw an error
-		donation.on_payment_authorized()
-		donation.reload()
-
-		self.assertEqual(donation.paid, 1)
-		self.assertTrue(frappe.db.exists('Payment Entry', {'reference_no': donation.name}))
-
-
-def create_donor_type():
-	if not frappe.db.exists('Donor Type', '_Test Donor'):
-		frappe.get_doc({
-			'doctype': 'Donor Type',
-			'donor_type': '_Test Donor'
-		}).insert()
-
-
-def create_donor():
-	donor = frappe.db.exists('Donor', 'donor@test.com')
-	if donor:
-		return frappe.get_doc('Donor', 'donor@test.com')
-	else:
-		return frappe.get_doc({
-			'doctype': 'Donor',
-			'donor_name': '_Test Donor',
-			'donor_type': '_Test Donor',
-			'email': 'donor@test.com'
-		}).insert()
-
-
-def create_mode_of_payment():
-	if not frappe.db.exists('Mode of Payment', 'Debit Card'):
-		frappe.get_doc({
-			'doctype': 'Mode of Payment',
-			'mode_of_payment': 'Debit Card',
-			'accounts': [{
-				'company': '_Test Company',
-				'default_account': 'Cash - _TC'
-			}]
-		}).insert()
diff --git a/erpnext/non_profit/doctype/donor/__init__.py b/erpnext/non_profit/doctype/donor/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/donor/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/donor/donor.js b/erpnext/non_profit/doctype/donor/donor.js
deleted file mode 100644
index 090d5af..0000000
--- a/erpnext/non_profit/doctype/donor/donor.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Donor', {
-	refresh: function(frm) {
-		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Donor'};
-
-		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
-
-		if(!frm.doc.__islocal) {
-			frappe.contacts.render_address_and_contact(frm);
-		} else {
-			frappe.contacts.clear_address_and_contact(frm);
-		}
-
-	}
-});
diff --git a/erpnext/non_profit/doctype/donor/donor.json b/erpnext/non_profit/doctype/donor/donor.json
deleted file mode 100644
index 72f24ef..0000000
--- a/erpnext/non_profit/doctype/donor/donor.json
+++ /dev/null
@@ -1,110 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "autoname": "field:email",
- "creation": "2017-09-19 16:20:27.510196",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "donor_name",
-  "column_break_5",
-  "donor_type",
-  "email",
-  "image",
-  "address_contacts",
-  "address_html",
-  "column_break_9",
-  "contact_html"
- ],
- "fields": [
-  {
-   "fieldname": "donor_name",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Donor Name",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_5",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "donor_type",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Donor Type",
-   "options": "Donor Type",
-   "reqd": 1
-  },
-  {
-   "fieldname": "email",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Email",
-   "reqd": 1,
-   "unique": 1
-  },
-  {
-   "fieldname": "image",
-   "fieldtype": "Attach Image",
-   "hidden": 1,
-   "label": "Image",
-   "no_copy": 1,
-   "print_hide": 1
-  },
-  {
-   "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"
-  },
-  {
-   "fieldname": "column_break_9",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "contact_html",
-   "fieldtype": "HTML",
-   "label": "Contact HTML"
-  }
- ],
- "image_field": "image",
- "links": [
-  {
-   "link_doctype": "Donation",
-   "link_fieldname": "donor"
-  }
- ],
- "modified": "2021-02-17 16:36:33.470731",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Donor",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "quick_entry": 1,
- "restrict_to_domain": "Non Profit",
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "donor_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/donor/donor.py b/erpnext/non_profit/doctype/donor/donor.py
deleted file mode 100644
index 058321b..0000000
--- a/erpnext/non_profit/doctype/donor/donor.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.contacts.address_and_contact import load_address_and_contact
-from frappe.model.document import Document
-
-
-class Donor(Document):
-	def onload(self):
-		"""Load address and contacts in `__onload`"""
-		load_address_and_contact(self)
-
-	def validate(self):
-		from frappe.utils import validate_email_address
-		if self.email:
-			validate_email_address(self.email.strip(), True)
diff --git a/erpnext/non_profit/doctype/donor/donor_list.js b/erpnext/non_profit/doctype/donor/donor_list.js
deleted file mode 100644
index 31d4d29..0000000
--- a/erpnext/non_profit/doctype/donor/donor_list.js
+++ /dev/null
@@ -1,3 +0,0 @@
-frappe.listview_settings['Donor'] = {
-	add_fields: ["donor_name", "donor_type", "image"],
-};
diff --git a/erpnext/non_profit/doctype/donor/test_donor.py b/erpnext/non_profit/doctype/donor/test_donor.py
deleted file mode 100644
index fe591c8..0000000
--- a/erpnext/non_profit/doctype/donor/test_donor.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestDonor(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/donor_type/__init__.py b/erpnext/non_profit/doctype/donor_type/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/donor_type/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/donor_type/donor_type.js b/erpnext/non_profit/doctype/donor_type/donor_type.js
deleted file mode 100644
index 7b1fd4f..0000000
--- a/erpnext/non_profit/doctype/donor_type/donor_type.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Donor Type', {
-	refresh: function() {
-
-	}
-});
diff --git a/erpnext/non_profit/doctype/donor_type/donor_type.json b/erpnext/non_profit/doctype/donor_type/donor_type.json
deleted file mode 100644
index 07118fd..0000000
--- a/erpnext/non_profit/doctype/donor_type/donor_type.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "field:donor_type", 
- "beta": 0, 
- "creation": "2017-09-19 16:19:16.639635", 
- "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": "donor_type", 
-   "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": 1, 
-   "label": "Donor Type", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-05 07:04:36.757595", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Donor Type", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Non Profit Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "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/non_profit/doctype/donor_type/donor_type.py b/erpnext/non_profit/doctype/donor_type/donor_type.py
deleted file mode 100644
index 17dca89..0000000
--- a/erpnext/non_profit/doctype/donor_type/donor_type.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class DonorType(Document):
-	pass
diff --git a/erpnext/non_profit/doctype/donor_type/test_donor_type.py b/erpnext/non_profit/doctype/donor_type/test_donor_type.py
deleted file mode 100644
index d433733..0000000
--- a/erpnext/non_profit/doctype/donor_type/test_donor_type.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestDonorType(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/grant_application/__init__.py b/erpnext/non_profit/doctype/grant_application/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/grant_application/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/grant_application/grant_application.js b/erpnext/non_profit/doctype/grant_application/grant_application.js
deleted file mode 100644
index 70f319b..0000000
--- a/erpnext/non_profit/doctype/grant_application/grant_application.js
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Grant Application', {
-	refresh: function(frm) {
-		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Grant Application'};
-
-		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
-
-		if(!frm.doc.__islocal) {
-			frappe.contacts.render_address_and_contact(frm);
-		} else {
-			frappe.contacts.clear_address_and_contact(frm);
-		}
-
-		if(frm.doc.status == 'Received' && !frm.doc.email_notification_sent){
-			frm.add_custom_button(__("Send Grant Review Email"), function() {
-				frappe.call({
-					method: "erpnext.non_profit.doctype.grant_application.grant_application.send_grant_review_emails",
-					args: {
-						grant_application: frm.doc.name
-					}
-				});
-			});
-		}
-	}
-});
diff --git a/erpnext/non_profit/doctype/grant_application/grant_application.json b/erpnext/non_profit/doctype/grant_application/grant_application.json
deleted file mode 100644
index 2eb2087..0000000
--- a/erpnext/non_profit/doctype/grant_application/grant_application.json
+++ /dev/null
@@ -1,851 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 1, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 0, 
- "creation": "2017-09-21 12:02:01.206913", 
- "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": "applicant_type", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Applicant Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Individual\nOrganization", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "applicant_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": "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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval:doc.applicant_type=='Organization'", 
-   "fieldname": "contact_person", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Contact Person", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "email", 
-   "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": "Email", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_5", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Open", 
-   "fieldname": "status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Open\nReceived\nIn Progress\nApproved\nRejected\nExpired\nWithdrawn", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "website_url", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Website URL", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Company", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "address_contacts", 
-   "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": "Address and Contact", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "fa fa-map-marker", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "address_html", 
-   "fieldtype": "HTML", 
-   "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": "Address HTML", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_9", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "contact_html", 
-   "fieldtype": "HTML", 
-   "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": "Contact HTML", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "grant_application_details", 
-   "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": "Grant Application Details ", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "grant_description", 
-   "fieldtype": "Long Text", 
-   "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": "Grant 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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_15", 
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "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": "Requested Amount", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "has_any_past_grant_record", 
-   "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": "Has any past Grant Record", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_17", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "route", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Route", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "published", 
-   "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": "Show on Website", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "assessment_result", 
-   "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": "Assessment Result", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "assessment_mark", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Assessment  Mark (Out of 10)", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "note", 
-   "fieldtype": "Small Text", 
-   "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": "Note", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_24", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "assessment_manager", 
-   "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": "Assessment  Manager", 
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "email_notification_sent", 
-   "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": "Email Notification Sent", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }
- ], 
- "has_web_view": 1, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_field": "", 
- "image_view": 0, 
- "in_create": 0, 
- "is_published_field": "published", 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-06 12:39:57.677899", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Grant Application", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Non Profit Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "route": "grant-application", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "title_field": "applicant_name", 
- "track_changes": 1, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/grant_application/grant_application.py b/erpnext/non_profit/doctype/grant_application/grant_application.py
deleted file mode 100644
index cc5e1b1..0000000
--- a/erpnext/non_profit/doctype/grant_application/grant_application.py
+++ /dev/null
@@ -1,58 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.contacts.address_and_contact import load_address_and_contact
-from frappe.utils import get_url
-from frappe.website.website_generator import WebsiteGenerator
-
-
-class GrantApplication(WebsiteGenerator):
-	_website = frappe._dict(
-		condition_field = "published",
-	)
-
-	def validate(self):
-		if not self.route:	#pylint: disable=E0203
-			self.route = 'grant-application/' + self.scrub(self.name)
-
-	def onload(self):
-		"""Load address and contacts in `__onload`"""
-		load_address_and_contact(self)
-
-	def get_context(self, context):
-		context.no_cache = True
-		context.show_sidebar = True
-		context.parents = [dict(label='View All Grant Applications',
-			route='grant-application', title='View Grants')]
-
-def get_list_context(context):
-	context.allow_guest = True
-	context.no_cache = True
-	context.no_breadcrumbs = True
-	context.show_sidebar = True
-	context.order_by = 'creation desc'
-	context.introduction ='''<a class="btn btn-primary" href="/my-grant?new=1">
-		Apply for new Grant Application</a>'''
-
-@frappe.whitelist()
-def send_grant_review_emails(grant_application):
-	grant = frappe.get_doc("Grant Application", grant_application)
-	url =  get_url('grant-application/{0}'.format(grant_application))
-	frappe.sendmail(
-		recipients= grant.assessment_manager,
-		sender=frappe.session.user,
-		subject='Grant Application for {0}'.format(grant.applicant_name),
-		message='<p> Please Review this grant application</p><br>' + url,
-		reference_doctype=grant.doctype,
-		reference_name=grant.name
-	)
-
-	grant.status = 'In Progress'
-	grant.email_notification_sent = 1
-	grant.save()
-	frappe.db.commit()
-
-	frappe.msgprint(_("Review Invitation Sent"))
diff --git a/erpnext/non_profit/doctype/grant_application/templates/grant_application.html b/erpnext/non_profit/doctype/grant_application/templates/grant_application.html
deleted file mode 100644
index 52e8469..0000000
--- a/erpnext/non_profit/doctype/grant_application/templates/grant_application.html
+++ /dev/null
@@ -1,68 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block page_content %}
-	<h1>{{ applicant_name }}</h1>
-	{% if frappe.user == owner %}
-		<p><a class='btn btn-primary btn-sm' href="/my-grant?name={{ name }}">Edit Grant</a></p>
-	{% endif %}
-	<br>
-	<table class='table table-bordered small' style='max-width: 400px; margin-bottom: 0px;'>
-		<tr>
-			<td style='width: 38.2%'>Organization/Indvidual</td>
-			<td>{{ applicant_type }}</td>
-		</tr>
-		<tr>
-			<td>Grant Applicant Name</td>
-			<td>{{ applicant_name}}</td>
-		</tr>
-		<tr>
-			<td>Date</td>
-			<td>{{ frappe.format_date(creation) }}</td>
-		</tr>
-		<tr>
-			<td>Status</td>
-			<td>{{ status }}</td>
-		</tr>
-		<tr>
-			<td>Email</td>
-			<td>{{ email }}</td>
-		</tr>
-	</table>
-	<h2>Q. Please outline your current situation and why you are applying for a grant?</h2>
-	<p>	{{ grant_description }}</p>
-	<h2>Q. Requested grant amount</h2>
-	<p>{{ amount }}</p>
-	<h2>Q. Have you recevied grant from us before?</h2>
-	<p>{{ has_any_past_grant_record }}</p>
-	<h3>Contact</h3>
-	{% if frappe.user != 'Guest' %}
-		<table class='table table-bordered small' style='max-width: 400px; margin-bottom: 0px;'>
-			{% if contact_person %}
-				<tr>
-					<td style='width: 38.2%'>Contact Person</td>
-					<td>{{ contact_person }}</td>
-				</tr>
-			{% endif %}
-			<tr>
-				<td style='width: 38.2%'>Email</td>
-				<td>{{ email }}</td>
-			</tr>
-		</table>
-	{% else %}
-		<p><a href="/login">You must register and login to view contact details</a></p>
-	{% endif %}
-	<br>
-	{% if frappe.session.user == assessment_manager %}
-		{% if assessment_scale %}
-			<p> Assessment Review done </p>
-		{% endif %}
-	{% else %}
-	<p><br><a href="/my-grant?new=1" class='btn btn-primary'>Post a New Grant</a></p>
-	{% endif %}
-{% endblock %}
-{% block style %}
-<link type="text/css" rel="stylesheet" href="/assets/css/non-profits.css">
-<style>
-{% if style is defined %}{{ style }}{% endif %}
-</style>
-{% endblock %}
diff --git a/erpnext/non_profit/doctype/grant_application/templates/grant_application_row.html b/erpnext/non_profit/doctype/grant_application/templates/grant_application_row.html
deleted file mode 100644
index e375b16..0000000
--- a/erpnext/non_profit/doctype/grant_application/templates/grant_application_row.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% if doc.published %}
-	<div style='margin-bottom: 30px; max-width: 600px;'
-		class='with-border clickable'>
-		<a href="/{{ doc.route }}">
-			<h3 style='margin-top: 0px;'>{{ doc.name }}</h3>
-			<p>
-				<span class='label'>{{ frappe.format_date(doc.creation) }}</span>
-			</p>
-		</a>
-	</div>
-{% endif %}
diff --git a/erpnext/non_profit/doctype/grant_application/test_grant_application.py b/erpnext/non_profit/doctype/grant_application/test_grant_application.py
deleted file mode 100644
index ef267d7..0000000
--- a/erpnext/non_profit/doctype/grant_application/test_grant_application.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestGrantApplication(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/member/__init__.py b/erpnext/non_profit/doctype/member/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/member/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/member/member.js b/erpnext/non_profit/doctype/member/member.js
deleted file mode 100644
index e58ec0f..0000000
--- a/erpnext/non_profit/doctype/member/member.js
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Member', {
-	setup: function(frm) {
-		frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => {
-			if (val && (frm.doc.subscription_id || frm.doc.customer_id)) {
-				frm.set_df_property('razorpay_details_section', 'hidden', false);
-			}
-		})
-	},
-
-	refresh: function(frm) {
-
-		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Member'};
-
-		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
-
-		if(!frm.doc.__islocal) {
-			frappe.contacts.render_address_and_contact(frm);
-
-			// custom buttons
-			frm.add_custom_button(__('Accounting Ledger'), function() {
-				frappe.set_route('query-report', 'General Ledger',
-					{party_type:'Member', party:frm.doc.name});
-			});
-
-			frm.add_custom_button(__('Accounts Receivable'), function() {
-				frappe.set_route('query-report', 'Accounts Receivable', {member:frm.doc.name});
-			});
-
-			if (!frm.doc.customer) {
-				frm.add_custom_button(__('Create Customer'), () => {
-					frm.call('make_customer_and_link').then(() => {
-						frm.reload_doc();
-					});
-				});
-			}
-
-			// indicator
-			erpnext.utils.set_party_dashboard_indicators(frm);
-
-		} else {
-			frappe.contacts.clear_address_and_contact(frm);
-		}
-
-		frappe.call({
-			method:"frappe.client.get_value",
-			args:{
-				'doctype':"Membership",
-				'filters':{'member': frm.doc.name},
-				'fieldname':[
-					'to_date'
-				]
-			},
-			callback: function (data) {
-				if(data.message) {
-					frappe.model.set_value(frm.doctype,frm.docname,
-						"membership_expiry_date", data.message.to_date);
-				}
-			}
-		});
-	}
-});
diff --git a/erpnext/non_profit/doctype/member/member.json b/erpnext/non_profit/doctype/member/member.json
deleted file mode 100644
index 7c1baf1..0000000
--- a/erpnext/non_profit/doctype/member/member.json
+++ /dev/null
@@ -1,210 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "autoname": "naming_series:",
- "creation": "2017-09-11 09:24:52.898356",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "naming_series",
-  "member_name",
-  "membership_expiry_date",
-  "column_break_5",
-  "membership_type",
-  "email_id",
-  "image",
-  "customer_section",
-  "customer",
-  "customer_name",
-  "supplier_section",
-  "supplier",
-  "address_contacts",
-  "address_html",
-  "column_break_9",
-  "contact_html",
-  "razorpay_details_section",
-  "subscription_id",
-  "customer_id",
-  "subscription_status",
-  "column_break_21",
-  "subscription_start",
-  "subscription_end"
- ],
- "fields": [
-  {
-   "fieldname": "naming_series",
-   "fieldtype": "Select",
-   "label": "Series",
-   "options": "NPO-MEM-.YYYY.-",
-   "reqd": 1
-  },
-  {
-   "fieldname": "member_name",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Member Name",
-   "reqd": 1
-  },
-  {
-   "fieldname": "membership_expiry_date",
-   "fieldtype": "Date",
-   "label": "Membership Expiry Date"
-  },
-  {
-   "fieldname": "column_break_5",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "membership_type",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Membership Type",
-   "options": "Membership Type",
-   "reqd": 1
-  },
-  {
-   "fieldname": "image",
-   "fieldtype": "Attach Image",
-   "hidden": 1,
-   "label": "Image",
-   "no_copy": 1,
-   "print_hide": 1
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "customer_section",
-   "fieldtype": "Section Break",
-   "label": "Customer"
-  },
-  {
-   "fieldname": "customer",
-   "fieldtype": "Link",
-   "label": "Customer",
-   "options": "Customer"
-  },
-  {
-   "fetch_from": "customer.customer_name",
-   "fieldname": "customer_name",
-   "fieldtype": "Data",
-   "label": "Customer Name",
-   "read_only": 1
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "supplier_section",
-   "fieldtype": "Section Break",
-   "label": "Supplier"
-  },
-  {
-   "fieldname": "supplier",
-   "fieldtype": "Link",
-   "label": "Supplier",
-   "options": "Supplier"
-  },
-  {
-   "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"
-  },
-  {
-   "fieldname": "column_break_9",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "contact_html",
-   "fieldtype": "HTML",
-   "label": "Contact HTML"
-  },
-  {
-   "fieldname": "email_id",
-   "fieldtype": "Data",
-   "label": "Email Address",
-   "options": "Email"
-  },
-  {
-   "fieldname": "subscription_id",
-   "fieldtype": "Data",
-   "label": "Subscription ID",
-   "read_only": 1
-  },
-  {
-   "fieldname": "customer_id",
-   "fieldtype": "Data",
-   "label": "Customer ID",
-   "read_only": 1
-  },
-  {
-   "fieldname": "razorpay_details_section",
-   "fieldtype": "Section Break",
-   "hidden": 1,
-   "label": "Razorpay Details"
-  },
-  {
-   "fieldname": "column_break_21",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "subscription_start",
-   "fieldtype": "Date",
-   "label": "Subscription Start "
-  },
-  {
-   "fieldname": "subscription_end",
-   "fieldtype": "Date",
-   "label": "Subscription End"
-  },
-  {
-   "fieldname": "subscription_status",
-   "fieldtype": "Select",
-   "label": "Subscription Status",
-   "options": "\nActive\nHalted"
-  }
- ],
- "image_field": "image",
- "links": [],
- "modified": "2021-07-11 14:27:26.368039",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Member",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Manager",
-   "share": 1,
-   "write": 1
-  },
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Member",
-   "share": 1,
-   "write": 1
-  }
- ],
- "quick_entry": 1,
- "restrict_to_domain": "Non Profit",
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "member_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
deleted file mode 100644
index 4d80e57..0000000
--- a/erpnext/non_profit/doctype/member/member.py
+++ /dev/null
@@ -1,185 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.contacts.address_and_contact import load_address_and_contact
-from frappe.integrations.utils import get_payment_gateway_controller
-from frappe.model.document import Document
-from frappe.utils import cint, get_link_to_form
-
-from erpnext.non_profit.doctype.membership_type.membership_type import get_membership_type
-
-
-class Member(Document):
-	def onload(self):
-		"""Load address and contacts in `__onload`"""
-		load_address_and_contact(self)
-
-
-	def validate(self):
-		if self.email_id:
-			self.validate_email_type(self.email_id)
-
-	def validate_email_type(self, email):
-		from frappe.utils import validate_email_address
-		validate_email_address(email.strip(), True)
-
-	def setup_subscription(self):
-		non_profit_settings = frappe.get_doc('Non Profit Settings')
-		if not non_profit_settings.enable_razorpay_for_memberships:
-			frappe.throw(_('Please check Enable Razorpay for Memberships in {0} to setup subscription')).format(
-				get_link_to_form('Non Profit Settings', 'Non Profit Settings'))
-
-		controller = get_payment_gateway_controller("Razorpay")
-		settings = controller.get_settings({})
-
-		plan_id = frappe.get_value("Membership Type", self.membership_type, "razorpay_plan_id")
-
-		if not plan_id:
-			frappe.throw(_("Please setup Razorpay Plan ID"))
-
-		subscription_details = {
-			"plan_id": plan_id,
-			"billing_frequency": cint(non_profit_settings.billing_frequency),
-			"customer_notify": 1
-		}
-
-		args = {
-			'subscription_details': subscription_details
-		}
-
-		subscription = controller.setup_subscription(settings, **args)
-
-		return subscription
-
-	@frappe.whitelist()
-	def make_customer_and_link(self):
-		if self.customer:
-			frappe.msgprint(_("A customer is already linked to this Member"))
-
-		customer = create_customer(frappe._dict({
-			'fullname': self.member_name,
-			'email': self.email_id,
-			'phone': None
-		}))
-
-		self.customer = customer
-		self.save()
-		frappe.msgprint(_("Customer {0} has been created succesfully.").format(self.customer))
-
-
-def get_or_create_member(user_details):
-	member_list = frappe.get_all("Member", filters={'email': user_details.email, 'membership_type': user_details.plan_id})
-	if member_list and member_list[0]:
-		return member_list[0]['name']
-	else:
-		return create_member(user_details)
-
-def create_member(user_details):
-	user_details = frappe._dict(user_details)
-	member = frappe.new_doc("Member")
-	member.update({
-		"member_name": user_details.fullname,
-		"email_id": user_details.email,
-		"pan_number": user_details.pan or None,
-		"membership_type": user_details.plan_id,
-		"customer_id": user_details.customer_id or None,
-		"subscription_id": user_details.subscription_id or None,
-		"subscription_status": user_details.subscription_status or ""
-	})
-
-	member.insert(ignore_permissions=True)
-	member.customer = create_customer(user_details, member.name)
-	member.save(ignore_permissions=True)
-
-	return member
-
-def create_customer(user_details, member=None):
-	customer = frappe.new_doc("Customer")
-	customer.customer_name = user_details.fullname
-	customer.customer_type = "Individual"
-	customer.flags.ignore_mandatory = True
-	customer.insert(ignore_permissions=True)
-
-	try:
-		contact = frappe.new_doc("Contact")
-		contact.first_name = user_details.fullname
-		if user_details.mobile:
-			contact.add_phone(user_details.mobile, is_primary_phone=1, is_primary_mobile_no=1)
-		if user_details.email:
-			contact.add_email(user_details.email, is_primary=1)
-		contact.insert(ignore_permissions=True)
-
-		contact.append("links", {
-			"link_doctype": "Customer",
-			"link_name": customer.name
-		})
-
-		if member:
-			contact.append("links", {
-				"link_doctype": "Member",
-				"link_name": member
-			})
-
-		contact.save(ignore_permissions=True)
-
-	except frappe.DuplicateEntryError:
-		return customer.name
-
-	except Exception as e:
-		frappe.log_error(frappe.get_traceback(), _("Contact Creation Failed"))
-		pass
-
-	return customer.name
-
-@frappe.whitelist(allow_guest=True)
-def create_member_subscription_order(user_details):
-	"""Create Member subscription and order for payment
-
-	Args:
-		user_details (TYPE): Description
-
-	Returns:
-		Dictionary: Dictionary with subscription details
-		{
-			'subscription_details': {
-										'plan_id': 'plan_EXwyxDYDCj3X4v',
-										'billing_frequency': 24,
-										'customer_notify': 1
-									},
-			'subscription_id': 'sub_EZycCvXFvqnC6p'
-		}
-	"""
-
-	user_details = frappe._dict(user_details)
-	member = get_or_create_member(user_details)
-
-	subscription = member.setup_subscription()
-
-	member.subscription_id = subscription.get('subscription_id')
-	member.save(ignore_permissions=True)
-
-	return subscription
-
-@frappe.whitelist()
-def register_member(fullname, email, rzpay_plan_id, subscription_id, pan=None, mobile=None):
-	plan = get_membership_type(rzpay_plan_id)
-	if not plan:
-		raise frappe.DoesNotExistError
-
-	member = frappe.db.exists("Member", {'email': email, 'subscription_id': subscription_id })
-	if member:
-		return member
-	else:
-		member = create_member(dict(
-			fullname=fullname,
-			email=email,
-			plan_id=plan,
-			subscription_id=subscription_id,
-			pan=pan,
-			mobile=mobile
-		))
-
-		return member.name
diff --git a/erpnext/non_profit/doctype/member/member_dashboard.py b/erpnext/non_profit/doctype/member/member_dashboard.py
deleted file mode 100644
index 0e31e3c..0000000
--- a/erpnext/non_profit/doctype/member/member_dashboard.py
+++ /dev/null
@@ -1,22 +0,0 @@
-from frappe import _
-
-
-def get_data():
-	return {
-		'heatmap': True,
-		'heatmap_message': _('Member Activity'),
-		'fieldname': 'member',
-		'non_standard_fieldnames': {
-			'Bank Account': 'party'
-		},
-		'transactions': [
-			{
-				'label': _('Membership Details'),
-				'items': ['Membership']
-			},
-			{
-				'label': _('Fee'),
-				'items': ['Bank Account']
-			}
-		]
-	}
diff --git a/erpnext/non_profit/doctype/member/member_list.js b/erpnext/non_profit/doctype/member/member_list.js
deleted file mode 100644
index 8e41e7f..0000000
--- a/erpnext/non_profit/doctype/member/member_list.js
+++ /dev/null
@@ -1,3 +0,0 @@
-frappe.listview_settings['Member'] = {
-	add_fields: ["member_name", "membership_type", "image"],
-};
diff --git a/erpnext/non_profit/doctype/member/test_member.py b/erpnext/non_profit/doctype/member/test_member.py
deleted file mode 100644
index 46f14ed..0000000
--- a/erpnext/non_profit/doctype/member/test_member.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestMember(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/membership/__init__.py b/erpnext/non_profit/doctype/membership/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/membership/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/membership/membership.js b/erpnext/non_profit/doctype/membership/membership.js
deleted file mode 100644
index 3187204..0000000
--- a/erpnext/non_profit/doctype/membership/membership.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Membership', {
-	setup: function(frm) {
-		frappe.db.get_single_value("Non Profit Settings", "enable_razorpay_for_memberships").then(val => {
-			if (val) frm.set_df_property("razorpay_details_section", "hidden", false);
-		})
-	},
-
-	refresh: function(frm) {
-		if (frm.doc.__islocal)
-			return;
-
-		!frm.doc.invoice && frm.add_custom_button("Generate Invoice", () => {
-			frm.call({
-				doc: frm.doc,
-				method: "generate_invoice",
-				args: {save: true},
-				freeze: true,
-				freeze_message: __("Creating Membership Invoice"),
-				callback: function(r) {
-					if (r.invoice)
-						frm.reload_doc();
-				}
-			});
-		});
-
-		frappe.db.get_single_value("Non Profit Settings", "send_email").then(val => {
-			if (val) frm.add_custom_button("Send Acknowledgement", () => {
-				frm.call("send_acknowlement").then(() => {
-					frm.reload_doc();
-				});
-			});
-		})
-	},
-
-	onload: function(frm) {
-		frm.add_fetch("membership_type", "amount", "amount");
-	}
-});
diff --git a/erpnext/non_profit/doctype/membership/membership.json b/erpnext/non_profit/doctype/membership/membership.json
deleted file mode 100644
index 11d32f9..0000000
--- a/erpnext/non_profit/doctype/membership/membership.json
+++ /dev/null
@@ -1,184 +0,0 @@
-{
- "actions": [],
- "autoname": "NPO-MSH-.YYYY.-.#####",
- "creation": "2017-09-11 11:39:18.492184",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "member",
-  "member_name",
-  "membership_type",
-  "column_break_3",
-  "company",
-  "membership_status",
-  "membership_validity_section",
-  "from_date",
-  "to_date",
-  "column_break_8",
-  "member_since_date",
-  "payment_details",
-  "paid",
-  "currency",
-  "amount",
-  "invoice",
-  "razorpay_details_section",
-  "subscription_id",
-  "payment_id"
- ],
- "fields": [
-  {
-   "fieldname": "member",
-   "fieldtype": "Link",
-   "label": "Member",
-   "options": "Member"
-  },
-  {
-   "fieldname": "membership_type",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Membership Type",
-   "options": "Membership Type",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_3",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "membership_status",
-   "fieldtype": "Select",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Membership Status",
-   "options": "New\nCurrent\nExpired\nPending\nCancelled"
-  },
-  {
-   "fieldname": "membership_validity_section",
-   "fieldtype": "Section Break",
-   "label": "Validity"
-  },
-  {
-   "fieldname": "from_date",
-   "fieldtype": "Date",
-   "in_list_view": 1,
-   "label": "From",
-   "reqd": 1
-  },
-  {
-   "fieldname": "to_date",
-   "fieldtype": "Date",
-   "in_list_view": 1,
-   "label": "To",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_8",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "member_since_date",
-   "fieldtype": "Date",
-   "label": "Member Since"
-  },
-  {
-   "fieldname": "payment_details",
-   "fieldtype": "Section Break",
-   "label": "Payment Details"
-  },
-  {
-   "default": "0",
-   "fieldname": "paid",
-   "fieldtype": "Check",
-   "label": "Paid"
-  },
-  {
-   "fieldname": "currency",
-   "fieldtype": "Link",
-   "label": "Currency",
-   "options": "Currency"
-  },
-  {
-   "fieldname": "amount",
-   "fieldtype": "Float",
-   "label": "Amount"
-  },
-  {
-   "fieldname": "razorpay_details_section",
-   "fieldtype": "Section Break",
-   "hidden": 1,
-   "label": "Razorpay Details"
-  },
-  {
-   "fieldname": "subscription_id",
-   "fieldtype": "Data",
-   "label": "Subscription ID",
-   "read_only": 1
-  },
-  {
-   "fieldname": "payment_id",
-   "fieldtype": "Data",
-   "label": "Payment ID",
-   "read_only": 1
-  },
-  {
-   "fieldname": "invoice",
-   "fieldtype": "Link",
-   "label": "Invoice",
-   "options": "Sales Invoice"
-  },
-  {
-   "fetch_from": "member.member_name",
-   "fieldname": "member_name",
-   "fieldtype": "Data",
-   "label": "Member Name",
-   "read_only": 1
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-02-19 14:33:44.925122",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Membership",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Manager",
-   "share": 1,
-   "write": 1
-  },
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Member",
-   "share": 1,
-   "write": 1
-  }
- ],
- "restrict_to_domain": "Non Profit",
- "search_fields": "member, member_name",
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "member_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
deleted file mode 100644
index f9b295a..0000000
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ /dev/null
@@ -1,415 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import json
-from datetime import datetime
-
-import frappe
-from frappe import _
-from frappe.email import sendmail_to_system_managers
-from frappe.model.document import Document
-from frappe.utils import add_days, add_months, add_years, get_link_to_form, getdate, nowdate
-
-import erpnext
-from erpnext.non_profit.doctype.member.member import create_member
-
-
-class Membership(Document):
-	def validate(self):
-		if not self.member or not frappe.db.exists("Member", self.member):
-			# for web forms
-			user_type = frappe.db.get_value("User", frappe.session.user, "user_type")
-			if user_type == "Website User":
-				self.create_member_from_website_user()
-			else:
-				frappe.throw(_("Please select a Member"))
-
-		self.validate_membership_period()
-
-	def create_member_from_website_user(self):
-		member_name = frappe.get_value("Member", dict(email_id=frappe.session.user))
-
-		if not member_name:
-			user = frappe.get_doc("User", frappe.session.user)
-			member = frappe.get_doc(dict(
-				doctype="Member",
-				email_id=frappe.session.user,
-				membership_type=self.membership_type,
-				member_name=user.get_fullname()
-			)).insert(ignore_permissions=True)
-			member_name = member.name
-
-		if self.get("__islocal"):
-			self.member = member_name
-
-	def validate_membership_period(self):
-		# get last membership (if active)
-		last_membership = erpnext.get_last_membership(self.member)
-
-		# if person applied for offline membership
-		if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator":
-			# if last membership does not expire in 30 days, then do not allow to renew
-			if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
-				frappe.throw(_("You can only renew if your membership expires within 30 days"))
-
-			self.from_date = add_days(last_membership.to_date, 1)
-		elif frappe.session.user == "Administrator":
-			self.from_date = self.from_date
-		else:
-			self.from_date = nowdate()
-
-		if frappe.db.get_single_value("Non Profit Settings", "billing_cycle") == "Yearly":
-			self.to_date = add_years(self.from_date, 1)
-		else:
-			self.to_date = add_months(self.from_date, 1)
-
-	def on_payment_authorized(self, status_changed_to=None):
-		if status_changed_to not in ("Completed", "Authorized"):
-			return
-		self.load_from_db()
-		self.db_set("paid", 1)
-		settings = frappe.get_doc("Non Profit Settings")
-		if settings.allow_invoicing and settings.automate_membership_invoicing:
-			self.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
-
-
-	@frappe.whitelist()
-	def generate_invoice(self, save=True, with_payment_entry=False):
-		if not (self.paid or self.currency or self.amount):
-			frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
-
-		if self.invoice:
-			frappe.throw(_("An invoice is already linked to this document"))
-
-		member = frappe.get_doc("Member", self.member)
-		if not member.customer:
-			frappe.throw(_("No customer linked to member {0}").format(frappe.bold(self.member)))
-
-		plan = frappe.get_doc("Membership Type", self.membership_type)
-		settings = frappe.get_doc("Non Profit Settings")
-		self.validate_membership_type_and_settings(plan, settings)
-
-		invoice = make_invoice(self, member, plan, settings)
-		self.reload()
-		self.invoice = invoice.name
-
-		if with_payment_entry:
-			self.make_payment_entry(settings, invoice)
-
-		if save:
-			self.save()
-
-		return invoice
-
-	def validate_membership_type_and_settings(self, plan, settings):
-		settings_link = get_link_to_form("Membership Type", self.membership_type)
-
-		if not settings.membership_debit_account:
-			frappe.throw(_("You need to set <b>Debit Account</b> in {0}").format(settings_link))
-
-		if not settings.company:
-			frappe.throw(_("You need to set <b>Default Company</b> for invoicing in {0}").format(settings_link))
-
-		if not plan.linked_item:
-			frappe.throw(_("Please set a Linked Item for the Membership Type {0}").format(
-				get_link_to_form("Membership Type", self.membership_type)))
-
-	def make_payment_entry(self, settings, invoice):
-		if not settings.membership_payment_account:
-			frappe.throw(_("You need to set <b>Payment Account</b> for Membership in {0}").format(
-				get_link_to_form("Non Profit Settings", "Non Profit Settings")))
-
-		from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-		frappe.flags.ignore_account_permission = True
-		pe = get_payment_entry(dt="Sales Invoice", dn=invoice.name, bank_amount=invoice.grand_total)
-		frappe.flags.ignore_account_permission=False
-		pe.paid_to = settings.membership_payment_account
-		pe.reference_no = self.name
-		pe.reference_date = getdate()
-		pe.flags.ignore_mandatory = True
-		pe.save()
-		pe.submit()
-
-	@frappe.whitelist()
-	def send_acknowlement(self):
-		settings = frappe.get_doc("Non Profit Settings")
-		if not settings.send_email:
-			frappe.throw(_("You need to enable <b>Send Acknowledge Email</b> in {0}").format(
-				get_link_to_form("Non Profit Settings", "Non Profit Settings")))
-
-		member = frappe.get_doc("Member", self.member)
-		if not member.email_id:
-			frappe.throw(_("Email address of member {0} is missing").format(frappe.utils.get_link_to_form("Member", self.member)))
-
-		plan = frappe.get_doc("Membership Type", self.membership_type)
-		email = member.email_id
-		attachments = [frappe.attach_print("Membership", self.name, print_format=settings.membership_print_format)]
-
-		if self.invoice and settings.send_invoice:
-			attachments.append(frappe.attach_print("Sales Invoice", self.invoice, print_format=settings.inv_print_format))
-
-		email_template = frappe.get_doc("Email Template", settings.email_template)
-		context = { "doc": self, "member": member}
-
-		email_args = {
-			"recipients": [email],
-			"message": frappe.render_template(email_template.get("response"), context),
-			"subject": frappe.render_template(email_template.get("subject"), context),
-			"attachments": attachments,
-			"reference_doctype": self.doctype,
-			"reference_name": self.name
-		}
-
-		if not frappe.flags.in_test:
-			frappe.enqueue(method=frappe.sendmail, queue="short", timeout=300, is_async=True, **email_args)
-		else:
-			frappe.sendmail(**email_args)
-
-	def generate_and_send_invoice(self):
-		self.generate_invoice(save=False)
-		self.send_acknowlement()
-
-
-def make_invoice(membership, member, plan, settings):
-	invoice = frappe.get_doc({
-		"doctype": "Sales Invoice",
-		"customer": member.customer,
-		"debit_to": settings.membership_debit_account,
-		"currency": membership.currency,
-		"company": settings.company,
-		"is_pos": 0,
-		"items": [
-			{
-				"item_code": plan.linked_item,
-				"rate": membership.amount,
-				"qty": 1
-			}
-		]
-	})
-	invoice.set_missing_values()
-	invoice.insert()
-	invoice.submit()
-
-	frappe.msgprint(_("Sales Invoice created successfully"))
-
-	return invoice
-
-
-def get_member_based_on_subscription(subscription_id, email=None, customer_id=None):
-	filters = {"subscription_id": subscription_id}
-	if email:
-		filters.update({"email_id": email})
-	if customer_id:
-		filters.update({"customer_id": customer_id})
-
-	members = frappe.get_all("Member", filters=filters, order_by="creation desc")
-
-	try:
-		return frappe.get_doc("Member", members[0]["name"])
-	except Exception:
-		return None
-
-
-def verify_signature(data, endpoint="Membership"):
-	signature = frappe.request.headers.get("X-Razorpay-Signature")
-
-	settings = frappe.get_doc("Non Profit Settings")
-	key = settings.get_webhook_secret(endpoint)
-
-	controller = frappe.get_doc("Razorpay Settings")
-
-	controller.verify_signature(data, signature, key)
-	frappe.set_user(settings.creation_user)
-
-
-@frappe.whitelist(allow_guest=True)
-def trigger_razorpay_subscription(*args, **kwargs):
-	data = frappe.request.get_data(as_text=True)
-	data = process_request_data(data)
-
-	subscription = data.payload.get("subscription", {}).get("entity", {})
-	subscription = frappe._dict(subscription)
-
-	payment = data.payload.get("payment", {}).get("entity", {})
-	payment = frappe._dict(payment)
-
-	try:
-		if not data.event == "subscription.charged":
-			return
-
-		member = get_member_based_on_subscription(subscription.id, payment.email)
-		if not member:
-			member = create_member(frappe._dict({
-				"fullname": payment.email,
-				"email": payment.email,
-				"plan_id": get_plan_from_razorpay_id(subscription.plan_id)
-			}))
-
-			member.subscription_id = subscription.id
-			member.customer_id = payment.customer_id
-
-			if subscription.get("notes"):
-				member = get_additional_notes(member, subscription)
-
-		company = get_company_for_memberships()
-		# Update Membership
-		membership = frappe.new_doc("Membership")
-		membership.update({
-			"company": company,
-			"member": member.name,
-			"membership_status": "Current",
-			"membership_type": member.membership_type,
-			"currency": "INR",
-			"paid": 1,
-			"payment_id": payment.id,
-			"from_date": datetime.fromtimestamp(subscription.current_start),
-			"to_date": datetime.fromtimestamp(subscription.current_end),
-			"amount": payment.amount / 100 # Convert to rupees from paise
-		})
-		membership.flags.ignore_mandatory = True
-		membership.insert()
-
-		# Update membership values
-		member.subscription_start = datetime.fromtimestamp(subscription.start_at)
-		member.subscription_end = datetime.fromtimestamp(subscription.end_at)
-		member.subscription_status = "Active"
-		member.flags.ignore_mandatory = True
-		member.save()
-
-		settings = frappe.get_doc("Non Profit Settings")
-		if settings.allow_invoicing and settings.automate_membership_invoicing:
-			membership.reload()
-			membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
-
-	except Exception as e:
-		message = "{0}\n\n{1}\n\n{2}: {3}".format(e, frappe.get_traceback(), _("Payment ID"), payment.id)
-		log = frappe.log_error(message, _("Error creating membership entry for {0}").format(member.name))
-		notify_failure(log)
-		return {"status": "Failed", "reason": e}
-
-	return {"status": "Success"}
-
-
-@frappe.whitelist(allow_guest=True)
-def update_halted_razorpay_subscription(*args, **kwargs):
-	"""
-	When all retries have been exhausted, Razorpay moves the subscription to the halted state.
-	The customer has to manually retry the charge or change the card linked to the subscription,
-	for the subscription to move back to the active state.
-	"""
-	if frappe.request:
-		data = frappe.request.get_data(as_text=True)
-		data = process_request_data(data)
-	elif frappe.flags.in_test:
-		data = kwargs.get("data")
-		data = frappe._dict(data)
-	else:
-		return
-
-	if not data.event == "subscription.halted":
-		return
-
-	subscription = data.payload.get("subscription", {}).get("entity", {})
-	subscription = frappe._dict(subscription)
-
-	try:
-		member = get_member_based_on_subscription(subscription.id, customer_id=subscription.customer_id)
-		if not member:
-			frappe.throw(_("Member with Razorpay Subscription ID {0} not found").format(subscription.id))
-
-		member.subscription_status = "Halted"
-		member.flags.ignore_mandatory = True
-		member.save()
-
-		if subscription.get("notes"):
-			member = get_additional_notes(member, subscription)
-
-	except Exception as e:
-		message = "{0}\n\n{1}".format(e, frappe.get_traceback())
-		log = frappe.log_error(message, _("Error updating halted status for member {0}").format(member.name))
-		notify_failure(log)
-		return {"status": "Failed", "reason": e}
-
-	return {"status": "Success"}
-
-
-def process_request_data(data):
-	try:
-		verify_signature(data)
-	except Exception as e:
-		log = frappe.log_error(e, "Membership Webhook Verification Error")
-		notify_failure(log)
-		return {"status": "Failed", "reason": e}
-
-	if isinstance(data, str):
-		data = json.loads(data)
-	data = frappe._dict(data)
-
-	return data
-
-
-def get_company_for_memberships():
-	company = frappe.db.get_single_value("Non Profit Settings", "company")
-	if not company:
-		from erpnext.non_profit.utils import get_company
-		company = get_company()
-	return company
-
-
-def get_additional_notes(member, subscription):
-	if type(subscription.notes) == dict:
-		for k, v in subscription.notes.items():
-			notes = "\n".join("{}: {}".format(k, v))
-
-			# extract member name from notes
-			if "name" in k.lower():
-				member.update({
-					"member_name": subscription.notes.get(k)
-				})
-
-			# extract pan number from notes
-			if "pan" in k.lower():
-				member.update({
-					"pan_number": subscription.notes.get(k)
-				})
-
-		member.add_comment("Comment", notes)
-
-	elif type(subscription.notes) == str:
-		member.add_comment("Comment", subscription.notes)
-
-	return member
-
-
-def notify_failure(log):
-	try:
-		content = """
-			Dear System Manager,
-			Razorpay webhook for creating renewing membership subscription failed due to some reason.
-			Please check the following error log linked below
-			Error Log: {0}
-			Regards, Administrator
-		""".format(get_link_to_form("Error Log", log.name))
-
-		sendmail_to_system_managers("[Important] [ERPNext] Razorpay membership webhook failed , please check.", content)
-	except Exception:
-		pass
-
-
-def get_plan_from_razorpay_id(plan_id):
-	plan = frappe.get_all("Membership Type", filters={"razorpay_plan_id": plan_id}, order_by="creation desc")
-
-	try:
-		return plan[0]["name"]
-	except Exception:
-		return None
-
-
-def set_expired_status():
-	frappe.db.sql("""
-		UPDATE
-			`tabMembership` SET `membership_status` = 'Expired'
-		WHERE
-			`membership_status` not in ('Cancelled') AND `to_date` < %s
-		""", (nowdate()))
diff --git a/erpnext/non_profit/doctype/membership/membership_list.js b/erpnext/non_profit/doctype/membership/membership_list.js
deleted file mode 100644
index a959159..0000000
--- a/erpnext/non_profit/doctype/membership/membership_list.js
+++ /dev/null
@@ -1,15 +0,0 @@
-frappe.listview_settings['Membership'] = {
-	get_indicator: function(doc) {
-		if (doc.membership_status == 'New') {
-			return [__('New'), 'blue', 'membership_status,=,New'];
-		} else if (doc.membership_status === 'Current') {
-			return [__('Current'), 'green', 'membership_status,=,Current'];
-		} else if (doc.membership_status === 'Pending') {
-			return [__('Pending'), 'yellow', 'membership_status,=,Pending'];
-		} else if (doc.membership_status === 'Expired') {
-			return [__('Expired'), 'grey', 'membership_status,=,Expired'];
-		} else {
-			return [__('Cancelled'), 'red', 'membership_status,=,Cancelled'];
-		}
-	}
-};
diff --git a/erpnext/non_profit/doctype/membership/test_membership.py b/erpnext/non_profit/doctype/membership/test_membership.py
deleted file mode 100644
index fbe344c..0000000
--- a/erpnext/non_profit/doctype/membership/test_membership.py
+++ /dev/null
@@ -1,164 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-from frappe.utils import add_months, nowdate
-
-import erpnext
-from erpnext.non_profit.doctype.member.member import create_member
-from erpnext.non_profit.doctype.membership.membership import update_halted_razorpay_subscription
-
-
-class TestMembership(unittest.TestCase):
-	def setUp(self):
-		plan = setup_membership()
-
-		# make test member
-		self.member_doc = create_member(
-			frappe._dict({
-				"fullname": "_Test_Member",
-				"email": "_test_member_erpnext@example.com",
-				"plan_id": plan.name,
-				"subscription_id": "sub_DEX6xcJ1HSW4CR",
-				"customer_id": "cust_C0WlbKhp3aLA7W",
-				"subscription_status": "Active"
-			})
-		)
-		self.member_doc.make_customer_and_link()
-		self.member = self.member_doc.name
-
-	def test_auto_generate_invoice_and_payment_entry(self):
-		entry = make_membership(self.member)
-
-		# Naive test to see if at all invoice was generated and attached to member
-		# In any case if details were missing, the invoicing would throw an error
-		invoice = entry.generate_invoice(save=True)
-		self.assertEqual(invoice.name, entry.invoice)
-
-	def test_renew_within_30_days(self):
-		# create a membership for two months
-		# Should work fine
-		make_membership(self.member, { "from_date": nowdate() })
-		make_membership(self.member, { "from_date": add_months(nowdate(), 1) })
-
-		from frappe.utils.user import add_role
-		add_role("test@example.com", "Non Profit Manager")
-		frappe.set_user("test@example.com")
-
-		# create next membership with expiry not within 30 days
-		self.assertRaises(frappe.ValidationError, make_membership, self.member, {
-			"from_date": add_months(nowdate(), 2),
-		})
-
-		frappe.set_user("Administrator")
-		# create the same membership but as administrator
-		make_membership(self.member, {
-			"from_date": add_months(nowdate(), 2),
-			"to_date": add_months(nowdate(), 3),
-		})
-
-	def test_halted_memberships(self):
-		make_membership(self.member, {
-			"from_date": add_months(nowdate(), 2),
-			"to_date": add_months(nowdate(), 3)
-		})
-
-		self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Active")
-		payload = get_subscription_payload()
-		update_halted_razorpay_subscription(data=payload)
-		self.assertEqual(frappe.db.get_value("Member", self.member, "subscription_status"), "Halted")
-
-	def tearDown(self):
-		frappe.db.rollback()
-
-def set_config(key, value):
-	frappe.db.set_value("Non Profit Settings", None, key, value)
-
-def make_membership(member, payload={}):
-	data = {
-		"doctype": "Membership",
-		"member": member,
-		"membership_status": "Current",
-		"membership_type": "_rzpy_test_milythm",
-		"currency": "INR",
-		"paid": 1,
-		"from_date": nowdate(),
-		"amount": 100
-	}
-	data.update(payload)
-	membership = frappe.get_doc(data)
-	membership.insert(ignore_permissions=True, ignore_if_duplicate=True)
-	return membership
-
-def create_item(item_code):
-	if not frappe.db.exists("Item", item_code):
-		item = frappe.new_doc("Item")
-		item.item_code = item_code
-		item.item_name = item_code
-		item.stock_uom = "Nos"
-		item.description = item_code
-		item.item_group = "All Item Groups"
-		item.is_stock_item = 0
-		item.save()
-	else:
-		item = frappe.get_doc("Item", item_code)
-	return item
-
-def setup_membership():
-	# Get default company
-	company = frappe.get_doc("Company", erpnext.get_default_company())
-
-	# update non profit settings
-	settings = frappe.get_doc("Non Profit Settings")
-	# Enable razorpay
-	settings.enable_razorpay_for_memberships = 1
-	settings.billing_cycle = "Monthly"
-	settings.billing_frequency = 24
-	# Enable invoicing
-	settings.allow_invoicing = 1
-	settings.automate_membership_payment_entries = 1
-	settings.company = company.name
-	settings.donation_company = company.name
-	settings.membership_payment_account = company.default_cash_account
-	settings.membership_debit_account = company.default_receivable_account
-	settings.flags.ignore_mandatory = True
-	settings.save()
-
-	# make test plan
-	if not frappe.db.exists("Membership Type", "_rzpy_test_milythm"):
-		plan = frappe.new_doc("Membership Type")
-		plan.membership_type = "_rzpy_test_milythm"
-		plan.amount = 100
-		plan.razorpay_plan_id = "_rzpy_test_milythm"
-		plan.linked_item = create_item("_Test Item for Non Profit Membership").name
-		plan.insert()
-	else:
-		plan = frappe.get_doc("Membership Type", "_rzpy_test_milythm")
-
-	return plan
-
-def get_subscription_payload():
-	return {
-		"entity": "event",
-		"account_id": "acc_BFQ7uQEaa7j2z7",
-		"event": "subscription.halted",
-		"contains": [
-			"subscription"
-		],
-		"payload": {
-			"subscription": {
-				"entity": {
-					"id": "sub_DEX6xcJ1HSW4CR",
-					"entity": "subscription",
-					"plan_id": "_rzpy_test_milythm",
-					"customer_id": "cust_C0WlbKhp3aLA7W",
-					"status": "halted",
-					"notes": {
-						"Important": "Notes for Internal Reference"
-					},
-				}
-			}
-		}
-	}
diff --git a/erpnext/non_profit/doctype/membership_type/__init__.py b/erpnext/non_profit/doctype/membership_type/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/membership_type/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.js b/erpnext/non_profit/doctype/membership_type/membership_type.js
deleted file mode 100644
index 2f24276..0000000
--- a/erpnext/non_profit/doctype/membership_type/membership_type.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Membership Type', {
-	refresh: function (frm) {
-		frappe.db.get_single_value('Non Profit Settings', 'enable_razorpay_for_memberships').then(val => {
-			if (val) frm.set_df_property('razorpay_plan_id', 'hidden', false);
-		});
-
-		frappe.db.get_single_value('Non Profit Settings', 'allow_invoicing').then(val => {
-			if (val) frm.set_df_property('linked_item', 'hidden', false);
-		});
-
-		frm.set_query('linked_item', () => {
-			return {
-				filters: {
-					is_stock_item: 0
-				}
-			};
-		});
-	}
-});
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.json b/erpnext/non_profit/doctype/membership_type/membership_type.json
deleted file mode 100644
index 6ce1ecd..0000000
--- a/erpnext/non_profit/doctype/membership_type/membership_type.json
+++ /dev/null
@@ -1,71 +0,0 @@
-{
- "actions": [],
- "autoname": "field:membership_type",
- "creation": "2017-09-18 12:56:56.343999",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "membership_type",
-  "amount",
-  "razorpay_plan_id",
-  "linked_item"
- ],
- "fields": [
-  {
-   "fieldname": "membership_type",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Membership Type",
-   "reqd": 1,
-   "unique": 1
-  },
-  {
-   "fieldname": "amount",
-   "fieldtype": "Float",
-   "in_list_view": 1,
-   "label": "Amount",
-   "reqd": 1
-  },
-  {
-   "fieldname": "razorpay_plan_id",
-   "fieldtype": "Data",
-   "hidden": 1,
-   "label": "Razorpay Plan ID",
-   "unique": 1
-  },
-  {
-   "fieldname": "linked_item",
-   "fieldtype": "Link",
-   "label": "Linked Item",
-   "options": "Item",
-   "unique": 1
-  }
- ],
- "links": [],
- "modified": "2020-08-05 15:21:43.595745",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Membership Type",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "quick_entry": 1,
- "restrict_to_domain": "Non Profit",
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/membership_type/membership_type.py b/erpnext/non_profit/doctype/membership_type/membership_type.py
deleted file mode 100644
index b446421..0000000
--- a/erpnext/non_profit/doctype/membership_type/membership_type.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright (c) 2017, 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 MembershipType(Document):
-	def validate(self):
-		if self.linked_item:
-			is_stock_item = frappe.db.get_value("Item", self.linked_item, "is_stock_item")
-			if is_stock_item:
-				frappe.throw(_("The Linked Item should be a service item"))
-
-def get_membership_type(razorpay_id):
-	return frappe.db.exists("Membership Type", {"razorpay_plan_id": razorpay_id})
diff --git a/erpnext/non_profit/doctype/membership_type/test_membership_type.py b/erpnext/non_profit/doctype/membership_type/test_membership_type.py
deleted file mode 100644
index 98bc087..0000000
--- a/erpnext/non_profit/doctype/membership_type/test_membership_type.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestMembershipType(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/non_profit_settings/__init__.py b/erpnext/non_profit/doctype/non_profit_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/non_profit_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
deleted file mode 100644
index 4c4ca98..0000000
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
+++ /dev/null
@@ -1,133 +0,0 @@
-// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on("Non Profit Settings", {
-	refresh: function(frm) {
-		frm.set_query("inv_print_format", function() {
-			return {
-				filters: {
-					"doc_type": "Sales Invoice"
-				}
-			};
-		});
-
-		frm.set_query("membership_print_format", function() {
-			return {
-				filters: {
-					"doc_type": "Membership"
-				}
-			};
-		});
-
-		frm.set_query("membership_debit_account", function() {
-			return {
-				filters: {
-					"account_type": "Receivable",
-					"is_group": 0,
-					"company": frm.doc.company
-				}
-			};
-		});
-
-		frm.set_query("donation_debit_account", function() {
-			return {
-				filters: {
-					"account_type": "Receivable",
-					"is_group": 0,
-					"company": frm.doc.donation_company
-				}
-			};
-		});
-
-		frm.set_query("membership_payment_account", function () {
-			var account_types = ["Bank", "Cash"];
-			return {
-				filters: {
-					"account_type": ["in", account_types],
-					"is_group": 0,
-					"company": frm.doc.company
-				}
-			};
-		});
-
-		frm.set_query("donation_payment_account", function () {
-			var account_types = ["Bank", "Cash"];
-			return {
-				filters: {
-					"account_type": ["in", account_types],
-					"is_group": 0,
-					"company": frm.doc.donation_company
-				}
-			};
-		});
-
-		let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership";
-
-		frm.set_intro(__("You can learn more about memberships in the manual. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
-		frm.trigger("setup_buttons_for_membership");
-		frm.trigger("setup_buttons_for_donation");
-	},
-
-	setup_buttons_for_membership: function(frm) {
-		let label;
-
-		if (frm.doc.membership_webhook_secret) {
-
-			frm.add_custom_button(__("Copy Webhook URL"), () => {
-				frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.membership.membership.trigger_razorpay_subscription`);
-			}, __("Memberships"));
-
-			frm.add_custom_button(__("Revoke Key"), () => {
-				frm.call("revoke_key",  {
-					key: "membership_webhook_secret"
-				}).then(() => {
-					frm.refresh();
-				});
-			}, __("Memberships"));
-
-			label = __("Regenerate Webhook Secret");
-
-		} else {
-			label = __("Generate Webhook Secret");
-		}
-
-		frm.add_custom_button(label, () => {
-			frm.call("generate_webhook_secret", {
-				field: "membership_webhook_secret"
-			}).then(() => {
-				frm.refresh();
-			});
-		}, __("Memberships"));
-	},
-
-	setup_buttons_for_donation: function(frm) {
-		let label;
-
-		if (frm.doc.donation_webhook_secret) {
-			label = __("Regenerate Webhook Secret");
-
-			frm.add_custom_button(__("Copy Webhook URL"), () => {
-				frappe.utils.copy_to_clipboard(`https://${frappe.boot.sitename}/api/method/erpnext.non_profit.doctype.donation.donation.capture_razorpay_donations`);
-			}, __("Donations"));
-
-			frm.add_custom_button(__("Revoke Key"), () => {
-				frm.call("revoke_key", {
-					key: "donation_webhook_secret"
-				}).then(() => {
-					frm.refresh();
-				});
-			}, __("Donations"));
-
-		} else {
-			label = __("Generate Webhook Secret");
-		}
-
-		frm.add_custom_button(label, () => {
-			frm.call("generate_webhook_secret", {
-				field: "donation_webhook_secret"
-			}).then(() => {
-				frm.refresh();
-			});
-		}, __("Donations"));
-	}
-});
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json
deleted file mode 100644
index 25ff0c1..0000000
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.json
+++ /dev/null
@@ -1,273 +0,0 @@
-{
- "actions": [],
- "creation": "2020-03-29 12:57:03.005120",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "enable_razorpay_for_memberships",
-  "razorpay_settings_section",
-  "billing_cycle",
-  "billing_frequency",
-  "membership_webhook_secret",
-  "column_break_6",
-  "allow_invoicing",
-  "automate_membership_invoicing",
-  "automate_membership_payment_entries",
-  "company",
-  "membership_debit_account",
-  "membership_payment_account",
-  "column_break_9",
-  "send_email",
-  "send_invoice",
-  "membership_print_format",
-  "inv_print_format",
-  "email_template",
-  "donation_settings_section",
-  "donation_company",
-  "default_donor_type",
-  "donation_webhook_secret",
-  "column_break_22",
-  "automate_donation_payment_entries",
-  "donation_debit_account",
-  "donation_payment_account",
-  "section_break_27",
-  "creation_user"
- ],
- "fields": [
-  {
-   "fieldname": "billing_cycle",
-   "fieldtype": "Select",
-   "label": "Billing Cycle",
-   "options": "Monthly\nYearly"
-  },
-  {
-   "depends_on": "eval:doc.enable_razorpay_for_memberships",
-   "fieldname": "razorpay_settings_section",
-   "fieldtype": "Section Break",
-   "label": "RazorPay Settings for Memberships"
-  },
-  {
-   "description": "The number of billing cycles for which the customer should be charged. For example, if a customer is buying a 1-year membership that should be billed on a monthly basis, this value should be 12.",
-   "fieldname": "billing_frequency",
-   "fieldtype": "Int",
-   "label": "Billing Frequency"
-  },
-  {
-   "fieldname": "column_break_6",
-   "fieldtype": "Section Break",
-   "label": "Membership Invoicing"
-  },
-  {
-   "fieldname": "column_break_9",
-   "fieldtype": "Column Break"
-  },
-  {
-   "description": "This company will be set for the Memberships created via webhook.",
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  },
-  {
-   "default": "0",
-   "depends_on": "eval:doc.allow_invoicing && doc.send_email",
-   "fieldname": "send_invoice",
-   "fieldtype": "Check",
-   "label": "Send Invoice with Email"
-  },
-  {
-   "default": "0",
-   "fieldname": "send_email",
-   "fieldtype": "Check",
-   "label": "Send Membership Acknowledgement"
-  },
-  {
-   "depends_on": "eval: doc.send_invoice",
-   "fieldname": "inv_print_format",
-   "fieldtype": "Link",
-   "label": "Invoice Print Format",
-   "mandatory_depends_on": "eval: doc.send_invoice",
-   "options": "Print Format"
-  },
-  {
-   "depends_on": "eval:doc.send_email",
-   "fieldname": "membership_print_format",
-   "fieldtype": "Link",
-   "label": "Membership Print Format",
-   "options": "Print Format"
-  },
-  {
-   "depends_on": "eval:doc.send_email",
-   "fieldname": "email_template",
-   "fieldtype": "Link",
-   "label": "Email Template",
-   "mandatory_depends_on": "eval:doc.send_email",
-   "options": "Email Template"
-  },
-  {
-   "default": "0",
-   "fieldname": "allow_invoicing",
-   "fieldtype": "Check",
-   "label": "Allow Invoicing for Memberships",
-   "mandatory_depends_on": "eval:doc.send_invoice || doc.make_payment_entry"
-  },
-  {
-   "default": "0",
-   "depends_on": "eval:doc.allow_invoicing",
-   "description": "Automatically create an invoice when payment is authorized from a web form entry",
-   "fieldname": "automate_membership_invoicing",
-   "fieldtype": "Check",
-   "label": "Automate Invoicing for Web Forms"
-  },
-  {
-   "default": "0",
-   "depends_on": "eval:doc.allow_invoicing",
-   "description": "Auto creates Payment Entry for Sales Invoices created for Membership from web forms.",
-   "fieldname": "automate_membership_payment_entries",
-   "fieldtype": "Check",
-   "label": "Automate Payment Entry Creation"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_razorpay_for_memberships",
-   "fieldtype": "Check",
-   "label": "Enable RazorPay For Memberships"
-  },
-  {
-   "depends_on": "eval:doc.automate_membership_payment_entries",
-   "description": "Account for accepting membership payments",
-   "fieldname": "membership_payment_account",
-   "fieldtype": "Link",
-   "label": "Membership Payment To",
-   "mandatory_depends_on": "eval:doc.automate_membership_payment_entries",
-   "options": "Account"
-  },
-  {
-   "fieldname": "membership_webhook_secret",
-   "fieldtype": "Password",
-   "label": "Membership Webhook Secret",
-   "read_only": 1
-  },
-  {
-   "fieldname": "donation_webhook_secret",
-   "fieldtype": "Password",
-   "label": "Donation Webhook Secret",
-   "read_only": 1
-  },
-  {
-   "depends_on": "automate_donation_payment_entries",
-   "description": "Account for accepting donation payments",
-   "fieldname": "donation_payment_account",
-   "fieldtype": "Link",
-   "label": "Donation Payment To",
-   "mandatory_depends_on": "automate_donation_payment_entries",
-   "options": "Account"
-  },
-  {
-   "default": "0",
-   "description": "Auto creates Payment Entry for Donations created from web forms.",
-   "fieldname": "automate_donation_payment_entries",
-   "fieldtype": "Check",
-   "label": "Automate Donation Payment Entries"
-  },
-  {
-   "depends_on": "eval:doc.allow_invoicing",
-   "fieldname": "membership_debit_account",
-   "fieldtype": "Link",
-   "label": "Debit Account",
-   "mandatory_depends_on": "eval:doc.allow_invoicing",
-   "options": "Account"
-  },
-  {
-   "depends_on": "automate_donation_payment_entries",
-   "fieldname": "donation_debit_account",
-   "fieldtype": "Link",
-   "label": "Debit Account",
-   "mandatory_depends_on": "automate_donation_payment_entries",
-   "options": "Account"
-  },
-  {
-   "description": "This company will be set for the Donations created via webhook.",
-   "fieldname": "donation_company",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  },
-  {
-   "fieldname": "donation_settings_section",
-   "fieldtype": "Section Break",
-   "label": "Donation Settings"
-  },
-  {
-   "fieldname": "column_break_22",
-   "fieldtype": "Column Break"
-  },
-  {
-   "description": "This Donor Type will be set for the Donor created via Donation web form entry.",
-   "fieldname": "default_donor_type",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Default Donor Type",
-   "options": "Donor Type",
-   "reqd": 1
-  },
-  {
-   "fieldname": "section_break_27",
-   "fieldtype": "Section Break"
-  },
-  {
-   "description": "The user that will be used to create Donations, Memberships, Invoices, and Payment Entries. This user should have the relevant permissions.",
-   "fieldname": "creation_user",
-   "fieldtype": "Link",
-   "label": "Creation User",
-   "options": "User",
-   "reqd": 1
-  }
- ],
- "index_web_pages_for_search": 1,
- "issingle": 1,
- "links": [],
- "modified": "2021-03-11 10:43:38.124240",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Non Profit Settings",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "print": 1,
-   "read": 1,
-   "role": "System Manager",
-   "share": 1,
-   "write": 1
-  },
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "print": 1,
-   "read": 1,
-   "role": "Non Profit Manager",
-   "share": 1,
-   "write": 1
-  },
-  {
-   "email": 1,
-   "print": 1,
-   "read": 1,
-   "role": "Non Profit Member",
-   "share": 1
-  }
- ],
- "quick_entry": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
deleted file mode 100644
index ace6605..0000000
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.integrations.utils import get_payment_gateway_controller
-from frappe.model.document import Document
-
-
-class NonProfitSettings(Document):
-	@frappe.whitelist()
-	def generate_webhook_secret(self, field="membership_webhook_secret"):
-		key = frappe.generate_hash(length=20)
-		self.set(field, key)
-		self.save()
-
-		secret_for = "Membership" if field == "membership_webhook_secret" else "Donation"
-
-		frappe.msgprint(
-			_("Here is your webhook secret for {0} API, this will be shown to you only once.").format(secret_for) + "<br><br>" + key,
-			_("Webhook Secret")
-		)
-
-	@frappe.whitelist()
-	def revoke_key(self, key):
-		self.set(key, None)
-		self.save()
-
-	def get_webhook_secret(self, endpoint="Membership"):
-		fieldname = "membership_webhook_secret" if endpoint == "Membership" else "donation_webhook_secret"
-		return self.get_password(fieldname=fieldname, raise_exception=False)
-
-@frappe.whitelist()
-def get_plans_for_membership(*args, **kwargs):
-	controller = get_payment_gateway_controller("Razorpay")
-	plans = controller.get_plans()
-	return [plan.get("item") for plan in plans.get("items")]
diff --git a/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py
deleted file mode 100644
index 51d1ba0..0000000
--- a/erpnext/non_profit/doctype/non_profit_settings/test_non_profit_settings.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-# import frappe
-import unittest
-
-
-class TestNonProfitSettings(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/volunteer/__init__.py b/erpnext/non_profit/doctype/volunteer/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/volunteer/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/volunteer/test_volunteer.py b/erpnext/non_profit/doctype/volunteer/test_volunteer.py
deleted file mode 100644
index 0a0ab2c..0000000
--- a/erpnext/non_profit/doctype/volunteer/test_volunteer.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestVolunteer(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/volunteer/volunteer.js b/erpnext/non_profit/doctype/volunteer/volunteer.js
deleted file mode 100644
index ac93d8c..0000000
--- a/erpnext/non_profit/doctype/volunteer/volunteer.js
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Volunteer', {
-	refresh: function(frm) {
-
-		frappe.dynamic_link = {doc: frm.doc, fieldname: 'name', doctype: 'Volunteer'};
-
-		frm.toggle_display(['address_html','contact_html'], !frm.doc.__islocal);
-
-		if(!frm.doc.__islocal) {
-			frappe.contacts.render_address_and_contact(frm);
-		} else {
-			frappe.contacts.clear_address_and_contact(frm);
-		}
-	}
-});
diff --git a/erpnext/non_profit/doctype/volunteer/volunteer.json b/erpnext/non_profit/doctype/volunteer/volunteer.json
deleted file mode 100644
index 08b7f87..0000000
--- a/erpnext/non_profit/doctype/volunteer/volunteer.json
+++ /dev/null
@@ -1,148 +0,0 @@
-{
- "actions": [],
- "allow_rename": 1,
- "autoname": "field:email",
- "creation": "2017-09-19 16:16:45.676019",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "volunteer_name",
-  "column_break_5",
-  "volunteer_type",
-  "email",
-  "image",
-  "address_contacts",
-  "address_html",
-  "column_break_9",
-  "contact_html",
-  "volunteer_availability_and_skills_details",
-  "availability",
-  "availability_timeslot",
-  "column_break_12",
-  "volunteer_skills",
-  "section_break_15",
-  "note"
- ],
- "fields": [
-  {
-   "fieldname": "volunteer_name",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Volunteer Name",
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_5",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "volunteer_type",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Volunteer Type",
-   "options": "Volunteer Type",
-   "reqd": 1
-  },
-  {
-   "fieldname": "email",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Email",
-   "reqd": 1,
-   "unique": 1
-  },
-  {
-   "fieldname": "image",
-   "fieldtype": "Attach Image",
-   "hidden": 1,
-   "label": "Image",
-   "no_copy": 1,
-   "print_hide": 1
-  },
-  {
-   "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"
-  },
-  {
-   "fieldname": "column_break_9",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "contact_html",
-   "fieldtype": "HTML",
-   "label": "Contact HTML"
-  },
-  {
-   "fieldname": "volunteer_availability_and_skills_details",
-   "fieldtype": "Section Break",
-   "label": "Availability and Skills"
-  },
-  {
-   "fieldname": "availability",
-   "fieldtype": "Select",
-   "label": "Availability",
-   "options": "\nWeekly\nWeekdays\nWeekends"
-  },
-  {
-   "fieldname": "availability_timeslot",
-   "fieldtype": "Select",
-   "label": "Availability Timeslot",
-   "options": "\nMorning\nAfternoon\nEvening\nAnytime"
-  },
-  {
-   "fieldname": "column_break_12",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "volunteer_skills",
-   "fieldtype": "Table",
-   "label": "Volunteer Skills",
-   "options": "Volunteer Skill"
-  },
-  {
-   "fieldname": "section_break_15",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "note",
-   "fieldtype": "Long Text",
-   "label": "Note"
-  }
- ],
- "image_field": "image",
- "links": [],
- "modified": "2020-09-16 23:45:15.595952",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Volunteer",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "Non Profit Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "quick_entry": 1,
- "restrict_to_domain": "Non Profit",
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "volunteer_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/doctype/volunteer/volunteer.py b/erpnext/non_profit/doctype/volunteer/volunteer.py
deleted file mode 100644
index b44d67d..0000000
--- a/erpnext/non_profit/doctype/volunteer/volunteer.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.contacts.address_and_contact import load_address_and_contact
-from frappe.model.document import Document
-
-
-class Volunteer(Document):
-	def onload(self):
-		"""Load address and contacts in `__onload`"""
-		load_address_and_contact(self)
diff --git a/erpnext/non_profit/doctype/volunteer_skill/__init__.py b/erpnext/non_profit/doctype/volunteer_skill/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/volunteer_skill/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.json b/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.json
deleted file mode 100644
index 7d210aa..0000000
--- a/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.json
+++ /dev/null
@@ -1,73 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-09-20 15:26:26.453435", 
- "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": "volunteer_skill", 
-   "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": "Volunteer Skill", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "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
-  }
- ], 
- "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-12-06 11:54:14.396354", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Volunteer Skill", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "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/non_profit/doctype/volunteer_skill/volunteer_skill.py b/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.py
deleted file mode 100644
index fe72518..0000000
--- a/erpnext/non_profit/doctype/volunteer_skill/volunteer_skill.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class VolunteerSkill(Document):
-	pass
diff --git a/erpnext/non_profit/doctype/volunteer_type/__init__.py b/erpnext/non_profit/doctype/volunteer_type/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/doctype/volunteer_type/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py b/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py
deleted file mode 100644
index cef27c8..0000000
--- a/erpnext/non_profit/doctype/volunteer_type/test_volunteer_type.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestVolunteerType(unittest.TestCase):
-	pass
diff --git a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.js b/erpnext/non_profit/doctype/volunteer_type/volunteer_type.js
deleted file mode 100644
index 5c17505..0000000
--- a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Volunteer Type', {
-	refresh: function() {
-
-	}
-});
diff --git a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.json b/erpnext/non_profit/doctype/volunteer_type/volunteer_type.json
deleted file mode 100644
index 256b25f..0000000
--- a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.json
+++ /dev/null
@@ -1,94 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "prompt", 
- "beta": 0, 
- "creation": "2017-09-19 16:13:07.763273", 
- "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": "amount", 
-   "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": "Amount", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-06 11:52:08.800425", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Volunteer Type", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Non Profit Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Non Profit", 
- "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/non_profit/doctype/volunteer_type/volunteer_type.py b/erpnext/non_profit/doctype/volunteer_type/volunteer_type.py
deleted file mode 100644
index 3b1ae1a..0000000
--- a/erpnext/non_profit/doctype/volunteer_type/volunteer_type.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class VolunteerType(Document):
-	pass
diff --git a/erpnext/non_profit/report/__init__.py b/erpnext/non_profit/report/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/report/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/report/expiring_memberships/__init__.py b/erpnext/non_profit/report/expiring_memberships/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/report/expiring_memberships/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.js b/erpnext/non_profit/report/expiring_memberships/expiring_memberships.js
deleted file mode 100644
index be3a243..0000000
--- a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.js
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-/* eslint-disable */
-
-frappe.query_reports["Expiring Memberships"] = {
-	"filters": [
-		{
-			"fieldname": "fiscal_year",
-			"label": __("Fiscal Year"),
-			"fieldtype": "Link",
-			"options": "Fiscal Year",
-			"default": frappe.defaults.get_user_default("fiscal_year"),
-			"reqd": 1
-		},
-		{
-			"fieldname":"month",
-			"label": __("Month"),
-			"fieldtype": "Select",
-			"options": "Jan\nFeb\nMar\nApr\nMay\nJun\nJul\nAug\nSep\nOct\nNov\nDec",
-			"default": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
-				"Dec"][frappe.datetime.str_to_obj(frappe.datetime.get_today()).getMonth()],
-		}
-	]
-}
diff --git a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.json b/erpnext/non_profit/report/expiring_memberships/expiring_memberships.json
deleted file mode 100644
index c311057..0000000
--- a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "add_total_row": 0, 
- "apply_user_permissions": 1, 
- "creation": "2018-05-24 11:44:08.942809", 
- "disabled": 0, 
- "docstatus": 0, 
- "doctype": "Report", 
- "idx": 0, 
- "is_standard": "Yes", 
- "letter_head": "ERPNext Foundation", 
- "modified": "2018-05-24 11:44:08.942809", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "Expiring Memberships", 
- "owner": "Administrator", 
- "ref_doctype": "Membership", 
- "report_name": "Expiring Memberships", 
- "report_type": "Script Report", 
- "roles": [
-  {
-   "role": "Non Profit Manager"
-  }, 
-  {
-   "role": "Non Profit Member"
-  }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py b/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py
deleted file mode 100644
index 3ddbfdc..0000000
--- a/erpnext/non_profit/report/expiring_memberships/expiring_memberships.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-
-
-def execute(filters=None):
-	columns = get_columns(filters)
-	data = get_data(filters)
-	return columns, data
-
-def get_columns(filters):
-	return [
-			_("Membership Type") + ":Link/Membership Type:100", _("Membership ID") + ":Link/Membership:140",
-			_("Member ID") + ":Link/Member:140",  _("Member Name") + ":Data:140", _("Email") + ":Data:140",
-	 		_("Expiring On") + ":Date:120"
-	]
-
-def get_data(filters):
-
-	filters["month"] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"].index(filters.month) + 1
-
-	return frappe.db.sql("""
-		select ms.membership_type,ms.name,m.name,m.member_name,m.email,ms.max_membership_date
-		from `tabMember` m
-		inner join (select name,membership_type,max(to_date) as max_membership_date,member
-					from `tabMembership`
-					where paid = 1
-					group by member
-					order by max_membership_date asc) ms
-		on m.name = ms.member
-		where month(max_membership_date) = %(month)s and year(max_membership_date) = %(year)s """,{'month': filters.get('month'),'year':filters.get('fiscal_year')})
diff --git a/erpnext/non_profit/utils.py b/erpnext/non_profit/utils.py
deleted file mode 100644
index 47ea5f5..0000000
--- a/erpnext/non_profit/utils.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import frappe
-
-
-def get_company():
-	company = frappe.defaults.get_defaults().company
-	if company:
-		return company
-	else:
-		company = frappe.get_list("Company", limit=1)
-		if company:
-			return company[0].name
-	return None
diff --git a/erpnext/non_profit/web_form/__init__.py b/erpnext/non_profit/web_form/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/web_form/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/web_form/certification_application/__init__.py b/erpnext/non_profit/web_form/certification_application/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/web_form/certification_application/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/web_form/certification_application/certification_application.js b/erpnext/non_profit/web_form/certification_application/certification_application.js
deleted file mode 100644
index 8b455ed..0000000
--- a/erpnext/non_profit/web_form/certification_application/certification_application.js
+++ /dev/null
@@ -1,16 +0,0 @@
-frappe.ready(function() {
-    // bind events here
-    $(".page-header-actions-block .btn-primary, .page-header-actions-block .btn-default").addClass('hidden');
-    $(".text-right .btn-primary").addClass('hidden');
-
-    if (frappe.utils.get_url_arg('name')) {
-        $('.page-content .btn-form-submit').addClass('hidden');
-    } else {
-        user_name = frappe.full_name
-        user_email_id = frappe.session.user
-        $('[data-fieldname="currency"]').val("INR");
-        $('[data-fieldname="name_of_applicant"]').val(user_name);
-        $('[data-fieldname="email"]').val(user_email_id);
-        $('[data-fieldname="amount"]').val(20000);
-    }
-})
diff --git a/erpnext/non_profit/web_form/certification_application/certification_application.json b/erpnext/non_profit/web_form/certification_application/certification_application.json
deleted file mode 100644
index 5fda978..0000000
--- a/erpnext/non_profit/web_form/certification_application/certification_application.json
+++ /dev/null
@@ -1,79 +0,0 @@
-{
- "accept_payment": 1, 
- "allow_comments": 0, 
- "allow_delete": 0, 
- "allow_edit": 0, 
- "allow_incomplete": 0, 
- "allow_multiple": 1, 
- "allow_print": 0, 
- "amount": 0.0, 
- "amount_based_on_field": 1, 
- "amount_field": "amount", 
- "creation": "2018-06-08 16:24:05.805225", 
- "doc_type": "Certification Application", 
- "docstatus": 0, 
- "doctype": "Web Form", 
- "idx": 0, 
- "introduction_text": "", 
- "is_standard": 1, 
- "login_required": 1, 
- "max_attachment_size": 0, 
- "modified": "2018-06-11 16:11:14.544987", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "certification-application", 
- "owner": "Administrator", 
- "payment_button_help": "Pay for your certification using RazorPay", 
- "payment_button_label": "Pay Now", 
- "payment_gateway": "Razorpay", 
- "published": 1, 
- "route": "certification-application", 
- "show_sidebar": 1, 
- "sidebar_items": [], 
- "success_url": "/certification-application", 
- "title": "Certification Application", 
- "web_form_fields": [
-  {
-   "fieldname": "name_of_applicant", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Name of Applicant", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 0
-  }, 
-  {
-   "fieldname": "email", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "label": "Email", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "User", 
-   "read_only": 1, 
-   "reqd": 1
-  }, 
-  {
-   "fieldname": "currency", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "label": "Currency", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "USD\nINR", 
-   "read_only": 1, 
-   "reqd": 0
-  }, 
-  {
-   "fieldname": "amount", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "label": "Amount", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 1, 
-   "reqd": 0
-  }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/web_form/certification_application/certification_application.py b/erpnext/non_profit/web_form/certification_application/certification_application.py
deleted file mode 100644
index 02e3e93..0000000
--- a/erpnext/non_profit/web_form/certification_application/certification_application.py
+++ /dev/null
@@ -1,3 +0,0 @@
-def get_context(context):
-	# do your magic here
-	pass
diff --git a/erpnext/non_profit/web_form/certification_application_usd/__init__.py b/erpnext/non_profit/web_form/certification_application_usd/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/web_form/certification_application_usd/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.js b/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.js
deleted file mode 100644
index 005d1dd..0000000
--- a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.js
+++ /dev/null
@@ -1,16 +0,0 @@
-frappe.ready(function() {
-    // bind events here
-    $(".page-header-actions-block .btn-primary, .page-header-actions-block .btn-default").addClass('hidden');
-    $(".text-right .btn-primary").addClass('hidden');
-
-    if (frappe.utils.get_url_arg('name')) {
-        $('.page-content .btn-form-submit').addClass('hidden');
-    } else {
-        user_name = frappe.full_name
-        user_email_id = frappe.session.user
-        $('[data-fieldname="currency"]').val("USD");
-        $('[data-fieldname="name_of_applicant"]').val(user_name);
-        $('[data-fieldname="email"]').val(user_email_id);
-        $('[data-fieldname="amount"]').val(300);
-    }
-})
diff --git a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.json b/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.json
deleted file mode 100644
index 266109f..0000000
--- a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.json
+++ /dev/null
@@ -1,80 +0,0 @@
-{
- "accept_payment": 1, 
- "allow_comments": 0, 
- "allow_delete": 0, 
- "allow_edit": 0, 
- "allow_incomplete": 0, 
- "allow_multiple": 1, 
- "allow_print": 0, 
- "amount": 0.0, 
- "amount_based_on_field": 1, 
- "amount_field": "amount", 
- "creation": "2018-06-13 09:22:48.262441", 
- "currency": "USD", 
- "doc_type": "Certification Application", 
- "docstatus": 0, 
- "doctype": "Web Form", 
- "idx": 0, 
- "introduction_text": "", 
- "is_standard": 1, 
- "login_required": 1, 
- "max_attachment_size": 0, 
- "modified": "2018-06-13 09:26:35.502064", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "certification-application-usd", 
- "owner": "Administrator", 
- "payment_button_help": "Pay for your certification using PayPal", 
- "payment_button_label": "Pay Now", 
- "payment_gateway": "PayPal", 
- "published": 1, 
- "route": "certification-application-usd", 
- "show_sidebar": 1, 
- "sidebar_items": [], 
- "success_url": "/certification-application-usd", 
- "title": "Certification Application USD", 
- "web_form_fields": [
-  {
-   "fieldname": "name_of_applicant", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Name of Applicant", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 0
-  }, 
-  {
-   "fieldname": "email", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "label": "Email", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "User", 
-   "read_only": 1, 
-   "reqd": 1
-  }, 
-  {
-   "fieldname": "currency", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "label": "Currency", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "USD\nINR", 
-   "read_only": 1, 
-   "reqd": 0
-  }, 
-  {
-   "fieldname": "amount", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "label": "Amount", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 1, 
-   "reqd": 0
-  }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py b/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py
deleted file mode 100644
index 02e3e93..0000000
--- a/erpnext/non_profit/web_form/certification_application_usd/certification_application_usd.py
+++ /dev/null
@@ -1,3 +0,0 @@
-def get_context(context):
-	# do your magic here
-	pass
diff --git a/erpnext/non_profit/web_form/grant_application/__init__.py b/erpnext/non_profit/web_form/grant_application/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/non_profit/web_form/grant_application/__init__.py
+++ /dev/null
diff --git a/erpnext/non_profit/web_form/grant_application/grant_application.js b/erpnext/non_profit/web_form/grant_application/grant_application.js
deleted file mode 100644
index f09e540..0000000
--- a/erpnext/non_profit/web_form/grant_application/grant_application.js
+++ /dev/null
@@ -1,3 +0,0 @@
-frappe.ready(function() {
-	// bind events here
-});
diff --git a/erpnext/non_profit/web_form/grant_application/grant_application.json b/erpnext/non_profit/web_form/grant_application/grant_application.json
deleted file mode 100644
index 73c9445..0000000
--- a/erpnext/non_profit/web_form/grant_application/grant_application.json
+++ /dev/null
@@ -1,108 +0,0 @@
-{
- "accept_payment": 0, 
- "allow_comments": 0, 
- "allow_delete": 1, 
- "allow_edit": 1, 
- "allow_incomplete": 0, 
- "allow_multiple": 1, 
- "allow_print": 0, 
- "amount": 0.0, 
- "amount_based_on_field": 0, 
- "creation": "2017-10-30 15:57:10.825188", 
- "currency": "INR", 
- "doc_type": "Grant Application", 
- "docstatus": 0, 
- "doctype": "Web Form", 
- "idx": 0, 
- "introduction_text": "Share as many details as you can to get quick response from organization", 
- "is_standard": 1, 
- "login_required": 1, 
- "max_attachment_size": 0, 
- "modified": "2017-12-06 12:32:16.893289", 
- "modified_by": "Administrator", 
- "module": "Non Profit", 
- "name": "grant-application", 
- "owner": "Administrator", 
- "payment_button_label": "Buy Now", 
- "published": 1, 
- "route": "my-grant", 
- "show_sidebar": 1, 
- "sidebar_items": [], 
- "success_url": "/grant-application", 
- "title": "Grant Application", 
- "web_form_fields": [
-  {
-   "fieldname": "applicant_type", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "label": "Applicant Type", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "Individual\nOrganization", 
-   "read_only": 0, 
-   "reqd": 1
-  }, 
-  {
-   "fieldname": "applicant_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Name", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 1
-  }, 
-  {
-   "fieldname": "email", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "label": "Email Address", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 1
-  }, 
-  {
-   "description": "", 
-   "fieldname": "grant_description", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "label": "Please outline your current situation and why you are applying for a grant?", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 1
-  }, 
-  {
-   "fieldname": "amount", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "label": "Requested Amount", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 0
-  }, 
-  {
-   "fieldname": "has_any_past_grant_record", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "label": "Have you received any grant from us before?", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "options": "", 
-   "read_only": 0, 
-   "reqd": 0
-  }, 
-  {
-   "fieldname": "published", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "label": "Show on Website", 
-   "max_length": 0, 
-   "max_value": 0, 
-   "read_only": 0, 
-   "reqd": 0
-  }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/non_profit/web_form/grant_application/grant_application.py b/erpnext/non_profit/web_form/grant_application/grant_application.py
deleted file mode 100644
index 3dfb381..0000000
--- a/erpnext/non_profit/web_form/grant_application/grant_application.py
+++ /dev/null
@@ -1,4 +0,0 @@
-def get_context(context):
-	context.no_cache = True
-	context.parents = [dict(label='View All ',
-		route='grant-application', title='View All')]
diff --git a/erpnext/non_profit/workspace/non_profit/non_profit.json b/erpnext/non_profit/workspace/non_profit/non_profit.json
deleted file mode 100644
index fc90475..0000000
--- a/erpnext/non_profit/workspace/non_profit/non_profit.json
+++ /dev/null
@@ -1,272 +0,0 @@
-{
- "charts": [],
- "content": "[{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Member\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Non Profit Settings\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Membership\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chapter\",\"col\":3}},{\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Chapter Member\",\"col\":3}},{\"type\":\"spacer\",\"data\":{\"col\":12}},{\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports & Masters</b></span>\",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Loan Management\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Grant Application\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Membership\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Volunteer\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Chapter\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Donation\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Tax Exemption Certification (India)\",\"col\":4}}]",
- "creation": "2020-03-02 17:23:47.811421",
- "docstatus": 0,
- "doctype": "Workspace",
- "for_user": "",
- "hide_custom": 0,
- "icon": "non-profit",
- "idx": 0,
- "label": "Non Profit",
- "links": [
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Loan Management",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Loan Type",
-   "link_count": 0,
-   "link_to": "Loan Type",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Loan Application",
-   "link_count": 0,
-   "link_to": "Loan Application",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Loan",
-   "link_count": 0,
-   "link_to": "Loan",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Grant Application",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Grant Application",
-   "link_count": 0,
-   "link_to": "Grant Application",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Membership",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Member",
-   "link_count": 0,
-   "link_to": "Member",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Membership",
-   "link_count": 0,
-   "link_to": "Membership",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Membership Type",
-   "link_count": 0,
-   "link_to": "Membership Type",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Membership Settings",
-   "link_count": 0,
-   "link_to": "Non Profit Settings",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Volunteer",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Volunteer",
-   "link_count": 0,
-   "link_to": "Volunteer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Volunteer Type",
-   "link_count": 0,
-   "link_to": "Volunteer Type",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Chapter",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Chapter",
-   "link_count": 0,
-   "link_to": "Chapter",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donation",
-   "link_count": 0,
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donor",
-   "link_count": 0,
-   "link_to": "Donor",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donor Type",
-   "link_count": 0,
-   "link_to": "Donor Type",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donation",
-   "link_count": 0,
-   "link_to": "Donation",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Tax Exemption Certification (India)",
-   "link_count": 0,
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Tax Exemption 80G Certificate",
-   "link_count": 0,
-   "link_to": "Tax Exemption 80G Certificate",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  }
- ],
- "modified": "2022-01-13 17:40:50.220877",
- "modified_by": "Administrator",
- "module": "Non Profit",
- "name": "Non Profit",
- "owner": "Administrator",
- "parent_page": "",
- "public": 1,
- "restrict_to_domain": "Non Profit",
- "roles": [],
- "sequence_id": 18.0,
- "shortcuts": [
-  {
-   "label": "Member",
-   "link_to": "Member",
-   "type": "DocType"
-  },
-  {
-   "label": "Non Profit Settings",
-   "link_to": "Non Profit Settings",
-   "type": "DocType"
-  },
-  {
-   "label": "Membership",
-   "link_to": "Membership",
-   "type": "DocType"
-  },
-  {
-   "label": "Chapter",
-   "link_to": "Chapter",
-   "type": "DocType"
-  },
-  {
-   "label": "Chapter Member",
-   "link_to": "Chapter Member",
-   "type": "DocType"
-  }
- ],
- "title": "Non Profit"
-}
\ No newline at end of file
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index ed39c20..13f0e7b 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -1,4 +1,6 @@
+[pre_model_sync]
 erpnext.patches.v12_0.update_is_cancelled_field
+erpnext.patches.v13_0.add_bin_unique_constraint
 erpnext.patches.v11_0.rename_production_order_to_work_order
 erpnext.patches.v11_0.refactor_naming_series
 erpnext.patches.v11_0.refactor_autoname_naming
@@ -202,6 +204,7 @@
 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
+execute:frappe.reload_doc("regional", "doctype", "e_invoice_settings")
 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
@@ -222,11 +225,11 @@
 erpnext.patches.v13_0.set_app_name
 erpnext.patches.v13_0.print_uom_after_quantity_patch
 erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
+erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.update_reason_for_resignation_in_employee
 execute:frappe.delete_doc("Report", "Quoted Item Comparison")
 erpnext.patches.v13_0.update_member_email_address
-erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
 erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
 erpnext.patches.v13_0.add_po_to_global_search
@@ -242,6 +245,8 @@
 erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
 erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
 erpnext.patches.v13_0.update_vehicle_no_reqd_condition
+erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17
+erpnext.patches.v12_0.add_einvoice_summary_report_permissions
 erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
 erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
 erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
@@ -252,9 +257,10 @@
 erpnext.patches.v12_0.purchase_receipt_status
 erpnext.patches.v13_0.fix_non_unique_represents_company
 erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
-erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021
+erpnext.patches.v13_0.make_non_standard_user_type #13-04-2021 #17-01-2022
 erpnext.patches.v13_0.update_shipment_status
 erpnext.patches.v13_0.remove_attribute_field_from_item_variant_setting
+erpnext.patches.v12_0.add_ewaybill_validity_field
 erpnext.patches.v13_0.germany_make_custom_fields
 erpnext.patches.v13_0.germany_fill_debtor_creditor_number
 erpnext.patches.v13_0.set_pos_closing_as_failed
@@ -266,12 +272,11 @@
 erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
 erpnext.patches.v13_0.rename_issue_status_hold_to_on_hold
 erpnext.patches.v13_0.update_response_by_variance
-erpnext.patches.v13_0.bill_for_rejected_quantity_in_purchase_invoice
 erpnext.patches.v13_0.update_job_card_details
-erpnext.patches.v13_0.update_level_in_bom #1234sswef
 erpnext.patches.v13_0.add_missing_fg_item_for_stock_entry
 erpnext.patches.v13_0.update_subscription_status_in_memberships
 erpnext.patches.v13_0.update_amt_in_work_order_required_items
+erpnext.patches.v12_0.show_einvoice_irn_cancelled_field
 erpnext.patches.v13_0.delete_orphaned_tables
 erpnext.patches.v13_0.update_export_type_for_gst #2021-08-16
 erpnext.patches.v13_0.update_tds_check_field #3
@@ -282,15 +287,15 @@
 erpnext.patches.v13_0.trim_whitespace_from_serial_nos  # 16-01-2022
 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
 execute:frappe.reload_doc("erpnext_integrations", "doctype", "TaxJar Settings")
 execute:frappe.reload_doc("erpnext_integrations", "doctype", "Product Tax Category")
-erpnext.patches.v14_0.delete_einvoicing_doctypes
 erpnext.patches.v13_0.custom_fields_for_taxjar_integration          #08-11-2021
 erpnext.patches.v13_0.set_operation_time_based_on_operating_cost
 erpnext.patches.v13_0.create_gst_payment_entry_fields #27-11-2021
-erpnext.patches.v14_0.delete_shopify_doctypes
 erpnext.patches.v13_0.fix_invoice_statuses
+erpnext.patches.v13_0.create_website_items #30-09-2021
+erpnext.patches.v13_0.populate_e_commerce_settings
+erpnext.patches.v13_0.make_homepage_products_website_items
 erpnext.patches.v13_0.replace_supplier_item_group_with_party_specific_item
 erpnext.patches.v13_0.update_dates_in_tax_withholding_category
 erpnext.patches.v14_0.update_opportunity_currency_fields
@@ -312,19 +317,44 @@
 erpnext.patches.v14_0.delete_healthcare_doctypes
 erpnext.patches.v13_0.update_category_in_ltds_certificate
 erpnext.patches.v13_0.create_pan_field_for_india #2
-erpnext.patches.v14_0.delete_hub_doctypes
+erpnext.patches.v13_0.fetch_thumbnail_in_website_items
+erpnext.patches.v13_0.update_maintenance_schedule_field_in_visit
 erpnext.patches.v13_0.create_ksa_vat_custom_fields # 07-01-2022
-erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
 erpnext.patches.v14_0.migrate_crm_settings
 erpnext.patches.v13_0.rename_ksa_qr_field
+erpnext.patches.v13_0.wipe_serial_no_field_for_0_qty
 erpnext.patches.v13_0.disable_ksa_print_format_for_others # 16-12-2021
-erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
 erpnext.patches.v13_0.update_tax_category_for_rcm
 execute:frappe.delete_doc_if_exists('Workspace', 'ERPNext Integrations Settings')
 erpnext.patches.v14_0.set_payroll_cost_centers
 erpnext.patches.v13_0.agriculture_deprecation_warning
+erpnext.patches.v13_0.hospitality_deprecation_warning
+erpnext.patches.v13_0.update_asset_quantity_field
+erpnext.patches.v13_0.delete_bank_reconciliation_detail
+erpnext.patches.v13_0.enable_provisional_accounting
+erpnext.patches.v13_0.non_profit_deprecation_warning
+
+[post_model_sync]
+erpnext.patches.v14_0.rename_ongoing_status_in_sla_documents
+erpnext.patches.v14_0.add_default_exit_questionnaire_notification_template
+erpnext.patches.v14_0.delete_shopify_doctypes
+erpnext.patches.v14_0.delete_hub_doctypes
+erpnext.patches.v14_0.delete_hospitality_doctypes # 20-01-2022
 erpnext.patches.v14_0.delete_agriculture_doctypes
-erpnext.patches.v13_0.update_exchange_rate_settings
 erpnext.patches.v14_0.rearrange_company_fields
 erpnext.patches.v14_0.update_leave_notification_template
+erpnext.patches.v14_0.restore_einvoice_fields
+erpnext.patches.v13_0.update_sane_transfer_against
+erpnext.patches.v12_0.add_company_link_to_einvoice_settings
+erpnext.patches.v14_0.migrate_cost_center_allocations
+erpnext.patches.v13_0.convert_to_website_item_in_item_card_group_template
+erpnext.patches.v13_0.shopping_cart_to_ecommerce
+erpnext.patches.v13_0.update_disbursement_account
+erpnext.patches.v13_0.update_reserved_qty_closed_wo
+erpnext.patches.v13_0.update_exchange_rate_settings
+erpnext.patches.v14_0.delete_amazon_mws_doctype
+erpnext.patches.v13_0.set_work_order_qty_in_so_from_mr
+erpnext.patches.v13_0.update_accounts_in_loan_docs
+erpnext.patches.v14_0.update_batch_valuation_flag
+erpnext.patches.v14_0.delete_non_profit_doctypes
 erpnext.patches.v14_0.update_employee_advance_status
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
new file mode 100644
index 0000000..e498b67
--- /dev/null
+++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company or not frappe.db.count('E Invoice User'):
+		return
+
+	frappe.reload_doc("regional", "doctype", "e_invoice_user")
+	for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
+		company_name = frappe.db.sql("""
+			select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
+			where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
+		""", (creds.get('gstin')))
+		if company_name and len(company_name) > 0:
+			frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py
new file mode 100644
index 0000000..aeff9ca
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_status_field.py
@@ -0,0 +1,72 @@
+from __future__ import unicode_literals
+
+import json
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	# move hidden einvoice fields to a different section
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+			print_hide=1, hidden=1),
+
+			dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+				no_copy=1, print_hide=1),
+
+			dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+			dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
+				no_copy=1, print_hide=1),
+
+			dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+				no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+				no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+				no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+				options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+				hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+
+	if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'):
+		frappe.db.sql('''
+			UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
+			WHERE
+				posting_date >= '2021-04-01'
+				AND ifnull(irn, '') = ''
+				AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
+				AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
+		''')
+
+		# set appropriate statuses
+		frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
+			WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''')
+
+		frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
+			WHERE ifnull(irn_cancelled, 0) = 1''')
+
+	# set correct acknowledgement in e-invoices
+	einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice'])
+
+	if einvoices:
+		for inv in einvoices:
+			signed_einvoice = inv.get('signed_einvoice')
+			if signed_einvoice:
+				signed_einvoice = json.loads(signed_einvoice)
+				frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False)
+				frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False)
diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
new file mode 100644
index 0000000..e837786
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
@@ -0,0 +1,20 @@
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	if frappe.db.exists('Report', 'E-Invoice Summary') and \
+		not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')):
+		frappe.get_doc(dict(
+			doctype='Custom Role',
+			report='E-Invoice Summary',
+			roles= [
+				dict(role='Accounts User'),
+				dict(role='Accounts Manager')
+			]
+		)).insert()
diff --git a/erpnext/patches/v12_0/add_ewaybill_validity_field.py b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
new file mode 100644
index 0000000..247140d
--- /dev/null
+++ b/erpnext/patches/v12_0/add_ewaybill_validity_field.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+				depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill')
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py
index 9b083ca..8dec9ff 100644
--- a/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py
+++ b/erpnext/patches/v12_0/recalculate_requested_qty_in_bin.py
@@ -9,6 +9,8 @@
 		FROM `tabBin`""",as_dict=1)
 
 	for entry in bin_details:
+		if not (entry.item_code and entry.warehouse):
+			continue
 		update_bin_qty(entry.get("item_code"), entry.get("warehouse"), {
 			"indented_qty": get_indented_qty(entry.get("item_code"), entry.get("warehouse"))
 		})
diff --git a/erpnext/patches/v12_0/rename_mws_settings_fields.py b/erpnext/patches/v12_0/rename_mws_settings_fields.py
deleted file mode 100644
index d5bf38d..0000000
--- a/erpnext/patches/v12_0/rename_mws_settings_fields.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (c) 2020, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-
-
-def execute():
-	count = frappe.db.sql("SELECT COUNT(*) FROM `tabSingles` WHERE doctype='Amazon MWS Settings' AND field='enable_sync';")[0][0]
-	if count == 0:
-		frappe.db.sql("UPDATE `tabSingles` SET field='enable_sync' WHERE doctype='Amazon MWS Settings' AND field='enable_synch';")
-
-	frappe.reload_doc("ERPNext Integrations", "doctype", "Amazon MWS Settings")
diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py
new file mode 100644
index 0000000..c17666a
--- /dev/null
+++ b/erpnext/patches/v12_0/setup_einvoice_fields.py
@@ -0,0 +1,59 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+from erpnext.regional.india.setup import add_permissions, add_print_formats
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	frappe.reload_doc("custom", "doctype", "custom_field")
+	frappe.reload_doc("regional", "doctype", "e_invoice_settings")
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+				depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+
+			dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
+
+			dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+			dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+	add_permissions()
+	add_print_formats()
+
+	einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
+	t = {
+		'mode_of_transport': [{'default': None}],
+		'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
+		'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+		'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+		'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+		'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+		'ewaybill': [
+			{'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
+			{'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
+		]
+	}
+
+	for field, conditions in t.items():
+		for c in conditions:
+			[(prop, value)] = c.items()
+			frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)
diff --git a/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
new file mode 100644
index 0000000..3f90a03
--- /dev/null
+++ b/erpnext/patches/v12_0/show_einvoice_irn_cancelled_field.py
@@ -0,0 +1,14 @@
+from __future__ import unicode_literals
+
+import frappe
+
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	irn_cancelled_field = frappe.db.exists('Custom Field', {'dt': 'Sales Invoice', 'fieldname': 'irn_cancelled'})
+	if irn_cancelled_field:
+		frappe.db.set_value('Custom Field', irn_cancelled_field, 'depends_on', 'eval: doc.irn')
+		frappe.db.set_value('Custom Field', irn_cancelled_field, 'read_only', 0)
diff --git a/erpnext/patches/v12_0/update_is_cancelled_field.py b/erpnext/patches/v12_0/update_is_cancelled_field.py
index df78750..0401034 100644
--- a/erpnext/patches/v12_0/update_is_cancelled_field.py
+++ b/erpnext/patches/v12_0/update_is_cancelled_field.py
@@ -2,14 +2,28 @@
 
 
 def execute():
-	try:
-		frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
-		frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 0 where is_cancelled in ('', NULL, 'No')")
+	#handle type casting for is_cancelled field
+	module_doctypes = (
+		('stock', 'Stock Ledger Entry'),
+		('stock', 'Serial No'),
+		('accounts', 'GL Entry')
+	)
 
-		frappe.db.sql("UPDATE `tabStock Ledger Entry` SET is_cancelled = 1 where is_cancelled = 'Yes'")
-		frappe.db.sql("UPDATE `tabSerial No` SET is_cancelled = 1 where is_cancelled = 'Yes'")
+	for module, doctype in module_doctypes:
+		if (not frappe.db.has_column(doctype, "is_cancelled")
+			or frappe.db.get_column_type(doctype, "is_cancelled").lower() == "int(1)"
+		):
+			continue
 
-		frappe.reload_doc("stock", "doctype", "stock_ledger_entry")
-		frappe.reload_doc("stock", "doctype", "serial_no")
-	except Exception:
-		pass
+		frappe.db.sql("""
+				UPDATE `tab{doctype}`
+				SET is_cancelled = 0
+				where is_cancelled in ('', NULL, 'No')"""
+				.format(doctype=doctype))
+		frappe.db.sql("""
+				UPDATE `tab{doctype}`
+				SET is_cancelled = 1
+				where is_cancelled = 'Yes'"""
+				.format(doctype=doctype))
+
+		frappe.reload_doc(module, "doctype", frappe.scrub(doctype))
diff --git a/erpnext/patches/v13_0/add_bin_unique_constraint.py b/erpnext/patches/v13_0/add_bin_unique_constraint.py
new file mode 100644
index 0000000..57fbaae
--- /dev/null
+++ b/erpnext/patches/v13_0/add_bin_unique_constraint.py
@@ -0,0 +1,63 @@
+import frappe
+
+from erpnext.stock.stock_balance import (
+	get_balance_qty_from_sle,
+	get_indented_qty,
+	get_ordered_qty,
+	get_planned_qty,
+	get_reserved_qty,
+)
+from erpnext.stock.utils import get_bin
+
+
+def execute():
+	delete_broken_bins()
+	delete_and_patch_duplicate_bins()
+
+def delete_broken_bins():
+	# delete useless bins
+	frappe.db.sql("delete from `tabBin` where item_code is null or warehouse is null")
+
+def delete_and_patch_duplicate_bins():
+
+	duplicate_bins = frappe.db.sql("""
+		SELECT
+			item_code, warehouse, count(*) as bin_count
+		FROM
+			tabBin
+		GROUP BY
+			item_code, warehouse
+		HAVING
+			bin_count > 1
+	""", as_dict=1)
+
+	for duplicate_bin in duplicate_bins:
+		item_code = duplicate_bin.item_code
+		warehouse = duplicate_bin.warehouse
+		existing_bins = frappe.get_list("Bin",
+				filters={
+					"item_code": item_code,
+					"warehouse": warehouse
+					},
+				fields=["name"],
+				order_by="creation",)
+
+		# keep last one
+		existing_bins.pop()
+
+		for broken_bin in existing_bins:
+			frappe.delete_doc("Bin", broken_bin.name)
+
+		qty_dict = {
+			"reserved_qty": get_reserved_qty(item_code, warehouse),
+			"indented_qty": get_indented_qty(item_code, warehouse),
+			"ordered_qty": get_ordered_qty(item_code, warehouse),
+			"planned_qty": get_planned_qty(item_code, warehouse),
+			"actual_qty": get_balance_qty_from_sle(item_code, warehouse)
+		}
+
+		bin = get_bin(item_code, warehouse)
+		bin.update(qty_dict)
+		bin.update_reserved_qty_for_production()
+		bin.update_reserved_qty_for_sub_contracting()
+		bin.db_update()
diff --git a/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py b/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py
new file mode 100644
index 0000000..d3ee3f8
--- /dev/null
+++ b/erpnext/patches/v13_0/convert_to_website_item_in_item_card_group_template.py
@@ -0,0 +1,57 @@
+import json
+from typing import List, Union
+
+import frappe
+
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+
+
+def execute():
+    """
+        Convert all Item links to Website Item link values in
+        exisitng 'Item Card Group' Web Page Block data.
+    """
+    frappe.reload_doc("e_commerce", "web_template", "item_card_group")
+
+    blocks = frappe.db.get_all(
+        "Web Page Block",
+        filters={"web_template": "Item Card Group"},
+        fields=["parent", "web_template_values", "name"]
+    )
+
+    fields = generate_fields_to_edit()
+
+    for block in blocks:
+        web_template_value = json.loads(block.get('web_template_values'))
+
+        for field in fields:
+            item = web_template_value.get(field)
+            if not item:
+                continue
+
+            if frappe.db.exists("Website Item", {"item_code": item}):
+                website_item = frappe.db.get_value("Website Item", {"item_code": item})
+            else:
+                website_item = make_new_website_item(item)
+
+            if website_item:
+                web_template_value[field] = website_item
+
+        frappe.db.set_value("Web Page Block", block.name, "web_template_values", json.dumps(web_template_value))
+
+def generate_fields_to_edit() -> List:
+    fields = []
+    for i in range(1, 13):
+        fields.append(f"card_{i}_item") # fields like 'card_1_item', etc.
+
+    return fields
+
+def make_new_website_item(item: str) -> Union[str, None]:
+    try:
+        doc = frappe.get_doc("Item", item)
+        web_item = make_website_item(doc) # returns [website_item.name, item_name]
+        return web_item[0]
+    except Exception:
+        title = f"{item}: Error while converting to Website Item "
+        frappe.log_error(title + "for Item Card Group Template" + "\n\n" + frappe.get_traceback(), title=title)
+        return None
diff --git a/erpnext/patches/v13_0/create_website_items.py b/erpnext/patches/v13_0/create_website_items.py
new file mode 100644
index 0000000..da162a3
--- /dev/null
+++ b/erpnext/patches/v13_0/create_website_items.py
@@ -0,0 +1,72 @@
+import frappe
+
+from erpnext.e_commerce.doctype.website_item.website_item import make_website_item
+
+
+def execute():
+	frappe.reload_doc("e_commerce", "doctype", "website_item")
+	frappe.reload_doc("e_commerce", "doctype", "website_item_tabbed_section")
+	frappe.reload_doc("e_commerce", "doctype", "website_offer")
+	frappe.reload_doc("e_commerce", "doctype", "recommended_items")
+	frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings")
+	frappe.reload_doc("stock", "doctype", "item")
+
+	item_fields = ["item_code", "item_name", "item_group", "stock_uom", "brand", "image",
+		"has_variants", "variant_of", "description", "weightage"]
+	web_fields_to_map = ["route", "slideshow", "website_image_alt",
+		"website_warehouse", "web_long_description", "website_content", "thumbnail"]
+
+	# get all valid columns (fields) from Item master DB schema
+	item_table_fields = frappe.db.sql("desc `tabItem`", as_dict=1) # nosemgrep
+	item_table_fields = [d.get('Field') for d in item_table_fields]
+
+	# prepare fields to query from Item, check if the web field exists in Item master
+	web_query_fields = []
+	for web_field in web_fields_to_map:
+		if web_field in item_table_fields:
+			web_query_fields.append(web_field)
+			item_fields.append(web_field)
+
+	# check if the filter fields exist in Item master
+	or_filters = {}
+	for field in ["show_in_website", "show_variant_in_website"]:
+		if field in item_table_fields:
+			or_filters[field] = 1
+
+	if not web_query_fields or not or_filters:
+		# web fields to map are not present in Item master schema
+		# most likely a fresh installation that doesnt need this patch
+		return
+
+	items = frappe.db.get_all(
+		"Item",
+		fields=item_fields,
+		or_filters=or_filters
+	)
+	total_count = len(items)
+
+	for count, item in enumerate(items, start=1):
+		if frappe.db.exists("Website Item", {"item_code": item.item_code}):
+			continue
+
+		# make new website item from item (publish item)
+		website_item = make_website_item(item, save=False)
+		website_item.ranking = item.get("weightage")
+
+		for field in web_fields_to_map:
+			website_item.update({field: item.get(field)})
+
+		website_item.save()
+
+		# move Website Item Group & Website Specification table to Website Item
+		for doctype in ("Website Item Group", "Item Website Specification"):
+			frappe.db.set_value(
+				doctype,
+				{"parenttype": "Item", "parent": item.item_code}, # filters
+				{"parenttype": "Website Item", "parent": website_item.name} # value dict
+			)
+
+		if count % 20 == 0: # commit after every 20 items
+			frappe.db.commit()
+
+		frappe.utils.update_progress_bar('Creating Website Items', count, total_count)
diff --git a/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
new file mode 100644
index 0000000..75953b0
--- /dev/null
+++ b/erpnext/patches/v13_0/delete_bank_reconciliation_detail.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+
+import frappe
+
+
+def execute():
+
+	if frappe.db.exists('DocType', 'Bank Reconciliation Detail') and \
+		frappe.db.exists('DocType', 'Bank Clearance Detail'):
+
+		frappe.delete_doc("DocType", 'Bank Reconciliation Detail', force=1)
diff --git a/erpnext/patches/v13_0/delete_old_sales_reports.py b/erpnext/patches/v13_0/delete_old_sales_reports.py
index c597fe8..e6eba0a 100644
--- a/erpnext/patches/v13_0/delete_old_sales_reports.py
+++ b/erpnext/patches/v13_0/delete_old_sales_reports.py
@@ -12,6 +12,7 @@
 
 	for report in reports_to_delete:
 		if frappe.db.exists("Report", report):
+			delete_links_from_desktop_icons(report)
 			delete_auto_email_reports(report)
 			check_and_delete_linked_reports(report)
 
@@ -22,3 +23,9 @@
 	auto_email_reports = frappe.db.get_values("Auto Email Report", {"report": report}, ["name"])
 	for auto_email_report in auto_email_reports:
 		frappe.delete_doc("Auto Email Report", auto_email_report[0])
+
+def delete_links_from_desktop_icons(report):
+	""" Check for one or multiple Desktop Icons and delete """
+	desktop_icons = frappe.db.get_values("Desktop Icon", {"_report": report}, ["name"])
+	for desktop_icon in desktop_icons:
+		frappe.delete_doc("Desktop Icon", desktop_icon[0])
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py b/erpnext/patches/v13_0/einvoicing_deprecation_warning.py
deleted file mode 100644
index e123a55..0000000
--- a/erpnext/patches/v13_0/einvoicing_deprecation_warning.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import click
-
-
-def execute():
-	click.secho(
-		"Indian E-Invoicing integration is moved to a separate app and will be removed from ERPNext in version-14.\n"
-		"Please install the app to continue using the integration: https://github.com/frappe/erpnext_gst_compliance",
-		fg="yellow",
-	)
diff --git a/erpnext/patches/v13_0/enable_provisional_accounting.py b/erpnext/patches/v13_0/enable_provisional_accounting.py
new file mode 100644
index 0000000..8e22270
--- /dev/null
+++ b/erpnext/patches/v13_0/enable_provisional_accounting.py
@@ -0,0 +1,19 @@
+import frappe
+
+
+def execute():
+	frappe.reload_doc("setup", "doctype", "company")
+
+	company = frappe.qb.DocType("Company")
+
+	frappe.qb.update(
+		company
+	).set(
+		company.enable_provisional_accounting_for_non_stock_items, company.enable_perpetual_inventory_for_non_stock_items
+	).set(
+		company.default_provisional_account, company.service_received_but_not_billed
+	).where(
+		company.enable_perpetual_inventory_for_non_stock_items == 1
+	).where(
+		company.service_received_but_not_billed.isnotnull()
+	).run()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py
new file mode 100644
index 0000000..32ad542
--- /dev/null
+++ b/erpnext/patches/v13_0/fetch_thumbnail_in_website_items.py
@@ -0,0 +1,16 @@
+import frappe
+
+
+def execute():
+    if frappe.db.has_column("Item", "thumbnail"):
+        website_item = frappe.qb.DocType("Website Item").as_("wi")
+        item = frappe.qb.DocType("Item")
+
+        frappe.qb.update(website_item).inner_join(item).on(
+            website_item.item_code == item.item_code
+        ).set(
+            website_item.thumbnail, item.thumbnail
+        ).where(
+            website_item.website_image.notnull()
+            & website_item.thumbnail.isnull()
+        ).run()
diff --git a/erpnext/patches/v13_0/hospitality_deprecation_warning.py b/erpnext/patches/v13_0/hospitality_deprecation_warning.py
new file mode 100644
index 0000000..2708b2c
--- /dev/null
+++ b/erpnext/patches/v13_0/hospitality_deprecation_warning.py
@@ -0,0 +1,10 @@
+import click
+
+
+def execute():
+
+	click.secho(
+		"Hospitality domain is moved to a separate app and will be removed from ERPNext in version-14.\n"
+		"When upgrading to ERPNext version-14, please install the app to continue using the Hospitality domain: https://github.com/frappe/hospitality",
+		fg="yellow",
+	)
diff --git a/erpnext/patches/v13_0/make_homepage_products_website_items.py b/erpnext/patches/v13_0/make_homepage_products_website_items.py
new file mode 100644
index 0000000..7a7ddba
--- /dev/null
+++ b/erpnext/patches/v13_0/make_homepage_products_website_items.py
@@ -0,0 +1,15 @@
+import frappe
+
+
+def execute():
+	homepage = frappe.get_doc("Homepage")
+
+	for row in homepage.products:
+		web_item = frappe.db.get_value("Website Item", {"item_code": row.item_code}, "name")
+		if not web_item:
+			continue
+
+		row.item_code = web_item
+
+	homepage.flags.ignore_mandatory = True
+	homepage.save()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/make_non_standard_user_type.py b/erpnext/patches/v13_0/make_non_standard_user_type.py
index a7bdf93..ff241a3 100644
--- a/erpnext/patches/v13_0/make_non_standard_user_type.py
+++ b/erpnext/patches/v13_0/make_non_standard_user_type.py
@@ -10,8 +10,15 @@
 def execute():
 	doctype_dict = {
 		'projects': ['Timesheet'],
-		'payroll': ['Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission'],
-		'hr': ['Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request']
+		'payroll': [
+			'Salary Slip', 'Employee Tax Exemption Declaration', 'Employee Tax Exemption Proof Submission',
+			'Employee Benefit Application', 'Employee Benefit Claim'
+		],
+		'hr': [
+			'Employee', 'Expense Claim', 'Leave Application', 'Attendance Request', 'Compensatory Leave Request',
+			'Holiday List', 'Employee Advance', 'Training Program', 'Training Feedback',
+			'Shift Request', 'Employee Grievance', 'Employee Referral', 'Travel Request'
+		]
 	}
 
 	for module, doctypes in doctype_dict.items():
diff --git a/erpnext/patches/v13_0/non_profit_deprecation_warning.py b/erpnext/patches/v13_0/non_profit_deprecation_warning.py
new file mode 100644
index 0000000..5b54b25
--- /dev/null
+++ b/erpnext/patches/v13_0/non_profit_deprecation_warning.py
@@ -0,0 +1,10 @@
+import click
+
+
+def execute():
+
+	click.secho(
+		"Non Profit Domain is moved to a separate app and will be removed from ERPNext in version-14.\n"
+		"When upgrading to ERPNext version-14, please install the app to continue using the Non Profit domain: https://github.com/frappe/non_profit",
+		fg="yellow",
+	)
diff --git a/erpnext/patches/v13_0/populate_e_commerce_settings.py b/erpnext/patches/v13_0/populate_e_commerce_settings.py
new file mode 100644
index 0000000..8f9ee51
--- /dev/null
+++ b/erpnext/patches/v13_0/populate_e_commerce_settings.py
@@ -0,0 +1,62 @@
+import frappe
+from frappe.utils import cint
+
+
+def execute():
+	frappe.reload_doc("e_commerce", "doctype", "e_commerce_settings")
+	frappe.reload_doc("portal", "doctype", "website_filter_field")
+	frappe.reload_doc("portal", "doctype", "website_attribute")
+
+	products_settings_fields = [
+		"hide_variants", "products_per_page",
+		"enable_attribute_filters", "enable_field_filters"
+	]
+
+	shopping_cart_settings_fields = [
+		"enabled", "show_attachments", "show_price",
+		"show_stock_availability", "enable_variants", "show_contact_us_button",
+		"show_quantity_in_website", "show_apply_coupon_code_in_website",
+		"allow_items_not_in_stock", "company", "price_list", "default_customer_group",
+		"quotation_series", "enable_checkout", "payment_success_url",
+		"payment_gateway_account", "save_quotations_as_draft"
+	]
+
+	settings = frappe.get_doc("E Commerce Settings")
+
+	def map_into_e_commerce_settings(doctype, fields):
+		singles = frappe.qb.DocType("Singles")
+		query = (
+			frappe.qb.from_(singles)
+			.select(
+				singles["field"], singles.value
+			).where(
+				(singles.doctype == doctype)
+				& (singles["field"].isin(fields))
+			)
+		)
+		data = query.run(as_dict=True)
+
+		# {'enable_attribute_filters': '1', ...}
+		mapper = {row.field: row.value for row in data}
+
+		for key, value in mapper.items():
+			value = cint(value) if (value and value.isdigit()) else value
+			settings.update({key: value})
+
+		settings.save()
+
+	# shift data to E Commerce Settings
+	map_into_e_commerce_settings("Products Settings", products_settings_fields)
+	map_into_e_commerce_settings("Shopping Cart Settings", shopping_cart_settings_fields)
+
+	# move filters and attributes tables to E Commerce Settings from Products Settings
+	for doctype in ("Website Filter Field", "Website Attribute"):
+		frappe.db.set_value(
+			doctype,
+			{"parent": "Products Settings"},
+			{
+				"parenttype": "E Commerce Settings",
+				"parent": "E Commerce Settings"
+			},
+			update_modified=False
+		)
diff --git a/erpnext/patches/v13_0/remove_bad_selling_defaults.py b/erpnext/patches/v13_0/remove_bad_selling_defaults.py
index 5487a6c..0262539 100644
--- a/erpnext/patches/v13_0/remove_bad_selling_defaults.py
+++ b/erpnext/patches/v13_0/remove_bad_selling_defaults.py
@@ -3,6 +3,7 @@
 
 
 def execute():
+	frappe.reload_doctype('Selling Settings')
 	selling_settings = frappe.get_single("Selling Settings")
 
 	if selling_settings.customer_group in (_("All Customer Groups"), "All Customer Groups"):
diff --git a/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py b/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py
new file mode 100644
index 0000000..f097ab9
--- /dev/null
+++ b/erpnext/patches/v13_0/set_work_order_qty_in_so_from_mr.py
@@ -0,0 +1,36 @@
+import frappe
+
+
+def execute():
+    """
+    1. Get submitted Work Orders with MR, MR Item and SO set
+    2. Get SO Item detail from MR Item detail in WO, and set in WO
+    3. Update work_order_qty in SO
+    """
+    work_order = frappe.qb.DocType("Work Order")
+    query = (
+        frappe.qb.from_(work_order)
+        .select(
+            work_order.name, work_order.produced_qty,
+            work_order.material_request,
+            work_order.material_request_item,
+            work_order.sales_order
+        ).where(
+            (work_order.material_request.isnotnull())
+            & (work_order.material_request_item.isnotnull())
+            & (work_order.sales_order.isnotnull())
+            & (work_order.docstatus == 1)
+            & (work_order.produced_qty > 0)
+        )
+    )
+    results = query.run(as_dict=True)
+
+    for row in results:
+        so_item = frappe.get_value(
+            "Material Request Item", row.material_request_item, "sales_order_item"
+        )
+        frappe.db.set_value("Work Order", row.name, "sales_order_item", so_item)
+
+        if so_item:
+            wo = frappe.get_doc("Work Order", row.name)
+            wo.update_work_order_qty_in_so()
diff --git a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
index 7a2a253..2d35ea3 100644
--- a/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
+++ b/erpnext/patches/v13_0/setup_fields_for_80g_certificate_and_donation.py
@@ -5,6 +5,9 @@
 
 def execute():
 	if frappe.get_all('Company', filters = {'country': 'India'}):
+		frappe.reload_doc('accounts', 'doctype', 'POS Invoice')
+		frappe.reload_doc('accounts', 'doctype', 'POS Invoice Item')
+
 		make_custom_fields()
 
 		if not frappe.db.exists('Party Type', 'Donor'):
diff --git a/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py b/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py
new file mode 100644
index 0000000..35710a9
--- /dev/null
+++ b/erpnext/patches/v13_0/shopping_cart_to_ecommerce.py
@@ -0,0 +1,29 @@
+import click
+import frappe
+
+
+def execute():
+
+	frappe.delete_doc("DocType", "Shopping Cart Settings", ignore_missing=True)
+	frappe.delete_doc("DocType", "Products Settings", ignore_missing=True)
+	frappe.delete_doc("DocType", "Supplier Item Group", ignore_missing=True)
+
+	if frappe.db.get_single_value("E Commerce Settings", "enabled"):
+		notify_users()
+
+
+def notify_users():
+
+	click.secho(
+		"Shopping cart and Product settings are merged into E-commerce settings.\n"
+		"Checkout the documentation to learn more:"
+		"https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce",
+		fg="yellow",
+	)
+
+	note = frappe.new_doc("Note")
+	note.title = "New E-Commerce Module"
+	note.public = 1
+	note.notify_on_login = 1
+	note.content = """<div class="ql-editor read-mode"><p>You are seeing this message because Shopping Cart is enabled on your site. </p><p><br></p><p>Shopping Cart Settings and Products settings are now merged into "E Commerce Settings". </p><p><br></p><p>You can learn about new and improved E-Commerce features in the official documentation.</p><ol><li data-list="bullet"><span class="ql-ui" contenteditable="false"></span><a href="https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce" rel="noopener noreferrer">https://docs.erpnext.com/docs/v13/user/manual/en/e_commerce/set_up_e_commerce</a></li></ol><p><br></p></div>"""
+	note.insert(ignore_mandatory=True)
diff --git a/erpnext/patches/v13_0/update_accounts_in_loan_docs.py b/erpnext/patches/v13_0/update_accounts_in_loan_docs.py
new file mode 100644
index 0000000..440f912
--- /dev/null
+++ b/erpnext/patches/v13_0/update_accounts_in_loan_docs.py
@@ -0,0 +1,37 @@
+import frappe
+
+
+def execute():
+	ld = frappe.qb.DocType("Loan Disbursement").as_("ld")
+	lr = frappe.qb.DocType("Loan Repayment").as_("lr")
+	loan = frappe.qb.DocType("Loan")
+
+	frappe.qb.update(
+		ld
+	).inner_join(
+		loan
+	).on(
+		loan.name == ld.against_loan
+	).set(
+		ld.disbursement_account, loan.disbursement_account
+	).set(
+		ld.loan_account, loan.loan_account
+	).where(
+		ld.docstatus < 2
+	).run()
+
+	frappe.qb.update(
+		lr
+	).inner_join(
+		loan
+	).on(
+		loan.name == lr.against_loan
+	).set(
+		lr.payment_account, loan.payment_account
+	).set(
+		lr.loan_account, loan.loan_account
+	).set(
+		lr.penalty_income_account, loan.penalty_income_account
+	).where(
+		lr.docstatus < 2
+	).run()
diff --git a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
index 55fd465..60466eb 100644
--- a/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
+++ b/erpnext/patches/v13_0/update_actual_start_and_end_date_in_wo.py
@@ -37,4 +37,4 @@
 			jc.production_item = wo.production_item, jc.item_name = wo.item_name
 		WHERE
 			jc.work_order = wo.name and IFNULL(jc.production_item, "") = ""
-	""")
+	""")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_asset_quantity_field.py b/erpnext/patches/v13_0/update_asset_quantity_field.py
new file mode 100644
index 0000000..47884d1
--- /dev/null
+++ b/erpnext/patches/v13_0/update_asset_quantity_field.py
@@ -0,0 +1,8 @@
+import frappe
+
+
+def execute():
+	if frappe.db.count('Asset'):
+		frappe.reload_doc("assets", "doctype", "Asset")
+		asset = frappe.qb.DocType('Asset')
+		frappe.qb.update(asset).set(asset.asset_quantity, 1).run()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_disbursement_account.py b/erpnext/patches/v13_0/update_disbursement_account.py
new file mode 100644
index 0000000..c56fa8f
--- /dev/null
+++ b/erpnext/patches/v13_0/update_disbursement_account.py
@@ -0,0 +1,22 @@
+import frappe
+
+
+def execute():
+
+	frappe.reload_doc("loan_management", "doctype", "loan_type")
+	frappe.reload_doc("loan_management", "doctype", "loan")
+
+	loan_type = frappe.qb.DocType("Loan Type")
+	loan = frappe.qb.DocType("Loan")
+
+	frappe.qb.update(
+		loan_type
+	).set(
+		loan_type.disbursement_account, loan_type.payment_account
+	).run()
+
+	frappe.qb.update(
+		loan
+	).set(
+		loan.disbursement_account, loan.payment_account
+	).run()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_level_in_bom.py b/erpnext/patches/v13_0/update_level_in_bom.py
deleted file mode 100644
index 499412e..0000000
--- a/erpnext/patches/v13_0/update_level_in_bom.py
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (c) 2020, Frappe and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-
-def execute():
-	for document in ["bom", "bom_item", "bom_explosion_item"]:
-		frappe.reload_doc('manufacturing', 'doctype', document)
-
-	frappe.db.sql(" update `tabBOM` set bom_level = 0 where docstatus = 1")
-
-	bom_list = frappe.db.sql_list("""select name from `tabBOM` bom
-		where docstatus=1 and is_active=1 and not exists(select bom_no from `tabBOM Item`
-		where parent=bom.name and ifnull(bom_no, '')!='')""")
-
-	count = 0
-	while(count < len(bom_list)):
-		for parent_bom in get_parent_boms(bom_list[count]):
-			bom_doc = frappe.get_cached_doc("BOM", parent_bom)
-			bom_doc.set_bom_level(update=True)
-			bom_list.append(parent_bom)
-		count += 1
-
-def get_parent_boms(bom_no):
-	return frappe.db.sql_list("""
-		select distinct bom_item.parent from `tabBOM Item` bom_item
-		where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
-			and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
-	""", bom_no)
diff --git a/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
new file mode 100644
index 0000000..7a8c1c6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_maintenance_schedule_field_in_visit.py
@@ -0,0 +1,25 @@
+
+import frappe
+
+
+def execute():
+	frappe.reload_doctype('Maintenance Visit')
+	frappe.reload_doctype('Maintenance Visit Purpose')
+
+	# Updates the Maintenance Schedule link to fetch serial nos
+	from frappe.query_builder.functions import Coalesce
+	mvp = frappe.qb.DocType('Maintenance Visit Purpose')
+	mv = frappe.qb.DocType('Maintenance Visit')
+
+	frappe.qb.update(
+		mv
+	).join(
+		mvp
+	).on(mvp.parent == mv.name).set(
+		mv.maintenance_schedule,
+		Coalesce(mvp.prevdoc_docname, '')
+	).where(
+		(mv.maintenance_type == "Scheduled")
+		& (mvp.prevdoc_docname.notnull())
+		& (mv.docstatus < 2)
+	).run(as_dict=1)
diff --git a/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py b/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py
new file mode 100644
index 0000000..00926b0
--- /dev/null
+++ b/erpnext/patches/v13_0/update_reserved_qty_closed_wo.py
@@ -0,0 +1,28 @@
+import frappe
+
+from erpnext.stock.utils import get_bin
+
+
+def execute():
+
+	wo = frappe.qb.DocType("Work Order")
+	wo_item = frappe.qb.DocType("Work Order Item")
+
+	incorrect_item_wh = (
+		frappe.qb
+			.from_(wo)
+			.join(wo_item).on(wo.name == wo_item.parent)
+			.select(wo_item.item_code, wo.source_warehouse).distinct()
+			.where(
+				(wo.status == "Closed")
+				& (wo.docstatus == 1)
+				& (wo.source_warehouse.notnull())
+			)
+	).run()
+
+	for item_code, warehouse in incorrect_item_wh:
+		if not (item_code and warehouse):
+			continue
+
+		bin = get_bin(item_code, warehouse)
+		bin.update_reserved_qty_for_production()
diff --git a/erpnext/patches/v13_0/update_sane_transfer_against.py b/erpnext/patches/v13_0/update_sane_transfer_against.py
new file mode 100644
index 0000000..a163d38
--- /dev/null
+++ b/erpnext/patches/v13_0/update_sane_transfer_against.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+	bom = frappe.qb.DocType("BOM")
+
+	(frappe.qb
+		.update(bom)
+		.set(bom.transfer_material_against, "Work Order")
+		.where(bom.with_operations == 0)
+	).run()
diff --git a/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
new file mode 100644
index 0000000..e43a8ba
--- /dev/null
+++ b/erpnext/patches/v13_0/wipe_serial_no_field_for_0_qty.py
@@ -0,0 +1,18 @@
+import frappe
+
+
+def execute():
+
+	doctype = "Stock Reconciliation Item"
+
+	if not frappe.db.has_column(doctype, "current_serial_no"):
+		# nothing to fix if column doesn't exist
+		return
+
+	sr_item = frappe.qb.DocType(doctype)
+
+	(frappe.qb
+		.update(sr_item)
+		.set(sr_item.current_serial_no, None)
+		.where(sr_item.current_qty == 0)
+	).run()
diff --git a/erpnext/patches/v14_0/__init__.py b/erpnext/patches/v14_0/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/patches/v14_0/__init__.py
+++ /dev/null
diff --git a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
index 120182a..2a8b6ef 100644
--- a/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
+++ b/erpnext/patches/v14_0/add_default_exit_questionnaire_notification_template.py
@@ -5,9 +5,6 @@
 
 
 def execute():
-	frappe.reload_doc("email", "doctype", "email_template")
-	frappe.reload_doc("hr", "doctype", "hr_settings")
-
 	template = frappe.db.exists("Email Template", _("Exit Questionnaire Notification"))
 	if not template:
 		base_path = frappe.get_app_path("erpnext", "hr", "doctype")
diff --git a/erpnext/patches/v14_0/delete_amazon_mws_doctype.py b/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
new file mode 100644
index 0000000..525da6c
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_amazon_mws_doctype.py
@@ -0,0 +1,5 @@
+import frappe
+
+
+def execute():
+	frappe.delete_doc("DocType", "Amazon MWS Settings", ignore_missing=True)
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py b/erpnext/patches/v14_0/delete_einvoicing_doctypes.py
deleted file mode 100644
index a3a8149..0000000
--- a/erpnext/patches/v14_0/delete_einvoicing_doctypes.py
+++ /dev/null
@@ -1,10 +0,0 @@
-import frappe
-
-
-def execute():
-	frappe.delete_doc('DocType', 'E Invoice Settings', ignore_missing=True)
-	frappe.delete_doc('DocType', 'E Invoice User', ignore_missing=True)
-	frappe.delete_doc('Report', 'E-Invoice Summary', ignore_missing=True)
-	frappe.delete_doc('Print Format', 'GST E-Invoice', ignore_missing=True)
-	frappe.delete_doc('Custom Field', 'Sales Invoice-eway_bill_cancelled', ignore_missing=True)
-	frappe.delete_doc('Custom Field', 'Sales Invoice-irn_cancelled', ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_hospitality_doctypes.py b/erpnext/patches/v14_0/delete_hospitality_doctypes.py
new file mode 100644
index 0000000..d0216f8
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_hospitality_doctypes.py
@@ -0,0 +1,32 @@
+import frappe
+
+
+def execute():
+	modules = ['Hotels', 'Restaurant']
+
+	for module in modules:
+		frappe.delete_doc("Module Def", module, ignore_missing=True, force=True)
+
+		frappe.delete_doc("Workspace", module, ignore_missing=True, force=True)
+
+		reports = frappe.get_all("Report", {"module": module, "is_standard": "Yes"}, pluck='name')
+		for report in reports:
+			frappe.delete_doc("Report", report, ignore_missing=True, force=True)
+
+		dashboards = frappe.get_all("Dashboard", {"module": module, "is_standard": 1}, pluck='name')
+		for dashboard in dashboards:
+			frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
+
+		doctypes = frappe.get_all("DocType", {"module": module, "custom": 0}, pluck='name')
+		for doctype in doctypes:
+			frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+	custom_fields = [
+		{"dt": "Sales Invoice", "fieldname": "restaurant"},
+		{"dt": "Sales Invoice", "fieldname": "restaurant_table"},
+		{"dt": "Price List", "fieldname": "restaurant_menu"},
+	]
+
+	for field in custom_fields:
+		custom_field = frappe.db.get_value("Custom Field", field)
+		frappe.delete_doc("Custom Field", custom_field, ignore_missing=True)
diff --git a/erpnext/patches/v14_0/delete_non_profit_doctypes.py b/erpnext/patches/v14_0/delete_non_profit_doctypes.py
new file mode 100644
index 0000000..565b10c
--- /dev/null
+++ b/erpnext/patches/v14_0/delete_non_profit_doctypes.py
@@ -0,0 +1,63 @@
+import frappe
+
+
+def execute():
+	frappe.delete_doc("Module Def", "Non Profit", ignore_missing=True, force=True)
+
+	frappe.delete_doc("Workspace", "Non Profit", ignore_missing=True, force=True)
+
+	print_formats = frappe.get_all("Print Format", {"module": "Non Profit", "standard": "Yes"}, pluck='name')
+	for print_format in print_formats:
+		frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True)
+
+	print_formats = ['80G Certificate for Membership', '80G Certificate for Donation']
+	for print_format in print_formats:
+		frappe.delete_doc("Print Format", print_format, ignore_missing=True, force=True)
+
+	reports = frappe.get_all("Report", {"module": "Non Profit", "is_standard": "Yes"}, pluck='name')
+	for report in reports:
+		frappe.delete_doc("Report", report, ignore_missing=True, force=True)
+
+	dashboards = frappe.get_all("Dashboard", {"module": "Non Profit", "is_standard": 1}, pluck='name')
+	for dashboard in dashboards:
+		frappe.delete_doc("Dashboard", dashboard, ignore_missing=True, force=True)
+
+	doctypes = frappe.get_all("DocType", {"module": "Non Profit", "custom": 0}, pluck='name')
+	for doctype in doctypes:
+		frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+	doctypes = ['Tax Exemption 80G Certificate', 'Tax Exemption 80G Certificate Detail']
+	for doctype in doctypes:
+		frappe.delete_doc("DocType", doctype, ignore_missing=True)
+
+	forms = ['grant-application', 'certification-application', 'certification-application-usd']
+	for form in forms:
+		frappe.delete_doc("Web Form", form, ignore_missing=True, force=True)
+
+	custom_records = [
+		{"doctype": "Party Type", "name": "Member"},
+		{"doctype": "Party Type", "name": "Donor"},
+	]
+	for record in custom_records:
+		try:
+			frappe.delete_doc(record['doctype'], record['name'], ignore_missing=True)
+		except frappe.LinkExistsError:
+			pass
+
+	custom_fields = {
+		'Member': ['pan_number'],
+		'Donor': ['pan_number'],
+		'Company': [
+			'non_profit_section', 'company_80g_number', 'with_effect_from',
+			'non_profit_column_break', 'pan_details'
+		],
+	}
+
+	for doc, fields in custom_fields.items():
+		filters = {
+			'dt': doc,
+			'fieldname': ['in', fields]
+		}
+		records = frappe.get_all('Custom Field', filters=filters, pluck='name')
+		for record in records:
+			frappe.delete_doc('Custom Field', record, ignore_missing=True, force=True)
diff --git a/erpnext/patches/v14_0/migrate_cost_center_allocations.py b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
new file mode 100644
index 0000000..c4f097f
--- /dev/null
+++ b/erpnext/patches/v14_0/migrate_cost_center_allocations.py
@@ -0,0 +1,48 @@
+import frappe
+from frappe.utils import today
+
+
+def execute():
+	for dt in ("cost_center_allocation", "cost_center_allocation_percentage"):
+		frappe.reload_doc('accounts', 'doctype', dt)
+
+	cc_allocations = get_existing_cost_center_allocations()
+	if cc_allocations:
+		create_new_cost_center_allocation_records(cc_allocations)
+
+	frappe.delete_doc('DocType', 'Distributed Cost Center', ignore_missing=True)
+
+def create_new_cost_center_allocation_records(cc_allocations):
+	for main_cc, allocations in cc_allocations.items():
+		cca = frappe.new_doc("Cost Center Allocation")
+		cca.main_cost_center = main_cc
+		cca.valid_from = today()
+
+		for child_cc, percentage in allocations.items():
+			cca.append("allocation_percentages", ({
+				"cost_center": child_cc,
+				"percentage": percentage
+			}))
+		cca.save()
+		cca.submit()
+
+def get_existing_cost_center_allocations():
+	if not frappe.db.exists("DocType", "Distributed Cost Center"):
+		return
+
+	par = frappe.qb.DocType("Cost Center")
+	child = frappe.qb.DocType("Distributed Cost Center")
+
+	records = (
+		frappe.qb.from_(par)
+		.inner_join(child).on(par.name == child.parent)
+		.select(par.name, child.cost_center, child.percentage_allocation)
+		.where(par.enable_distributed_cost_center == 1)
+	).run(as_dict=True)
+
+	cc_allocations = frappe._dict()
+	for d in records:
+		cc_allocations.setdefault(d.name, frappe._dict())\
+			.setdefault(d.cost_center, d.percentage_allocation)
+
+	return cc_allocations
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/migrate_crm_settings.py b/erpnext/patches/v14_0/migrate_crm_settings.py
index 30d3ea0..0c77853 100644
--- a/erpnext/patches/v14_0/migrate_crm_settings.py
+++ b/erpnext/patches/v14_0/migrate_crm_settings.py
@@ -9,8 +9,9 @@
 	], as_dict=True)
 
 	frappe.reload_doc('crm', 'doctype', 'crm_settings')
-	frappe.db.set_value('CRM Settings', 'CRM Settings', {
-		'campaign_naming_by': settings.campaign_naming_by,
-		'close_opportunity_after_days': settings.close_opportunity_after_days,
-		'default_valid_till': settings.default_valid_till
-	})
+	if settings:
+		frappe.db.set_value('CRM Settings', 'CRM Settings', {
+			'campaign_naming_by': settings.campaign_naming_by,
+			'close_opportunity_after_days': settings.close_opportunity_after_days,
+			'default_valid_till': settings.default_valid_till
+		})
diff --git a/erpnext/patches/v14_0/rearrange_company_fields.py b/erpnext/patches/v14_0/rearrange_company_fields.py
index dd953ff..fd7eb7f 100644
--- a/erpnext/patches/v14_0/rearrange_company_fields.py
+++ b/erpnext/patches/v14_0/rearrange_company_fields.py
@@ -1,10 +1,7 @@
-import frappe
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 
 
 def execute():
-	frappe.reload_doc('setup', 'doctype', 'company')
-
 	custom_fields = {
 		'Company': [
 			dict(fieldname='hra_section', label='HRA Settings',
@@ -28,4 +25,4 @@
 		]
 	}
 
-	create_custom_fields(custom_fields, update=True)
\ No newline at end of file
+	create_custom_fields(custom_fields, update=True)
diff --git a/erpnext/patches/v14_0/restore_einvoice_fields.py b/erpnext/patches/v14_0/restore_einvoice_fields.py
new file mode 100644
index 0000000..c4431fb
--- /dev/null
+++ b/erpnext/patches/v14_0/restore_einvoice_fields.py
@@ -0,0 +1,24 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+from erpnext.regional.india.setup import add_permissions, add_print_formats
+
+
+def execute():
+	# restores back the 2 custom fields that was deleted while removing e-invoicing from v14
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+	add_permissions()
+	add_print_formats()
diff --git a/erpnext/patches/v14_0/update_batch_valuation_flag.py b/erpnext/patches/v14_0/update_batch_valuation_flag.py
new file mode 100644
index 0000000..55c8c48
--- /dev/null
+++ b/erpnext/patches/v14_0/update_batch_valuation_flag.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+	"""
+	- Don't use batchwise valuation for existing batches.
+	- Only batches created after this patch shoule use it.
+	"""
+
+	batch = frappe.qb.DocType("Batch")
+	frappe.qb.update(batch).set(batch.use_batchwise_valuation, 0).run()
diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
index 1307147..75049a6 100644
--- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py
+++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
@@ -6,9 +6,6 @@
 
 
 def execute():
-	frappe.reload_doc('crm', 'doctype', 'opportunity')
-	frappe.reload_doc('crm', 'doctype', 'opportunity_item')
-
 	opportunities = frappe.db.get_list('Opportunity', filters={
 		'opportunity_amount': ['>', 0]
 	}, fields=['name', 'company', 'currency', 'opportunity_amount'])
@@ -20,15 +17,11 @@
 		if opportunity.currency != company_currency:
 			conversion_rate = get_exchange_rate(opportunity.currency, company_currency)
 			base_opportunity_amount = flt(conversion_rate) * flt(opportunity.opportunity_amount)
-			grand_total = flt(opportunity.opportunity_amount)
-			base_grand_total = flt(conversion_rate) * flt(opportunity.opportunity_amount)
 		else:
 			conversion_rate = 1
-			base_opportunity_amount = grand_total = base_grand_total = flt(opportunity.opportunity_amount)
+			base_opportunity_amount = flt(opportunity.opportunity_amount)
 
 		frappe.db.set_value('Opportunity', opportunity.name, {
 			'conversion_rate': conversion_rate,
-			'base_opportunity_amount': base_opportunity_amount,
-			'grand_total': grand_total,
-			'base_grand_total': base_grand_total
+			'base_opportunity_amount': base_opportunity_amount
 		}, update_modified=False)
diff --git a/erpnext/patches/v4_2/repost_reserved_qty.py b/erpnext/patches/v4_2/repost_reserved_qty.py
index c2ca9be..ed4b19d 100644
--- a/erpnext/patches/v4_2/repost_reserved_qty.py
+++ b/erpnext/patches/v4_2/repost_reserved_qty.py
@@ -29,9 +29,11 @@
 	""")
 
 	for item_code, warehouse in repost_for:
-			update_bin_qty(item_code, warehouse, {
-				"reserved_qty": get_reserved_qty(item_code, warehouse)
-			})
+		if not (item_code and warehouse):
+			continue
+		update_bin_qty(item_code, warehouse, {
+			"reserved_qty": get_reserved_qty(item_code, warehouse)
+		})
 
 	frappe.db.sql("""delete from tabBin
 		where exists(
diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
index 42b0b04..dd79410 100644
--- a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
+++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
@@ -14,6 +14,8 @@
 		union
 		select item_code, warehouse from `tabStock Ledger Entry`) a"""):
 			try:
+				if not (item_code and warehouse):
+					continue
 				count += 1
 				update_bin_qty(item_code, warehouse, {
 					"indented_qty": get_indented_qty(item_code, warehouse),
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index d9efe45..9c897a7 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -204,10 +204,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-05-26 11:10:00.812698",
+ "modified": "2022-01-19 12:56:51.765353",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Additional Salary",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -239,8 +240,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
- "title_field": "employee",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index 8332697..2e4b64e 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -147,10 +147,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:35:08.940087",
+ "modified": "2022-01-19 12:58:31.664468",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Application",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -212,8 +213,10 @@
   }
  ],
  "quick_entry": 1,
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
index b3bac01..5deb0a5 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -144,10 +144,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:37:21.024625",
+ "modified": "2022-01-19 12:59:15.699118",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Claim",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -208,8 +209,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
index 0d10b2c..64fb8c5 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -94,10 +94,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:38:20.332316",
+ "modified": "2022-01-19 12:52:19.850710",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Incentive",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -136,8 +137,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_other_income/employee_other_income.json b/erpnext/payroll/doctype/employee_other_income/employee_other_income.json
index 14f63e4..04ce9f7 100644
--- a/erpnext/payroll/doctype/employee_other_income/employee_other_income.json
+++ b/erpnext/payroll/doctype/employee_other_income/employee_other_income.json
@@ -76,10 +76,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-22 22:55:17.604688",
+ "modified": "2022-01-19 12:58:43.255900",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Other Income",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -129,7 +130,10 @@
   }
  ],
  "quick_entry": 1,
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index b247d26..5ef373e 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -119,10 +119,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:39:59.237361",
+ "modified": "2022-01-19 12:58:54.707871",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Declaration",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -186,7 +187,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index 77b107e..bb90051 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -142,10 +142,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:41:13.723339",
+ "modified": "2022-01-19 12:58:24.244546",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Proof Submission",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -209,7 +210,10 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.js b/erpnext/payroll/doctype/gratuity/gratuity.js
index d4f7c9c..3d69c46 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.js
+++ b/erpnext/payroll/doctype/gratuity/gratuity.js
@@ -3,6 +3,14 @@
 
 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: {
@@ -24,7 +32,7 @@
 		});
 	},
 	refresh: function (frm) {
-		if (frm.doc.docstatus == 1 && frm.doc.status == "Unpaid") {
+		if (frm.doc.docstatus == 1 && !frm.doc.pay_via_salary_slip && 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 48a9ce4..1fd1cec 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.json
+++ b/erpnext/payroll/doctype/gratuity/gratuity.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "autoname": "HR-GRA-PAY-.#####",
- "creation": "2020-08-05 20:52:13.024683",
+ "creation": "2022-01-27 16:24:28.200061",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -16,6 +16,9 @@
   "company",
   "gratuity_rule",
   "section_break_5",
+  "pay_via_salary_slip",
+  "payroll_date",
+  "salary_component",
   "payable_account",
   "expense_account",
   "mode_of_payment",
@@ -78,18 +81,20 @@
    "reqd": 1
   },
   {
+   "depends_on": "eval: !doc.pay_via_salary_slip",
    "fieldname": "expense_account",
    "fieldtype": "Link",
    "label": "Expense Account",
-   "options": "Account",
-   "reqd": 1
+   "mandatory_depends_on": "eval: !doc.pay_via_salary_slip",
+   "options": "Account"
   },
   {
+   "depends_on": "eval: !doc.pay_via_salary_slip",
    "fieldname": "mode_of_payment",
    "fieldtype": "Link",
    "label": "Mode of Payment",
-   "options": "Mode of Payment",
-   "reqd": 1
+   "mandatory_depends_on": "eval: !doc.pay_via_salary_slip",
+   "options": "Mode of Payment"
   },
   {
    "fieldname": "gratuity_rule",
@@ -151,26 +156,49 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval: !doc.pay_via_salary_slip",
    "fieldname": "payable_account",
    "fieldtype": "Link",
    "label": "Payable Account",
-   "options": "Account",
-   "reqd": 1
+   "mandatory_depends_on": "eval: !doc.pay_via_salary_slip",
+   "options": "Account"
   },
   {
    "fieldname": "cost_center",
    "fieldtype": "Link",
    "label": "Cost Center",
    "options": "Cost Center"
+  },
+  {
+   "default": "1",
+   "fieldname": "pay_via_salary_slip",
+   "fieldtype": "Check",
+   "label": "Pay via Salary Slip"
+  },
+  {
+   "depends_on": "pay_via_salary_slip",
+   "fieldname": "payroll_date",
+   "fieldtype": "Date",
+   "label": "Payroll Date",
+   "mandatory_depends_on": "pay_via_salary_slip"
+  },
+  {
+   "depends_on": "pay_via_salary_slip",
+   "fieldname": "salary_component",
+   "fieldtype": "Link",
+   "label": "Salary Component",
+   "mandatory_depends_on": "pay_via_salary_slip",
+   "options": "Salary Component"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-07-02 15:05:57.396398",
+ "modified": "2022-02-02 14:00:45.536152",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Gratuity",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -198,6 +226,9 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name"
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/gratuity/gratuity.py b/erpnext/payroll/doctype/gratuity/gratuity.py
index 476990a..939634a 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity.py
@@ -21,7 +21,10 @@
 			self.status = "Unpaid"
 
 	def on_submit(self):
-		self.create_gl_entries()
+		if self.pay_via_salary_slip:
+			self.create_additional_salary()
+		else:
+			self.create_gl_entries()
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ['GL Entry']
@@ -64,6 +67,19 @@
 
 		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
diff --git a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
index aeadba1..771a6fe 100644
--- a/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
+++ b/erpnext/payroll/doctype/gratuity/gratuity_dashboard.py
@@ -10,7 +10,7 @@
 		'transactions': [
 			{
 				'label': _('Payment'),
-				'items': ['Payment Entry']
+				'items': ['Payment Entry', 'Additional Salary']
 			}
 		]
 	}
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index 93cba06..90e8061 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -18,27 +18,25 @@
 
 test_dependencies = ["Salary Component", "Salary Slip", "Account"]
 class TestGratuity(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
+	def setUp(self):
+		frappe.db.delete("Gratuity")
+		frappe.db.delete("Additional Salary", {"ref_doctype": "Gratuity"})
+
 		make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
 		make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
 
-	def setUp(self):
-		frappe.db.sql("DELETE FROM `tabGratuity`")
-
 	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):
+	def test_check_gratuity_amount_based_on_current_slab_and_additional_salary_creation(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
+		# work experience calculation
 		date_of_joining, relieving_date = frappe.db.get_value('Employee', employee, ['date_of_joining', 'relieving_date'])
 		employee_total_workings_days = (get_datetime(relieving_date) - get_datetime(date_of_joining)).days
 
@@ -64,6 +62,9 @@
 
 		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)")
@@ -117,8 +118,8 @@
 		self.assertEqual(flt(gratuity.paid_amount,2), flt(gratuity.amount, 2))
 
 	def tearDown(self):
-		frappe.db.sql("DELETE FROM `tabGratuity`")
-		frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
+		frappe.db.rollback()
+
 
 def get_gratuity_rule(name):
 	rule = frappe.db.exists("Gratuity Rule", name)
@@ -141,9 +142,14 @@
 	gratuity.employee = args.employee
 	gratuity.posting_date = getdate()
 	gratuity.gratuity_rule = args.rule or "Rule Under Limited Contract (UAE)"
-	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.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.save()
 	gratuity.submit()
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index db88c06..a634dfe 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -527,11 +527,12 @@
 		""" % cond, {"sal_struct": tuple(sal_struct), "from_date": end_date, "payroll_payable_account": payroll_payable_account}, as_dict=True)
 
 def remove_payrolled_employees(emp_list, start_date, end_date):
+	new_emp_list = []
 	for employee_details in emp_list:
-		if frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}):
-			emp_list.remove(employee_details)
+		if not frappe.db.exists("Salary Slip", {"employee": employee_details.employee, "start_date": start_date, "end_date": end_date, "docstatus": 1}):
+			new_emp_list.append(employee_details)
 
-	return emp_list
+	return new_emp_list
 
 @frappe.whitelist()
 def get_start_end_dates(payroll_frequency, start_date=None, company=None):
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index 4f097fa..3b7f4b2 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -124,7 +124,7 @@
 
 		if not frappe.db.exists("Account", "_Test Payroll Payable - _TC"):
 				create_account(account_name="_Test Payroll Payable",
-					company="_Test Company", parent_account="Current Liabilities - _TC")
+					company="_Test Company", parent_account="Current Liabilities - _TC", account_type="Payable")
 
 		if not frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") or \
 			frappe.db.get_value("Company", "_Test Company", "default_payroll_payable_account") != "_Test Payroll Payable - _TC":
@@ -214,6 +214,7 @@
 			create_loan_type("Car Loan", 500000, 8.4,
 				is_term_loan=1,
 				mode_of_payment='Cash',
+				disbursement_account='Disbursement Account - _TC',
 				payment_account='Payment Account - _TC',
 				loan_account='Loan Account - _TC',
 				interest_income_account='Interest Income Account - _TC',
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
index 7ea6210..f8d8bb4 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -105,10 +105,11 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 22:43:28.363644",
+ "modified": "2022-01-19 12:57:37.898953",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Retention Bonus",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -163,7 +164,10 @@
    "share": 1
   }
  ],
+ "search_fields": "employee_name",
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
+ "title_field": "employee_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 4e40e13..fe8e22c 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -637,7 +637,7 @@
  "idx": 9,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-12-23 11:47:47.098248",
+ "modified": "2022-01-19 12:45:54.999345",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Slip",
@@ -673,9 +673,11 @@
    "role": "Employee"
   }
  ],
+ "search_fields": "employee_name",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "employee",
  "title_field": "employee_name"
-}
+}
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index f33443d..d2a3998 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -746,11 +746,12 @@
 		previous_total_paid_taxes = self.get_tax_paid_in_period(payroll_period.start_date, self.start_date, tax_component)
 
 		# get taxable_earnings for current period (all days)
-		current_taxable_earnings = self.get_taxable_earnings(tax_slab.allow_tax_exemption)
+		current_taxable_earnings = self.get_taxable_earnings(tax_slab.allow_tax_exemption, payroll_period=payroll_period)
 		future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (math.ceil(remaining_sub_periods) - 1)
 
 		# get taxable_earnings, addition_earnings for current actual payment days
-		current_taxable_earnings_for_payment_days = self.get_taxable_earnings(tax_slab.allow_tax_exemption, based_on_payment_days=1)
+		current_taxable_earnings_for_payment_days = self.get_taxable_earnings(tax_slab.allow_tax_exemption,
+			based_on_payment_days=1, payroll_period=payroll_period)
 		current_structured_taxable_earnings = current_taxable_earnings_for_payment_days.taxable_earnings
 		current_additional_earnings = current_taxable_earnings_for_payment_days.additional_income
 		current_additional_earnings_with_full_tax = current_taxable_earnings_for_payment_days.additional_income_with_full_tax
@@ -876,7 +877,7 @@
 
 		return total_tax_paid
 
-	def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0):
+	def get_taxable_earnings(self, allow_tax_exemption=False, based_on_payment_days=0, payroll_period=None):
 		joining_date, relieving_date = self.get_joining_and_relieving_dates()
 
 		taxable_earnings = 0
@@ -903,7 +904,7 @@
 					# Get additional amount based on future recurring additional salary
 					if additional_amount and earning.is_recurring_additional_salary:
 						additional_income += self.get_future_recurring_additional_amount(earning.additional_salary,
-							earning.additional_amount) # Used earning.additional_amount to consider the amount for the full month
+							earning.additional_amount, payroll_period) # Used earning.additional_amount to consider the amount for the full month
 
 					if earning.deduct_full_tax_on_selected_payroll_date:
 						additional_income_with_full_tax += additional_amount
@@ -920,7 +921,7 @@
 
 					if additional_amount and ded.is_recurring_additional_salary:
 						additional_income -= self.get_future_recurring_additional_amount(ded.additional_salary,
-							ded.additional_amount) # Used ded.additional_amount to consider the amount for the full month
+							ded.additional_amount, payroll_period) # Used ded.additional_amount to consider the amount for the full month
 
 		return frappe._dict({
 			"taxable_earnings": taxable_earnings,
@@ -929,12 +930,18 @@
 			"flexi_benefits": flexi_benefits
 		})
 
-	def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount):
+	def get_future_recurring_additional_amount(self, additional_salary, monthly_additional_amount, payroll_period):
 		future_recurring_additional_amount = 0
 		to_date = frappe.db.get_value("Additional Salary", additional_salary, 'to_date')
 
 		# future month count excluding current
 		from_date, to_date = getdate(self.start_date), getdate(to_date)
+
+		# If recurring period end date is beyond the payroll period,
+		# last day of payroll period should be considered for recurring period calculation
+		if getdate(to_date) > getdate(payroll_period.end_date):
+			to_date = getdate(payroll_period.end_date)
+
 		future_recurring_period = ((to_date.year - from_date.year) * 12) + (to_date.month - from_date.month)
 
 		if future_recurring_period > 0:
@@ -1261,7 +1268,7 @@
 			for i, earning in enumerate(self.earnings):
 				if earning.salary_component == salary_component:
 					self.earnings[i].amount = wages_amount
-				self.gross_pay += self.earnings[i].amount
+				self.gross_pay += flt(self.earnings[i].amount, earning.precision("amount"))
 		self.net_pay = flt(self.gross_pay) - flt(self.total_deduction)
 
 	def compute_year_to_date(self):
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index c0e005a..6a5debf 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -6,6 +6,7 @@
 import unittest
 
 import frappe
+from frappe.model.document import Document
 from frappe.utils import (
 	add_days,
 	add_months,
@@ -147,7 +148,7 @@
 		# Payroll based on attendance
 		frappe.db.set_value("Payroll Settings", None, "payroll_based_on", "Attendance")
 
-		emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company")
+		emp = make_employee("test_employee_timesheet@salary.com", company="_Test Company", holiday_list="Salary Slip Test Holiday List")
 		frappe.db.set_value("Employee", emp, {"relieving_date": None, "status": "Active"})
 
 		# mark attendance
@@ -370,6 +371,7 @@
 		create_loan_type("Car Loan", 500000, 8.4,
 			is_term_loan=1,
 			mode_of_payment='Cash',
+			disbursement_account='Disbursement Account - _TC',
 			payment_account='Payment Account - _TC',
 			loan_account='Loan Account - _TC',
 			interest_income_account='Interest Income Account - _TC',
@@ -686,20 +688,25 @@
 
 def make_salary_component(salary_components, test_tax, company_list=None):
 	for salary_component in salary_components:
-		if not frappe.db.exists('Salary Component', salary_component["salary_component"]):
-			if test_tax:
-				if salary_component["type"] == "Earning":
-					salary_component["is_tax_applicable"] = 1
-				elif salary_component["salary_component"] == "TDS":
-					salary_component["variable_based_on_taxable_salary"] = 1
-					salary_component["amount_based_on_formula"] = 0
-					salary_component["amount"] = 0
-					salary_component["formula"] = ""
-					salary_component["condition"] = ""
-			salary_component["doctype"] = "Salary Component"
-			salary_component["salary_component_abbr"] = salary_component["abbr"]
-			frappe.get_doc(salary_component).insert()
-		get_salary_component_account(salary_component["salary_component"], company_list)
+		if frappe.db.exists('Salary Component', salary_component["salary_component"]):
+			continue
+
+		if test_tax:
+			if salary_component["type"] == "Earning":
+				salary_component["is_tax_applicable"] = 1
+			elif salary_component["salary_component"] == "TDS":
+				salary_component["variable_based_on_taxable_salary"] = 1
+				salary_component["amount_based_on_formula"] = 0
+				salary_component["amount"] = 0
+				salary_component["formula"] = ""
+				salary_component["condition"] = ""
+
+		salary_component["salary_component_abbr"] = salary_component["abbr"]
+		doc = frappe.new_doc("Salary Component")
+		doc.update(salary_component)
+		doc.insert()
+
+		get_salary_component_account(doc, company_list)
 
 def get_salary_component_account(sal_comp, company_list=None):
 	company = erpnext.get_default_company()
@@ -707,7 +714,9 @@
 	if company_list and company not in company_list:
 		company_list.append(company)
 
-	sal_comp = frappe.get_doc("Salary Component", sal_comp)
+	if not isinstance(sal_comp, Document):
+		sal_comp = frappe.get_doc("Salary Component", sal_comp)
+
 	if not sal_comp.get("accounts"):
 		for d in company_list:
 			company_abbr = frappe.get_cached_value('Company', d, 'abbr')
@@ -725,7 +734,7 @@
 			})
 			sal_comp.save()
 
-def create_account(account_name, company, parent_account):
+def create_account(account_name, company, parent_account, account_type=None):
 	company_abbr = frappe.get_cached_value('Company',  company,  'abbr')
 	account = frappe.db.get_value("Account", account_name + " - " + company_abbr)
 	if not account:
@@ -994,6 +1003,8 @@
 	))
 	leave_application.submit()
 
+	return leave_application
+
 def setup_test():
 	make_earning_salary_component(setup=True, company_list=["_Test Company"])
 	make_deduction_salary_component(setup=True, company_list=["_Test Company"])
@@ -1008,13 +1019,13 @@
 	frappe.db.set_value('HR Settings', None, 'leave_status_notification_template', None)
 	frappe.db.set_value('HR Settings', None, 'leave_approval_notification_template', None)
 
-def make_holiday_list():
+def make_holiday_list(holiday_list_name=None):
 	fiscal_year = get_fiscal_year(nowdate(), company=erpnext.get_default_company())
-	holiday_list = frappe.db.exists("Holiday List", "Salary Slip Test Holiday List")
+	holiday_list = frappe.db.exists("Holiday List", holiday_list_name or "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",
+			"holiday_list_name": holiday_list_name or "Salary Slip Test Holiday List",
 			"from_date": fiscal_year[1],
 			"to_date": fiscal_year[2],
 			"weekly_off": "Sunday"
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.json b/erpnext/payroll/doctype/salary_structure/salary_structure.json
index 5dd1d70..8df9957 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.json
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.json
@@ -58,6 +58,7 @@
    "width": "50%"
   },
   {
+   "allow_on_submit": 1,
    "default": "Yes",
    "fieldname": "is_active",
    "fieldtype": "Select",
@@ -232,10 +233,11 @@
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-31 15:41:12.342380",
+ "modified": "2022-02-03 23:50:10.205676",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Structure",
+ "naming_rule": "Set by user",
  "owner": "Administrator",
  "permissions": [
   {
@@ -271,5 +273,6 @@
  ],
  "show_name_in_global_search": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
index 197ab5f..613246e 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -162,7 +162,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-12-23 17:28:09.794444",
+ "modified": "2022-01-19 12:43:54.439073",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Structure Assignment",
@@ -209,6 +209,7 @@
    "write": 1
   }
  ],
+ "search_fields": "employee_name, salary_structure",
  "sort_field": "modified",
  "sort_order": "DESC",
  "states": [],
diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js
index c7c66e0..59f808a 100644
--- a/erpnext/portal/doctype/homepage/homepage.js
+++ b/erpnext/portal/doctype/homepage/homepage.js
@@ -3,9 +3,9 @@
 
 frappe.ui.form.on('Homepage', {
 	setup: function(frm) {
-		frm.fields_dict["products"].grid.get_field("item_code").get_query = function(){
+		frm.fields_dict["products"].grid.get_field("item").get_query = function() {
 			return {
-				filters: {'show_in_website': 1}
+				filters: {'published': 1}
 			}
 		}
 	},
@@ -21,11 +21,10 @@
 });
 
 frappe.ui.form.on('Homepage Featured Product', {
-
-	view: function(frm, cdt, cdn){
-		var child= locals[cdt][cdn]
-		if(child.item_code && frm.doc.products_url){
-			window.location.href = frm.doc.products_url + '/' + encodeURIComponent(child.item_code);
+	view: function(frm, cdt, cdn) {
+		var child= locals[cdt][cdn];
+		if (child.item_code && child.route) {
+			window.open('/' + child.route, '_blank');
 		}
 	}
 });
diff --git a/erpnext/portal/doctype/homepage/homepage.json b/erpnext/portal/doctype/homepage/homepage.json
index ad27278..73f816d 100644
--- a/erpnext/portal/doctype/homepage/homepage.json
+++ b/erpnext/portal/doctype/homepage/homepage.json
@@ -1,518 +1,143 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
+ "actions": [],
  "beta": 1,
  "creation": "2016-04-22 05:27:52.109319",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
  "document_type": "Setup",
- "editable_grid": 0,
  "engine": "InnoDB",
+ "field_order": [
+  "company",
+  "hero_section_based_on",
+  "column_break_2",
+  "title",
+  "section_break_4",
+  "tag_line",
+  "description",
+  "hero_image",
+  "slideshow",
+  "hero_section",
+  "products_section",
+  "products_url",
+  "products"
+ ],
  "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": 1,
-   "in_standard_filter": 0,
    "label": "Company",
-   "length": 0,
-   "no_copy": 0,
    "options": "Company",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "hero_section_based_on",
    "fieldtype": "Select",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Hero Section Based On",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Default\nSlideshow\nHomepage Section",
-   "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
+   "options": "Default\nSlideshow\nHomepage Section"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_2",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "",
    "fieldname": "title",
    "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Title",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Title"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "",
    "fieldname": "section_break_4",
    "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": "Hero Section",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Hero Section"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.hero_section_based_on === 'Default'",
    "description": "Company Tagline for website homepage",
    "fieldname": "tag_line",
    "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": "Tag Line",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.hero_section_based_on === 'Default'",
    "description": "Company Description for website homepage",
    "fieldname": "description",
    "fieldtype": "Text",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "Description",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.hero_section_based_on === 'Default'",
    "fieldname": "hero_image",
    "fieldtype": "Attach Image",
-   "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": "Hero Image",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Hero Image"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.hero_section_based_on === 'Slideshow'",
-   "description": "",
    "fieldname": "slideshow",
    "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": "Homepage Slideshow",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Website Slideshow",
-   "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
+   "options": "Website Slideshow"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.hero_section_based_on === 'Homepage Section'",
    "fieldname": "hero_section",
    "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": "Homepage Section",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Homepage Section",
-   "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
+   "options": "Homepage Section"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "",
    "fieldname": "products_section",
    "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": "Products",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Products"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "default": "/products",
+   "default": "/all-products",
    "fieldname": "products_url",
    "fieldtype": "Data",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "URL for \"All Products\"",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "URL for \"All Products\""
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "description": "Products to be shown on website homepage",
    "fieldname": "products",
    "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": "Products",
-   "length": 0,
-   "no_copy": 0,
    "options": "Homepage Featured Product",
-   "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,
    "width": "40px"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
  "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-03-02 23:12:59.676202",
+ "links": [],
+ "modified": "2021-02-18 13:29:29.531639",
  "modified_by": "Administrator",
  "module": "Portal",
  "name": "Homepage",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
-   "report": 0,
    "role": "System Manager",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   },
   {
-   "amend": 0,
-   "cancel": 0,
    "create": 1,
    "delete": 1,
    "email": 1,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
    "print": 1,
    "read": 1,
-   "report": 0,
    "role": "Administrator",
-   "set_user_permissions": 0,
    "share": 1,
-   "submit": 0,
    "write": 1
   }
  ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
  "sort_order": "DESC",
  "title_field": "company",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/portal/doctype/homepage/homepage.py b/erpnext/portal/doctype/homepage/homepage.py
index 1e056a6..8092ba2 100644
--- a/erpnext/portal/doctype/homepage/homepage.py
+++ b/erpnext/portal/doctype/homepage/homepage.py
@@ -14,12 +14,14 @@
 		delete_page_cache('home')
 
 	def setup_items(self):
-		for d in frappe.get_all('Item', fields=['name', 'item_name', 'description', 'image'],
-			filters={'show_in_website': 1}, limit=3):
+		for d in frappe.get_all('Website Item', fields=['name', 'item_name', 'description', 'image', 'route'],
+			filters={'published': 1}, limit=3):
 
-			doc = frappe.get_doc('Item', d.name)
+			doc = frappe.get_doc('Website Item', d.name)
 			if not doc.route:
 				# set missing route
 				doc.save()
 			self.append('products', dict(item_code=d.name,
-				item_name=d.item_name, description=d.description, image=d.image))
+				item_name=d.item_name, description=d.description,
+				image=d.image, route=d.route))
+
diff --git a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json
index 01c32ef..63789e3 100644
--- a/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json
+++ b/erpnext/portal/doctype/homepage_featured_product/homepage_featured_product.json
@@ -25,10 +25,10 @@
    "fieldtype": "Link",
    "in_filter": 1,
    "in_list_view": 1,
-   "label": "Item Code",
+   "label": "Item",
    "oldfieldname": "item_code",
    "oldfieldtype": "Link",
-   "options": "Item",
+   "options": "Website Item",
    "print_width": "150px",
    "reqd": 1,
    "search_index": 1,
@@ -63,7 +63,7 @@
    "collapsible": 1,
    "fieldname": "section_break_5",
    "fieldtype": "Section Break",
-   "label": "Description"
+   "label": "Details"
   },
   {
    "fetch_from": "item_code.web_long_description",
@@ -89,12 +89,14 @@
    "label": "Image"
   },
   {
+   "fetch_from": "item_code.thumbnail",
    "fieldname": "thumbnail",
    "fieldtype": "Attach Image",
    "hidden": 1,
    "label": "Thumbnail"
   },
   {
+   "fetch_from": "item_code.route",
    "fieldname": "route",
    "fieldtype": "Small Text",
    "label": "route",
@@ -104,7 +106,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-08-25 15:27:49.573537",
+ "modified": "2021-02-18 13:05:50.669311",
  "modified_by": "Administrator",
  "module": "Portal",
  "name": "Homepage Featured Product",
diff --git a/erpnext/portal/doctype/homepage_section/test_homepage_section.py b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
index b30d983..c3be146 100644
--- a/erpnext/portal/doctype/homepage_section/test_homepage_section.py
+++ b/erpnext/portal/doctype/homepage_section/test_homepage_section.py
@@ -21,7 +21,7 @@
 					{'title': 'Card 2', 'subtitle': 'Subtitle 2', 'content': 'This is test card 2', 'image': 'test.jpg'},
 				],
 				'no_of_columns': 3
-			}).insert()
+			}).insert(ignore_if_duplicate=True)
 		except frappe.DuplicateEntryError:
 			pass
 
diff --git a/erpnext/portal/doctype/products_settings/__init__.py b/erpnext/portal/doctype/products_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/portal/doctype/products_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/portal/doctype/products_settings/products_settings.js b/erpnext/portal/doctype/products_settings/products_settings.js
deleted file mode 100644
index 2f8b037..0000000
--- a/erpnext/portal/doctype/products_settings/products_settings.js
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Products Settings', {
-	refresh: function(frm) {
-		frappe.model.with_doctype('Item', () => {
-			const item_meta = frappe.get_meta('Item');
-
-			const valid_fields = item_meta.fields.filter(
-				df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
-			).map(df => ({ label: df.label, value: df.fieldname }));
-
-			frm.fields_dict.filter_fields.grid.update_docfield_property(
-				'fieldname', 'fieldtype', 'Select'
-			);
-			frm.fields_dict.filter_fields.grid.update_docfield_property(
-				'fieldname', 'options', valid_fields
-			);
-		});
-	}
-});
diff --git a/erpnext/portal/doctype/products_settings/products_settings.json b/erpnext/portal/doctype/products_settings/products_settings.json
deleted file mode 100644
index 2cf8431..0000000
--- a/erpnext/portal/doctype/products_settings/products_settings.json
+++ /dev/null
@@ -1,389 +0,0 @@
-{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-04-22 09:11:55.272398",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
- "engine": "InnoDB",
- "fields": [
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "description": "If checked, the Home page will be the default Item Group for the website",
-   "fieldname": "home_page_is_products",
-   "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": "Home Page is Products",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "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_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "show_availability_status",
-   "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": "Show Availability Status",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "section_break_5",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Product Page",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "default": "6",
-   "fieldname": "products_per_page",
-   "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": "Products per Page",
-   "length": 0,
-   "no_copy": 0,
-   "options": "",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "enable_field_filters",
-   "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": "Enable Field Filters",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "enable_field_filters",
-   "fieldname": "filter_fields",
-   "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": "Item Fields",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Website Filter Field",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "enable_attribute_filters",
-   "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": "Enable Attribute Filters",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "enable_attribute_filters",
-   "fieldname": "filter_attributes",
-   "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": "Attributes",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Website Attribute",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
-  },
-  {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "fieldname": "hide_variants",
-   "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": "Hide Variants",
-   "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": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-03-07 19:18:31.822309",
- "modified_by": "Administrator",
- "module": "Portal",
- "name": "Products Settings",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [
-  {
-   "amend": 0,
-   "cancel": 0,
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 0,
-   "if_owner": 0,
-   "import": 0,
-   "permlevel": 0,
-   "print": 1,
-   "read": 1,
-   "report": 0,
-   "role": "Website Manager",
-   "set_user_permissions": 0,
-   "share": 1,
-   "submit": 0,
-   "write": 1
-  }
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/portal/doctype/products_settings/products_settings.py b/erpnext/portal/doctype/products_settings/products_settings.py
deleted file mode 100644
index 0e106c6..0000000
--- a/erpnext/portal/doctype/products_settings/products_settings.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import cint
-
-
-class ProductsSettings(Document):
-	def validate(self):
-		if self.home_page_is_products:
-			frappe.db.set_value("Website Settings", None, "home_page", "products")
-		elif frappe.db.get_single_value("Website Settings", "home_page") == 'products':
-			frappe.db.set_value("Website Settings", None, "home_page", "home")
-
-		self.validate_field_filters()
-		self.validate_attribute_filters()
-		frappe.clear_document_cache("Product Settings", "Product Settings")
-
-	def validate_field_filters(self):
-		if not (self.enable_field_filters and self.filter_fields): return
-
-		item_meta = frappe.get_meta('Item')
-		valid_fields = [df.fieldname for df in item_meta.fields if df.fieldtype in ['Link', 'Table MultiSelect']]
-
-		for f in self.filter_fields:
-			if f.fieldname not in valid_fields:
-				frappe.throw(_('Filter Fields Row #{0}: Fieldname <b>{1}</b> must be of type "Link" or "Table MultiSelect"').format(f.idx, f.fieldname))
-
-	def validate_attribute_filters(self):
-		if not (self.enable_attribute_filters and self.filter_attributes): return
-
-		# if attribute filters are enabled, hide_variants should be disabled
-		self.hide_variants = 0
-
-
-def home_page_is_products(doc, method):
-	'''Called on saving Website Settings'''
-	home_page_is_products = cint(frappe.db.get_single_value('Products Settings', 'home_page_is_products'))
-	if home_page_is_products:
-		doc.home_page = 'products'
diff --git a/erpnext/portal/doctype/products_settings/test_products_settings.py b/erpnext/portal/doctype/products_settings/test_products_settings.py
deleted file mode 100644
index 66026fc..0000000
--- a/erpnext/portal/doctype/products_settings/test_products_settings.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestProductsSettings(unittest.TestCase):
-	pass
diff --git a/erpnext/portal/doctype/website_attribute/website_attribute.json b/erpnext/portal/doctype/website_attribute/website_attribute.json
index 2874dc4..eed33ec 100644
--- a/erpnext/portal/doctype/website_attribute/website_attribute.json
+++ b/erpnext/portal/doctype/website_attribute/website_attribute.json
@@ -1,76 +1,32 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2019-01-01 13:04:54.479079", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2019-01-01 13:04:54.479079",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "attribute"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "attribute", 
-   "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": "Attribute", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Item Attribute", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "fieldname": "attribute",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Attribute",
+   "options": "Item Attribute",
+   "reqd": 1
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2019-01-01 13:04:59.715572", 
- "modified_by": "Administrator", 
- "module": "Portal", 
- "name": "Website Attribute", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-02-18 13:18:57.810536",
+ "modified_by": "Administrator",
+ "module": "Portal",
+ "name": "Website Attribute",
+ "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/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
deleted file mode 100644
index b478489..0000000
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ /dev/null
@@ -1,143 +0,0 @@
-import unittest
-
-import frappe
-from bs4 import BeautifulSoup
-from frappe.utils import get_html_for_route
-
-from erpnext.portal.product_configurator.utils import get_products_for_website
-
-test_dependencies = ["Item"]
-
-class TestProductConfigurator(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
-		cls.create_variant_item()
-
-	@classmethod
-	def create_variant_item(cls):
-		if not frappe.db.exists('Item', '_Test Variant Item - 2XL'):
-			frappe.get_doc({
-				"description": "_Test Variant Item - 2XL",
-				"item_code": "_Test Variant Item - 2XL",
-				"item_name": "_Test Variant Item - 2XL",
-				"doctype": "Item",
-				"is_stock_item": 1,
-				"variant_of": "_Test Variant Item",
-				"item_group": "_Test Item Group",
-				"stock_uom": "_Test UOM",
-				"item_defaults": [{
-					"company": "_Test Company",
-					"default_warehouse": "_Test Warehouse - _TC",
-					"expense_account": "_Test Account Cost for Goods Sold - _TC",
-					"buying_cost_center": "_Test Cost Center - _TC",
-					"selling_cost_center": "_Test Cost Center - _TC",
-					"income_account": "Sales - _TC"
-				}],
-				"attributes": [
-					{
-						"attribute": "Test Size",
-						"attribute_value": "2XL"
-					}
-				],
-				"show_variant_in_website": 1
-			}).insert()
-
-	def create_regular_web_item(self, name, item_group=None):
-		if not frappe.db.exists('Item', name):
-			doc = frappe.get_doc({
-				"description": name,
-				"item_code": name,
-				"item_name": name,
-				"doctype": "Item",
-				"is_stock_item": 1,
-				"item_group": item_group or "_Test Item Group",
-				"stock_uom": "_Test UOM",
-				"item_defaults": [{
-					"company": "_Test Company",
-					"default_warehouse": "_Test Warehouse - _TC",
-					"expense_account": "_Test Account Cost for Goods Sold - _TC",
-					"buying_cost_center": "_Test Cost Center - _TC",
-					"selling_cost_center": "_Test Cost Center - _TC",
-					"income_account": "Sales - _TC"
-				}],
-				"show_in_website": 1
-			}).insert()
-		else:
-			doc = frappe.get_doc("Item", name)
-		return doc
-
-	def test_product_list(self):
-		template_items = frappe.get_all('Item', {'show_in_website': 1})
-		variant_items = frappe.get_all('Item', {'show_variant_in_website': 1})
-
-		products_settings = frappe.get_doc('Products Settings')
-		products_settings.enable_field_filters = 1
-		products_settings.append('filter_fields', {'fieldname': 'item_group'})
-		products_settings.append('filter_fields', {'fieldname': 'stock_uom'})
-		products_settings.save()
-
-		html = get_html_for_route('all-products')
-
-		soup = BeautifulSoup(html, 'html.parser')
-		products_list = soup.find(class_='products-list')
-		items = products_list.find_all(class_='card')
-		self.assertEqual(len(items), len(template_items + variant_items))
-
-		items_with_item_group = frappe.get_all('Item', {'item_group': '_Test Item Group Desktops', 'show_in_website': 1})
-		variants_with_item_group = frappe.get_all('Item', {'item_group': '_Test Item Group Desktops', 'show_variant_in_website': 1})
-
-		# mock query params
-		frappe.form_dict = frappe._dict({
-			'field_filters': '{"item_group":["_Test Item Group Desktops"]}'
-		})
-		html = get_html_for_route('all-products')
-		soup = BeautifulSoup(html, 'html.parser')
-		products_list = soup.find(class_='products-list')
-		items = products_list.find_all(class_='card')
-		self.assertEqual(len(items), len(items_with_item_group + variants_with_item_group))
-
-
-	def test_get_products_for_website(self):
-		items = get_products_for_website(attribute_filters={
-			'Test Size': ['2XL']
-		})
-		self.assertEqual(len(items), 1)
-
-	def test_products_in_multiple_item_groups(self):
-		"""Check if product is visible on multiple item group pages barring its own."""
-		from erpnext.shopping_cart.product_query import ProductQuery
-
-		if not frappe.db.exists("Item Group", {"name": "Tech Items"}):
-			item_group_doc = frappe.get_doc({
-				"doctype": "Item Group",
-				"item_group_name": "Tech Items",
-				"parent_item_group": "All Item Groups",
-				"show_in_website": 1
-			}).insert()
-		else:
-			item_group_doc = frappe.get_doc("Item Group", "Tech Items")
-
-		doc = self.create_regular_web_item("Portal Item", item_group="Tech Items")
-		if not frappe.db.exists("Website Item Group", {"parent": "Portal Item"}):
-			doc.append("website_item_groups", {
-				"item_group": "_Test Item Group Desktops"
-			})
-			doc.save()
-
-		# check if item is visible in its own Item Group's page
-		engine = ProductQuery()
-		items = engine.query({}, {"item_group": "Tech Items"}, None, start=0, item_group="Tech Items")
-		self.assertEqual(len(items), 1)
-		self.assertEqual(items[0].item_code, "Portal Item")
-
-		# check if item is visible in configured foreign Item Group's page
-		engine = ProductQuery()
-		items = engine.query({}, {"item_group": "_Test Item Group Desktops"}, None, start=0, item_group="_Test Item Group Desktops")
-		item_codes = [row.item_code for row in items]
-
-		self.assertIn(len(items), [2, 3])
-		self.assertIn("Portal Item", item_codes)
-
-		# teardown
-		doc.delete()
-		item_group_doc.delete()
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
deleted file mode 100644
index cf623c8..0000000
--- a/erpnext/portal/product_configurator/utils.py
+++ /dev/null
@@ -1,446 +0,0 @@
-import frappe
-from frappe.utils import cint
-
-from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
-from erpnext.setup.doctype.item_group.item_group import get_child_groups
-from erpnext.shopping_cart.product_info import get_product_info_for_website
-
-
-def get_field_filter_data():
-	product_settings = get_product_settings()
-	filter_fields = [row.fieldname for row in product_settings.filter_fields]
-
-	meta = frappe.get_meta('Item')
-	fields = [df for df in meta.fields if df.fieldname in filter_fields]
-
-	filter_data = []
-	for f in fields:
-		doctype = f.get_link_doctype()
-
-		# apply enable/disable/show_in_website filter
-		meta = frappe.get_meta(doctype)
-		filters = {}
-		if meta.has_field('enabled'):
-			filters['enabled'] = 1
-		if meta.has_field('disabled'):
-			filters['disabled'] = 0
-		if meta.has_field('show_in_website'):
-			filters['show_in_website'] = 1
-
-		values = [d.name for d in frappe.get_all(doctype, filters)]
-		filter_data.append([f, values])
-
-	return filter_data
-
-
-def get_attribute_filter_data():
-	product_settings = get_product_settings()
-	attributes = [row.attribute for row in product_settings.filter_attributes]
-	attribute_docs = [
-		frappe.get_doc('Item Attribute', attribute) for attribute in attributes
-	]
-
-	# mark attribute values as checked if they are present in the request url
-	if frappe.form_dict:
-		for attr in attribute_docs:
-			if attr.name in frappe.form_dict:
-				value = frappe.form_dict[attr.name]
-				if value:
-					enabled_values = value.split(',')
-				else:
-					enabled_values = []
-
-				for v in enabled_values:
-					for item_attribute_row in attr.item_attribute_values:
-						if v == item_attribute_row.attribute_value:
-							item_attribute_row.checked = True
-
-	return attribute_docs
-
-
-def get_products_for_website(field_filters=None, attribute_filters=None, search=None):
-	if attribute_filters:
-		item_codes = get_item_codes_by_attributes(attribute_filters)
-		items_by_attributes = get_items([['name', 'in', item_codes]])
-
-	if field_filters:
-		items_by_fields = get_items_by_fields(field_filters)
-
-	if attribute_filters and not field_filters:
-		return items_by_attributes
-
-	if field_filters and not attribute_filters:
-		return items_by_fields
-
-	if field_filters and attribute_filters:
-		items_intersection = []
-		item_codes_in_attribute = [item.name for item in items_by_attributes]
-
-		for item in items_by_fields:
-			if item.name in item_codes_in_attribute:
-				items_intersection.append(item)
-
-		return items_intersection
-
-	if search:
-		return get_items(search=search)
-
-	return get_items()
-
-
-@frappe.whitelist(allow_guest=True)
-def get_products_html_for_website(field_filters=None, attribute_filters=None):
-	field_filters = frappe.parse_json(field_filters)
-	attribute_filters = frappe.parse_json(attribute_filters)
-	set_item_group_filters(field_filters)
-
-	items = get_products_for_website(field_filters, attribute_filters)
-	html = ''.join(get_html_for_items(items))
-
-	if not items:
-		html = frappe.render_template('erpnext/www/all-products/not_found.html', {})
-
-	return html
-
-def set_item_group_filters(field_filters):
-	if field_filters is not None and 'item_group' in field_filters:
-		field_filters['item_group'] = [ig[0] for ig in get_child_groups(field_filters['item_group'])]
-
-
-def get_item_codes_by_attributes(attribute_filters, template_item_code=None):
-	items = []
-
-	for attribute, values in attribute_filters.items():
-		attribute_values = values
-
-		if not isinstance(attribute_values, list):
-			attribute_values = [attribute_values]
-
-		if not attribute_values: continue
-
-		wheres = []
-		query_values = []
-		for attribute_value in attribute_values:
-			wheres.append('( attribute = %s and attribute_value = %s )')
-			query_values += [attribute, attribute_value]
-
-		attribute_query = ' or '.join(wheres)
-
-		if template_item_code:
-			variant_of_query = 'AND t2.variant_of = %s'
-			query_values.append(template_item_code)
-		else:
-			variant_of_query = ''
-
-		query = '''
-			SELECT
-				t1.parent
-			FROM
-				`tabItem Variant Attribute` t1
-			WHERE
-				1 = 1
-				AND (
-					{attribute_query}
-				)
-				AND EXISTS (
-					SELECT
-						1
-					FROM
-						`tabItem` t2
-					WHERE
-						t2.name = t1.parent
-						{variant_of_query}
-				)
-			GROUP BY
-				t1.parent
-			ORDER BY
-				NULL
-		'''.format(attribute_query=attribute_query, variant_of_query=variant_of_query)
-
-		item_codes = set([r[0] for r in frappe.db.sql(query, query_values)])
-		items.append(item_codes)
-
-	res = list(set.intersection(*items))
-
-	return res
-
-
-@frappe.whitelist(allow_guest=True)
-def get_attributes_and_values(item_code):
-	'''Build a list of attributes and their possible values.
-	This will ignore the values upon selection of which there cannot exist one item.
-	'''
-	item_cache = ItemVariantsCacheManager(item_code)
-	item_variants_data = item_cache.get_item_variants_data()
-
-	attributes = get_item_attributes(item_code)
-	attribute_list = [a.attribute for a in attributes]
-
-	valid_options = {}
-	for item_code, attribute, attribute_value in item_variants_data:
-		if attribute in attribute_list:
-			valid_options.setdefault(attribute, set()).add(attribute_value)
-
-	item_attribute_values = frappe.db.get_all('Item Attribute Value',
-		['parent', 'attribute_value', 'idx'], order_by='parent asc, idx asc')
-	ordered_attribute_value_map = frappe._dict()
-	for iv in item_attribute_values:
-		ordered_attribute_value_map.setdefault(iv.parent, []).append(iv.attribute_value)
-
-	# build attribute values in idx order
-	for attr in attributes:
-		valid_attribute_values = valid_options.get(attr.attribute, [])
-		ordered_values = ordered_attribute_value_map.get(attr.attribute, [])
-		attr['values'] = [v for v in ordered_values if v in valid_attribute_values]
-
-	return attributes
-
-
-@frappe.whitelist(allow_guest=True)
-def get_next_attribute_and_values(item_code, selected_attributes):
-	'''Find the count of Items that match the selected attributes.
-	Also, find the attribute values that are not applicable for further searching.
-	If less than equal to 10 items are found, return item_codes of those items.
-	If one item is matched exactly, return item_code of that item.
-	'''
-	selected_attributes = frappe.parse_json(selected_attributes)
-
-	item_cache = ItemVariantsCacheManager(item_code)
-	item_variants_data = item_cache.get_item_variants_data()
-
-	attributes = get_item_attributes(item_code)
-	attribute_list = [a.attribute for a in attributes]
-	filtered_items = get_items_with_selected_attributes(item_code, selected_attributes)
-
-	next_attribute = None
-
-	for attribute in attribute_list:
-		if attribute not in selected_attributes:
-			next_attribute = attribute
-			break
-
-	valid_options_for_attributes = frappe._dict({})
-
-	for a in attribute_list:
-		valid_options_for_attributes[a] = set()
-
-		selected_attribute = selected_attributes.get(a, None)
-		if selected_attribute:
-			# already selected attribute values are valid options
-			valid_options_for_attributes[a].add(selected_attribute)
-
-	for row in item_variants_data:
-		item_code, attribute, attribute_value = row
-		if item_code in filtered_items and attribute not in selected_attributes and attribute in attribute_list:
-			valid_options_for_attributes[attribute].add(attribute_value)
-
-	optional_attributes = item_cache.get_optional_attributes()
-	exact_match = []
-	# search for exact match if all selected attributes are required attributes
-	if len(selected_attributes.keys()) >= (len(attribute_list) - len(optional_attributes)):
-		item_attribute_value_map = item_cache.get_item_attribute_value_map()
-		for item_code, attr_dict in item_attribute_value_map.items():
-			if item_code in filtered_items and set(attr_dict.keys()) == set(selected_attributes.keys()):
-				exact_match.append(item_code)
-
-	filtered_items_count = len(filtered_items)
-
-	# get product info if exact match
-	from erpnext.shopping_cart.product_info import get_product_info_for_website
-	if exact_match:
-		data = get_product_info_for_website(exact_match[0])
-		product_info = data.product_info
-		if product_info:
-			product_info["allow_items_not_in_stock"] = cint(data.cart_settings.allow_items_not_in_stock)
-		if not data.cart_settings.show_price:
-			product_info = None
-	else:
-		product_info = None
-
-	return {
-		'next_attribute': next_attribute,
-		'valid_options_for_attributes': valid_options_for_attributes,
-		'filtered_items_count': filtered_items_count,
-		'filtered_items': filtered_items if filtered_items_count < 10 else [],
-		'exact_match': exact_match,
-		'product_info': product_info
-	}
-
-
-def get_items_with_selected_attributes(item_code, selected_attributes):
-	item_cache = ItemVariantsCacheManager(item_code)
-	attribute_value_item_map = item_cache.get_attribute_value_item_map()
-
-	items = []
-	for attribute, value in selected_attributes.items():
-		filtered_items = attribute_value_item_map.get((attribute, value), [])
-		items.append(set(filtered_items))
-
-	return set.intersection(*items)
-
-
-def get_items_by_fields(field_filters):
-	meta = frappe.get_meta('Item')
-	filters = []
-	for fieldname, values in field_filters.items():
-		if not values: continue
-
-		_doctype = 'Item'
-		_fieldname = fieldname
-
-		df = meta.get_field(fieldname)
-		if df.fieldtype == 'Table MultiSelect':
-			child_doctype = df.options
-			child_meta = frappe.get_meta(child_doctype)
-			fields = child_meta.get("fields", { "fieldtype": "Link", "in_list_view": 1 })
-			if fields:
-				_doctype = child_doctype
-				_fieldname = fields[0].fieldname
-
-		if len(values) == 1:
-			filters.append([_doctype, _fieldname, '=', values[0]])
-		else:
-			filters.append([_doctype, _fieldname, 'in', values])
-
-	return get_items(filters)
-
-
-def get_items(filters=None, search=None):
-	start = frappe.form_dict.get('start', 0)
-	products_settings = get_product_settings()
-	page_length = products_settings.products_per_page
-
-	filters = filters or []
-	# convert to list of filters
-	if isinstance(filters, dict):
-		filters = [['Item', fieldname, '=', value] for fieldname, value in filters.items()]
-
-	enabled_items_filter = get_conditions({ 'disabled': 0 }, 'and')
-
-	show_in_website_condition = ''
-	if products_settings.hide_variants:
-		show_in_website_condition = get_conditions({'show_in_website': 1 }, 'and')
-	else:
-		show_in_website_condition = get_conditions([
-			['show_in_website', '=', 1],
-			['show_variant_in_website', '=', 1]
-		], 'or')
-
-	search_condition = ''
-	if search:
-		# Default fields to search from
-		default_fields = {'name', 'item_name', 'description', 'item_group'}
-
-		# Get meta search fields
-		meta = frappe.get_meta("Item")
-		meta_fields = set(meta.get_search_fields())
-
-		# Join the meta fields and default fields set
-		search_fields = default_fields.union(meta_fields)
-		try:
-			if frappe.db.count('Item', cache=True) > 50000:
-				search_fields.remove('description')
-		except KeyError:
-			pass
-
-		# Build or filters for query
-		search = '%{}%'.format(search)
-		or_filters = [[field, 'like', search] for field in search_fields]
-
-		search_condition = get_conditions(or_filters, 'or')
-
-	filter_condition = get_conditions(filters, 'and')
-
-	where_conditions = ' and '.join(
-		[condition for condition in [enabled_items_filter, show_in_website_condition, \
-			search_condition, filter_condition] if condition]
-	)
-
-	left_joins = []
-	for f in filters:
-		if len(f) == 4 and f[0] != 'Item':
-			left_joins.append(f[0])
-
-	left_join = ' '.join(['LEFT JOIN `tab{0}` on (`tab{0}`.parent = `tabItem`.name)'.format(l) for l in left_joins])
-
-	results = frappe.db.sql('''
-		SELECT
-			`tabItem`.`name`, `tabItem`.`item_name`, `tabItem`.`item_code`,
-			`tabItem`.`website_image`, `tabItem`.`image`,
-			`tabItem`.`web_long_description`, `tabItem`.`description`,
-			`tabItem`.`route`, `tabItem`.`item_group`
-		FROM
-			`tabItem`
-		{left_join}
-		WHERE
-			{where_conditions}
-		GROUP BY
-			`tabItem`.`name`
-		ORDER BY
-			`tabItem`.`weightage` DESC
-		LIMIT
-			{page_length}
-		OFFSET
-			{start}
-	'''.format(
-			where_conditions=where_conditions,
-			start=start,
-			page_length=page_length,
-			left_join=left_join
-		)
-	, as_dict=1)
-
-	for r in results:
-		r.description = r.web_long_description or r.description
-		r.image = r.website_image or r.image
-		product_info = get_product_info_for_website(r.item_code, skip_quotation_creation=True).get('product_info')
-		if product_info:
-			r.formatted_price = product_info['price'].get('formatted_price') if product_info['price'] else None
-
-	return results
-
-
-def get_conditions(filter_list, and_or='and'):
-	from frappe.model.db_query import DatabaseQuery
-
-	if not filter_list:
-		return ''
-
-	conditions = []
-	DatabaseQuery('Item').build_filter_conditions(filter_list, conditions, ignore_permissions=True)
-	join_by = ' {0} '.format(and_or)
-
-	return '(' + join_by.join(conditions) + ')'
-
-# utilities
-
-def get_item_attributes(item_code):
-	attributes = frappe.db.get_all('Item Variant Attribute',
-		fields=['attribute'],
-		filters={
-			'parenttype': 'Item',
-			'parent': item_code
-		},
-		order_by='idx asc'
-	)
-
-	optional_attributes = ItemVariantsCacheManager(item_code).get_optional_attributes()
-
-	for a in attributes:
-		if a.attribute in optional_attributes:
-			a.optional = True
-
-	return attributes
-
-def get_html_for_items(items):
-	html = []
-	for item in items:
-		html.append(frappe.render_template('erpnext/www/all-products/item_row.html', {
-			'item': item
-		}))
-	return html
-
-def get_product_settings():
-	doc = frappe.get_cached_doc('Products Settings')
-	doc.products_per_page = doc.products_per_page or 20
-	return doc
diff --git a/erpnext/portal/utils.py b/erpnext/portal/utils.py
index 974b51e..24bcab4 100644
--- a/erpnext/portal/utils.py
+++ b/erpnext/portal/utils.py
@@ -1,10 +1,10 @@
 import frappe
 from frappe.utils.nestedset import get_root_of
 
-from erpnext.shopping_cart.cart import get_debtors_account
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
 )
+from erpnext.e_commerce.shopping_cart.cart import get_debtors_account
 
 
 def set_default_role(doc, method):
diff --git a/erpnext/projects/doctype/project/project.json b/erpnext/projects/doctype/project/project.json
index 2570df7..1cda0a0 100644
--- a/erpnext/projects/doctype/project/project.json
+++ b/erpnext/projects/doctype/project/project.json
@@ -235,13 +235,13 @@
   {
    "fieldname": "actual_start_date",
    "fieldtype": "Data",
-   "label": "Actual Start Date",
+   "label": "Actual Start Date (via Time Sheet)",
    "read_only": 1
   },
   {
    "fieldname": "actual_time",
    "fieldtype": "Float",
-   "label": "Actual Time (in Hours)",
+   "label": "Actual Time (in Hours via Time Sheet)",
    "read_only": 1
   },
   {
@@ -251,7 +251,7 @@
   {
    "fieldname": "actual_end_date",
    "fieldtype": "Date",
-   "label": "Actual End Date",
+   "label": "Actual End Date (via Time Sheet)",
    "oldfieldname": "act_completion_date",
    "oldfieldtype": "Date",
    "read_only": 1
@@ -458,10 +458,11 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 4,
- "modified": "2021-04-28 16:36:11.654632",
+ "modified": "2022-01-29 13:58:27.712714",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Project",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -499,6 +500,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "customer",
  "title_field": "project_name",
  "track_seen": 1
diff --git a/erpnext/projects/doctype/task/task.json b/erpnext/projects/doctype/task/task.json
index ef4740d..8182208 100644
--- a/erpnext/projects/doctype/task/task.json
+++ b/erpnext/projects/doctype/task/task.json
@@ -249,7 +249,7 @@
   {
    "fieldname": "actual_time",
    "fieldtype": "Float",
-   "label": "Actual Time (in hours)",
+   "label": "Actual Time (in Hours via Time Sheet)",
    "read_only": 1
   },
   {
@@ -397,10 +397,11 @@
  "is_tree": 1,
  "links": [],
  "max_attachments": 5,
- "modified": "2021-04-16 12:46:51.556741",
+ "modified": "2022-01-29 13:58:47.005241",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Task",
+ "naming_rule": "Expression (old style)",
  "nsm_parent_field": "parent_task",
  "owner": "Administrator",
  "permissions": [
@@ -421,6 +422,7 @@
  "show_preview_popup": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "project",
  "title_field": "subject",
  "track_seen": 1
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 148d8ba..8b60357 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -5,7 +5,7 @@
 import unittest
 
 import frappe
-from frappe.utils import add_months, now_datetime, nowdate
+from frappe.utils import add_months, add_to_date, now_datetime, nowdate
 
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.hr.doctype.employee.test_employee import make_employee
@@ -151,6 +151,56 @@
 		settings.ignore_employee_time_overlap = initial_setting
 		settings.save()
 
+	def test_timesheet_not_overlapping_with_continuous_timelogs(self):
+		emp = make_employee("test_employee_6@salary.com")
+
+		update_activity_type("_Test Activity Type")
+		timesheet = frappe.new_doc("Timesheet")
+		timesheet.employee = emp
+		timesheet.append(
+			'time_logs',
+			{
+				"billable": 1,
+				"activity_type": "_Test Activity Type",
+				"from_time": now_datetime(),
+				"to_time": now_datetime() + datetime.timedelta(hours=3),
+				"company": "_Test Company"
+			}
+		)
+		timesheet.append(
+			'time_logs',
+			{
+				"billable": 1,
+				"activity_type": "_Test Activity Type",
+				"from_time": now_datetime() + datetime.timedelta(hours=3),
+				"to_time": now_datetime() + datetime.timedelta(hours=4),
+				"company": "_Test Company"
+			}
+		)
+
+		timesheet.save() # should not throw an error
+
+	def test_to_time(self):
+		emp = make_employee("test_employee_6@salary.com")
+		from_time = now_datetime()
+
+		timesheet = frappe.new_doc("Timesheet")
+		timesheet.employee = emp
+		timesheet.append(
+			'time_logs',
+			{
+				"billable": 1,
+				"activity_type": "_Test Activity Type",
+				"from_time": from_time,
+				"hours": 2,
+				"company": "_Test Company"
+			}
+		)
+		timesheet.save()
+
+		to_time = timesheet.time_logs[0].to_time
+		self.assertEqual(to_time, add_to_date(from_time, hours=2, as_datetime=True))
+
 
 def make_salary_structure_for_timesheet(employee, company=None):
 	salary_structure_name = "Timesheet Salary Structure Test"
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index e92785e..b44d501 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -7,7 +7,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import flt, getdate, time_diff_in_hours
+from frappe.utils import add_to_date, flt, get_datetime, getdate, time_diff_in_hours
 
 from erpnext.controllers.queries import get_match_cond
 from erpnext.hr.utils import validate_active_employee
@@ -136,10 +136,19 @@
 
 	def validate_time_logs(self):
 		for data in self.get('time_logs'):
+			self.set_to_time(data)
 			self.validate_overlap(data)
 			self.set_project(data)
 			self.validate_project(data)
 
+	def set_to_time(self, data):
+		if not (data.from_time and data.hours):
+			return
+
+		_to_time = get_datetime(add_to_date(data.from_time, hours=data.hours, as_datetime=True))
+		if data.to_time != _to_time:
+			data.to_time = _to_time
+
 	def validate_overlap(self, data):
 		settings = frappe.get_single('Projects Settings')
 		self.validate_overlap_for("user", data, self.user, settings.ignore_user_time_overlap)
@@ -162,39 +171,54 @@
 				.format(args.idx, self.name, existing.name), OverlapError)
 
 	def get_overlap_for(self, fieldname, args, value):
-		cond = "ts.`{0}`".format(fieldname)
-		if fieldname == 'workstation':
-			cond = "tsd.`{0}`".format(fieldname)
+		timesheet = frappe.qb.DocType("Timesheet")
+		timelog = frappe.qb.DocType("Timesheet Detail")
 
-		existing = frappe.db.sql("""select ts.name as name, tsd.from_time as from_time, tsd.to_time as to_time from
-			`tabTimesheet Detail` tsd, `tabTimesheet` ts where {0}=%(val)s and tsd.parent = ts.name and
-			(
-				(%(from_time)s > tsd.from_time and %(from_time)s < tsd.to_time) or
-				(%(to_time)s > tsd.from_time and %(to_time)s < tsd.to_time) or
-				(%(from_time)s <= tsd.from_time and %(to_time)s >= tsd.to_time))
-			and tsd.name!=%(name)s
-			and ts.name!=%(parent)s
-			and ts.docstatus < 2""".format(cond),
-			{
-				"val": value,
-				"from_time": args.from_time,
-				"to_time": args.to_time,
-				"name": args.name or "No Name",
-				"parent": args.parent or "No Name"
-			}, as_dict=True)
-		# check internal overlap
-		for time_log in self.time_logs:
-			if not (time_log.from_time and time_log.to_time
-				and args.from_time and args.to_time): continue
+		from_time = get_datetime(args.from_time)
+		to_time = get_datetime(args.to_time)
 
-			if (fieldname != 'workstation' or args.get(fieldname) == time_log.get(fieldname)) and \
-				args.idx != time_log.idx and ((args.from_time > time_log.from_time and args.from_time < time_log.to_time) or
-				(args.to_time > time_log.from_time and args.to_time < time_log.to_time) or
-				(args.from_time <= time_log.from_time and args.to_time >= time_log.to_time)):
-				return self
+		existing = (
+			frappe.qb.from_(timesheet)
+				.join(timelog)
+				.on(timelog.parent == timesheet.name)
+				.select(timesheet.name.as_('name'), timelog.from_time.as_('from_time'), timelog.to_time.as_('to_time'))
+				.where(
+					(timelog.name != (args.name or "No Name"))
+					& (timesheet.name != (args.parent or "No Name"))
+					& (timesheet.docstatus < 2)
+					& (timesheet[fieldname] == value)
+					& (
+						((from_time > timelog.from_time) & (from_time < timelog.to_time))
+						| ((to_time > timelog.from_time) & (to_time < timelog.to_time))
+						| ((from_time <= timelog.from_time) & (to_time >= timelog.to_time))
+					)
+				)
+		).run(as_dict=True)
+
+		if self.check_internal_overlap(fieldname, args):
+			return self
 
 		return existing[0] if existing else None
 
+	def check_internal_overlap(self, fieldname, args):
+		for time_log in self.time_logs:
+			if not (time_log.from_time and time_log.to_time
+				and args.from_time and args.to_time):
+				continue
+
+			from_time = get_datetime(time_log.from_time)
+			to_time = get_datetime(time_log.to_time)
+			args_from_time = get_datetime(args.from_time)
+			args_to_time = get_datetime(args.to_time)
+
+			if (args.get(fieldname) == time_log.get(fieldname)) and (args.idx != time_log.idx) and (
+				(args_from_time > from_time and args_from_time < to_time)
+				or (args_to_time > from_time and args_to_time < to_time)
+				or (args_from_time <= from_time and args_to_time >= to_time)
+			):
+				return True
+		return False
+
 	def update_cost(self):
 		for data in self.time_logs:
 			if data.activity_type or data.is_billable:
diff --git a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
index ee04c61..90fdb83 100644
--- a/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
+++ b/erpnext/projects/doctype/timesheet_detail/timesheet_detail.json
@@ -14,12 +14,6 @@
   "to_time",
   "hours",
   "completed",
-  "section_break_7",
-  "completed_qty",
-  "workstation",
-  "column_break_12",
-  "operation",
-  "operation_id",
   "project_details",
   "project",
   "project_name",
@@ -84,43 +78,6 @@
    "label": "Completed"
   },
   {
-   "fieldname": "section_break_7",
-   "fieldtype": "Section Break"
-  },
-  {
-   "depends_on": "eval:parent.work_order",
-   "fieldname": "completed_qty",
-   "fieldtype": "Float",
-   "label": "Completed Qty"
-  },
-  {
-   "depends_on": "eval:parent.work_order",
-   "fieldname": "workstation",
-   "fieldtype": "Link",
-   "label": "Workstation",
-   "options": "Workstation",
-   "read_only": 1
-  },
-  {
-   "fieldname": "column_break_12",
-   "fieldtype": "Column Break"
-  },
-  {
-   "depends_on": "eval:parent.work_order",
-   "fieldname": "operation",
-   "fieldtype": "Link",
-   "label": "Operation",
-   "options": "Operation",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:parent.work_order",
-   "fieldname": "operation_id",
-   "fieldtype": "Data",
-   "hidden": 1,
-   "label": "Operation Id"
-  },
-  {
    "fieldname": "project_details",
    "fieldtype": "Section Break"
   },
@@ -267,7 +224,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-18 12:19:33.205940",
+ "modified": "2022-02-17 16:53:34.878798",
  "modified_by": "Administrator",
  "module": "Projects",
  "name": "Timesheet Detail",
@@ -275,5 +232,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/public/build.json b/erpnext/public/build.json
index f8e8177..91a752c 100644
--- a/erpnext/public/build.json
+++ b/erpnext/public/build.json
@@ -7,7 +7,8 @@
 	],
 	"js/erpnext-web.min.js": [
 		"public/js/website_utils.js",
-		"public/js/shopping_cart.js"
+		"public/js/shopping_cart.js",
+		"public/js/wishlist.js"
 	],
 	"css/erpnext-web.css": [
 		"public/scss/website.scss",
@@ -38,7 +39,8 @@
 		"public/js/utils/dimension_tree_filter.js",
 		"public/js/telephony.js",
 		"public/js/templates/call_link.html",
-		"public/js/templates/node_card.html"
+		"public/js/templates/node_card.html",
+		"public/js/bulk_transaction_processing.js"
 	],
 	"js/item-dashboard.min.js": [
 		"stock/dashboard/item_dashboard.html",
@@ -65,5 +67,11 @@
 	"js/hierarchy-chart.min.js": [
 		"public/js/hierarchy_chart/hierarchy_chart_desktop.js",
 		"public/js/hierarchy_chart/hierarchy_chart_mobile.js"
+	],
+	"js/e-commerce.min.js": [
+		"e_commerce/product_ui/views.js",
+		"e_commerce/product_ui/grid.js",
+		"e_commerce/product_ui/list.js",
+		"e_commerce/product_ui/search.js"
 	]
 }
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index ca73393..214a1be 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -182,6 +182,12 @@
 				onchange: () => this.update_options(),
 			},
 			{
+				fieldtype: "Check",
+				label: "Loan Repayment",
+				fieldname: "loan_repayment",
+				onchange: () => this.update_options(),
+			},
+			{
 				fieldname: "column_break_5",
 				fieldtype: "Column Break",
 			},
@@ -191,7 +197,6 @@
 				fieldname: "sales_invoice",
 				onchange: () => this.update_options(),
 			},
-
 			{
 				fieldtype: "Check",
 				label: "Purchase Invoice",
@@ -199,6 +204,12 @@
 				onchange: () => this.update_options(),
 			},
 			{
+				fieldtype: "Check",
+				label: "Show Only Exact Amount",
+				fieldname: "exact_match",
+				onchange: () => this.update_options(),
+			},
+			{
 				fieldname: "column_break_5",
 				fieldtype: "Column Break",
 			},
@@ -210,8 +221,8 @@
 			},
 			{
 				fieldtype: "Check",
-				label: "Show Only Exact Amount",
-				fieldname: "exact_match",
+				label: "Loan Disbursement",
+				fieldname: "loan_disbursement",
 				onchange: () => this.update_options(),
 			},
 			{
diff --git a/erpnext/public/js/bulk_transaction_processing.js b/erpnext/public/js/bulk_transaction_processing.js
new file mode 100644
index 0000000..101f50c
--- /dev/null
+++ b/erpnext/public/js/bulk_transaction_processing.js
@@ -0,0 +1,30 @@
+frappe.provide("erpnext.bulk_transaction_processing");
+
+$.extend(erpnext.bulk_transaction_processing, {
+	create: function(listview, from_doctype, to_doctype) {
+		let checked_items = listview.get_checked_items();
+		const doc_name = [];
+		checked_items.forEach((Item)=> {
+			if (Item.docstatus == 0) {
+				doc_name.push(Item.name);
+			}
+		});
+
+		let count_of_rows = checked_items.length;
+		frappe.confirm(__("Create {0} {1} ?", [count_of_rows, to_doctype]), ()=>{
+			if (doc_name.length == 0) {
+				frappe.call({
+					method: "erpnext.utilities.bulk_transaction.transaction_processing",
+					args: {data: checked_items, from_doctype: from_doctype, to_doctype: to_doctype}
+				}).then(()=> {
+
+				});
+				if (count_of_rows > 10) {
+					frappe.show_alert("Starting a background job to create {0} {1}", [count_of_rows, to_doctype]);
+				}
+			} else {
+				frappe.msgprint(__("Selected document must be in submitted state"));
+			}
+		});
+	}
+});
\ No newline at end of file
diff --git a/erpnext/public/js/conf.js b/erpnext/public/js/conf.js
index eb709e5..a0f56a2 100644
--- a/erpnext/public/js/conf.js
+++ b/erpnext/public/js/conf.js
@@ -21,6 +21,6 @@
 	'Geo': 'Settings',
 	'Portal': 'Website',
 	'Utilities': 'Settings',
-	'Shopping Cart': 'Website',
+	'E-commerce': 'Website',
 	'Contacts': 'CRM'
 });
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index d696ef5..54e5daa 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -441,7 +441,7 @@
 				type: "GET",
 				method: "erpnext.stock.doctype.packed_item.packed_item.get_items_from_product_bundle",
 				args: {
-					args: {
+					row: {
 						item_code: args.product_bundle,
 						quantity: args.quantity,
 						parenttype: frm.doc.doctype,
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 3791741..00373a6 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -525,6 +525,7 @@
 
 		item.weight_per_unit = 0;
 		item.weight_uom = '';
+		item.conversion_factor = 0;
 
 		if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
 			update_stock = cint(me.frm.doc.update_stock);
@@ -719,6 +720,7 @@
 			'posting_time': posting_time,
 			'qty': item.qty * item.conversion_factor,
 			'serial_no': item.serial_no,
+			'batch_no': item.batch_no,
 			'voucher_type': voucher_type,
 			'company': company,
 			'allow_zero_valuation_rate': item.allow_zero_valuation_rate
@@ -1463,7 +1465,8 @@
 						"item_code": d.item_code,
 						"pricing_rules": d.pricing_rules,
 						"parenttype": d.parenttype,
-						"parent": d.parent
+						"parent": d.parent,
+						"price_list_rate": d.price_list_rate
 					})
 				}
 			});
@@ -2282,18 +2285,15 @@
 	}
 
 	coupon_code() {
-		var me = this;
-		if (this.frm.doc.coupon_code) {
-			frappe.run_serially([
+		if (this.frm.doc.coupon_code || this.frm._last_coupon_code) {
+			// reset pricing rules if coupon code is set or is unset
+			const _ignore_pricing_rule = this.frm.doc.ignore_pricing_rule;
+			return frappe.run_serially([
 				() => this.frm.doc.ignore_pricing_rule=1,
-				() => me.ignore_pricing_rule(),
-				() => this.frm.doc.ignore_pricing_rule=0,
-				() => me.apply_pricing_rule()
-			]);
-		} else {
-			frappe.run_serially([
-				() => this.frm.doc.ignore_pricing_rule=1,
-				() => me.ignore_pricing_rule()
+				() => this.frm.trigger('ignore_pricing_rule'),
+				() => this.frm.doc.ignore_pricing_rule=_ignore_pricing_rule,
+				() => this.frm.trigger('apply_pricing_rule'),
+				() => this.frm._last_coupon_code = this.frm.doc.coupon_code
 			]);
 		}
 	}
diff --git a/erpnext/public/js/customer_reviews.js b/erpnext/public/js/customer_reviews.js
new file mode 100644
index 0000000..e13ded6
--- /dev/null
+++ b/erpnext/public/js/customer_reviews.js
@@ -0,0 +1,138 @@
+$(() => {
+	class CustomerReviews {
+		constructor() {
+			this.bind_button_actions();
+			this.start = 0;
+			this.page_length = 10;
+		}
+
+		bind_button_actions() {
+			this.write_review();
+			this.view_more();
+		}
+
+		write_review() {
+			//TODO: make dialog popup on stray page
+			$('.page_content').on('click', '.btn-write-review', (e) => {
+				// Bind action on write a review button
+				const $btn = $(e.currentTarget);
+
+				let d = new frappe.ui.Dialog({
+					title: __("Write a Review"),
+					fields: [
+						{fieldname: "title", fieldtype: "Data", label: "Headline", reqd: 1},
+						{fieldname: "rating", fieldtype: "Rating", label: "Overall Rating", reqd: 1},
+						{fieldtype: "Section Break"},
+						{fieldname: "comment", fieldtype: "Small Text", label: "Your Review"}
+					],
+					primary_action: function() {
+						let data = d.get_values();
+						frappe.call({
+							method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review",
+							args: {
+								web_item: $btn.attr('data-web-item'),
+								title: data.title,
+								rating: data.rating,
+								comment: data.comment
+							},
+							freeze: true,
+							freeze_message: __("Submitting Review ..."),
+							callback: (r) => {
+								if (!r.exc) {
+									frappe.msgprint({
+										message: __("Thank you for submitting your review"),
+										title: __("Review Submitted"),
+										indicator: "green"
+									});
+									d.hide();
+									location.reload();
+								}
+							}
+						});
+					},
+					primary_action_label: __('Submit')
+				});
+				d.show();
+			});
+		}
+
+		view_more() {
+			$('.page_content').on('click', '.btn-view-more', (e) => {
+				// Bind action on view more button
+				const $btn = $(e.currentTarget);
+				$btn.prop('disabled', true);
+
+				this.start += this.page_length;
+				let me = this;
+
+				frappe.call({
+					method: "erpnext.e_commerce.doctype.item_review.item_review.get_item_reviews",
+					args: {
+						web_item: $btn.attr('data-web-item'),
+						start: me.start,
+						end: me.page_length
+					},
+					callback: (result) => {
+						if (result.message) {
+							let res = result.message;
+							me.get_user_review_html(res.reviews);
+
+							$btn.prop('disabled', false);
+							if (res.total_reviews <= (me.start + me.page_length)) {
+								$btn.hide();
+							}
+
+						}
+					}
+				});
+			});
+
+		}
+
+		get_user_review_html(reviews) {
+			let me = this;
+			let $content = $('.user-reviews');
+
+			reviews.forEach((review) => {
+				$content.append(`
+					<div class="mb-3 review">
+						<div class="d-flex">
+							<p class="mr-4 user-review-title">
+								<span>${__(review.review_title)}</span>
+							</p>
+							<div class="rating">
+								${me.get_review_stars(review.rating)}
+							</div>
+						</div>
+
+						<div class="product-description mb-4">
+							<p>
+								${__(review.comment)}
+							</p>
+						</div>
+						<div class="review-signature mb-2">
+							<span class="reviewer">${__(review.customer)}</span>
+							<span class="indicator grey" style="--text-on-gray: var(--gray-300);"></span>
+							<span class="reviewer">${__(review.published_on)}</span>
+						</div>
+					</div>
+				`);
+			});
+		}
+
+		get_review_stars(rating) {
+			let stars = ``;
+			for (let i = 1; i < 6; i++) {
+				let fill_class = i <= rating ? 'star-click' : '';
+				stars += `
+					<svg class="icon icon-sm ${fill_class}">
+						<use href="#icon-star"></use>
+					</svg>
+				`;
+			}
+			return stars;
+		}
+	}
+
+	new CustomerReviews();
+});
\ No newline at end of file
diff --git a/erpnext/public/js/education/student_button.html b/erpnext/public/js/education/student_button.html
deleted file mode 100644
index b64c73a..0000000
--- a/erpnext/public/js/education/student_button.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<div class="col-sm-3">
-    <div class="checkbox">
-        <label>
-            <input
-                type="checkbox"
-                data-group_roll_number="{{group_roll_number}}"
-                data-student="{{student}}"
-                data-student-name="{{student_name}}"
-                class="students-check"
-                {% if status === "Present" %}
-                checked
-                {% endif %}
-                >
-            {{ group_roll_number }} - {{ student_name }}
-        </label>
-    </div>
-</div>
diff --git a/erpnext/public/js/erpnext-web.bundle.js b/erpnext/public/js/erpnext-web.bundle.js
index 7db6967..cbe899d 100644
--- a/erpnext/public/js/erpnext-web.bundle.js
+++ b/erpnext/public/js/erpnext-web.bundle.js
@@ -1,2 +1,8 @@
 import "./website_utils";
+import "./wishlist";
 import "./shopping_cart";
+import "./customer_reviews";
+import "../../e_commerce/product_ui/list";
+import "../../e_commerce/product_ui/views";
+import "../../e_commerce/product_ui/grid";
+import "../../e_commerce/product_ui/search";
\ No newline at end of file
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index 5259bdc..8409e78 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -16,11 +16,11 @@
 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 "./call_popup/call_popup";
 import "./utils/dimension_tree_filter";
 import "./telephony";
 import "./templates/call_link.html";
+import "./bulk_transaction_processing";
 
 // import { sum } from 'frappe/public/utils/util.js'
diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
index 831626a..a585aa6 100644
--- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
+++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_desktop.js
@@ -304,12 +304,13 @@
 	}
 
 	get_child_nodes(node_id) {
+		let me = this;
 		return new Promise(resolve => {
 			frappe.call({
-				method: this.method,
+				method: me.method,
 				args: {
 					parent: node_id,
-					company: this.company
+					company: me.company
 				}
 			}).then(r => resolve(r.message));
 		});
@@ -350,12 +351,13 @@
 	}
 
 	get_all_nodes() {
+		let me = this;
 		return new Promise(resolve => {
 			frappe.call({
 				method: 'erpnext.utilities.hierarchy_chart.get_all_nodes',
 				args: {
-					method: this.method,
-					company: this.company
+					method: me.method,
+					company: me.company
 				},
 				callback: (r) => {
 					resolve(r.message);
@@ -427,8 +429,8 @@
 
 	add_connector(parent_id, child_id) {
 		// using pure javascript for better performance
-		const parent_node = document.querySelector(`#${parent_id}`);
-		const child_node = document.querySelector(`#${child_id}`);
+		const parent_node = document.getElementById(`${parent_id}`);
+		const child_node = document.getElementById(`${child_id}`);
 
 		let path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
 
diff --git a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js
index 0a8ba78..52236e7 100644
--- a/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js
+++ b/erpnext/public/js/hierarchy_chart/hierarchy_chart_mobile.js
@@ -235,7 +235,7 @@
 		let me = this;
 		return new Promise(resolve => {
 			frappe.call({
-				method: this.method,
+				method: me.method,
 				args: {
 					parent: node_id,
 					company: me.company,
@@ -286,8 +286,8 @@
 	}
 
 	add_connector(parent_id, child_id) {
-		const parent_node = document.querySelector(`#${parent_id}`);
-		const child_node = document.querySelector(`#${child_id}`);
+		const parent_node = document.getElementById(`${parent_id}`);
+		const child_node = document.getElementById(`${child_id}`);
 
 		const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
 
@@ -518,7 +518,8 @@
 		level.nextAll('li').remove();
 
 		let node_object = this.nodes[node.id];
-		let current_node = level.find(`#${node.id}`).detach();
+		let current_node = level.find(`[id="${node.id}"]`).detach();
+
 		current_node.removeClass('active-child active-path');
 
 		node_object.expanded = 0;
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index e746ce9..83b69ae 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -78,11 +78,11 @@
 			slide.get_input("company_name").on("change", function () {
 				var parts = slide.get_input("company_name").val().split(" ");
 				var abbr = $.map(parts, function (p) { return p ? p.substr(0, 1) : null }).join("");
-				slide.get_field("company_abbr").set_value(abbr.slice(0, 5).toUpperCase());
+				slide.get_field("company_abbr").set_value(abbr.slice(0, 10).toUpperCase());
 			}).val(frappe.boot.sysdefaults.company_name || "").trigger("change");
 
 			slide.get_input("company_abbr").on("change", function () {
-				if (slide.get_input("company_abbr").val().length > 5) {
+				if (slide.get_input("company_abbr").val().length > 10) {
 					frappe.msgprint(__("Company Abbreviation cannot have more than 5 characters"));
 					slide.get_field("company_abbr").set_value("");
 				}
@@ -96,7 +96,7 @@
 			if (!this.values.company_abbr) {
 				return false;
 			}
-			if (this.values.company_abbr.length > 5) {
+			if (this.values.company_abbr.length > 10) {
 				return false;
 			}
 			return true;
diff --git a/erpnext/public/js/shopping_cart.js b/erpnext/public/js/shopping_cart.js
index 6a923ae..d14740c 100644
--- a/erpnext/public/js/shopping_cart.js
+++ b/erpnext/public/js/shopping_cart.js
@@ -2,8 +2,8 @@
 // License: GNU General Public License v3. See license.txt
 
 // shopping cart
-frappe.provide("erpnext.shopping_cart");
-var shopping_cart = erpnext.shopping_cart;
+frappe.provide("erpnext.e_commerce.shopping_cart");
+var shopping_cart = erpnext.e_commerce.shopping_cart;
 
 var getParams = function (url) {
 	var params = [];
@@ -51,10 +51,10 @@
 	if (referral_sales_partner) {
 		$(".txtreferral_sales_partner").val(referral_sales_partner);
 	}
+
 	// update login
 	shopping_cart.show_shoppingcart_dropdown();
 	shopping_cart.set_cart_count();
-	shopping_cart.bind_dropdown_cart_buttons();
 	shopping_cart.show_cart_navbar();
 });
 
@@ -63,7 +63,7 @@
 		$(".shopping-cart").on('shown.bs.dropdown', function() {
 			if (!$('.shopping-cart-menu .cart-container').length) {
 				return frappe.call({
-					method: 'erpnext.shopping_cart.cart.get_shopping_cart_menu',
+					method: 'erpnext.e_commerce.shopping_cart.cart.get_shopping_cart_menu',
 					callback: function(r) {
 						if (r.message) {
 							$('.shopping-cart-menu').html(r.message);
@@ -75,15 +75,18 @@
 	},
 
 	update_cart: function(opts) {
-		if(frappe.session.user==="Guest") {
-			if(localStorage) {
+		if (frappe.session.user==="Guest") {
+			if (localStorage) {
 				localStorage.setItem("last_visited", window.location.pathname);
 			}
-			window.location.href = "/login";
+			frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => {
+				window.location.href = res.message || "/login";
+			});
 		} else {
+			shopping_cart.freeze();
 			return frappe.call({
 				type: "POST",
-				method: "erpnext.shopping_cart.cart.update_cart",
+				method: "erpnext.e_commerce.shopping_cart.cart.update_cart",
 				args: {
 					item_code: opts.item_code,
 					qty: opts.qty,
@@ -92,10 +95,8 @@
 				},
 				btn: opts.btn,
 				callback: function(r) {
-					shopping_cart.set_cart_count();
-					if (r.message.shopping_cart_menu) {
-						$('.shopping-cart-menu').html(r.message.shopping_cart_menu);
-					}
+					shopping_cart.unfreeze();
+					shopping_cart.set_cart_count(true);
 					if(opts.callback)
 						opts.callback(r);
 				}
@@ -103,7 +104,9 @@
 		}
 	},
 
-	set_cart_count: function() {
+	set_cart_count: function(animate=false) {
+		$(".intermediate-empty-cart").remove();
+
 		var cart_count = frappe.get_cookie("cart_count");
 		if(frappe.session.user==="Guest") {
 			cart_count = 0;
@@ -118,24 +121,37 @@
 
 		if(parseInt(cart_count) === 0 || cart_count === undefined) {
 			$cart.css("display", "none");
-			$(".cart-items").html('Cart is Empty');
 			$(".cart-tax-items").hide();
 			$(".btn-place-order").hide();
-			$(".cart-addresses").hide();
+			$(".cart-payment-addresses").hide();
+
+			let intermediate_empty_cart_msg = `
+				<div class="text-center w-100 intermediate-empty-cart mt-4 mb-4 text-muted">
+					${ __("Cart is Empty") }
+				</div>
+			`;
+			$(".cart-table").after(intermediate_empty_cart_msg);
 		}
 		else {
 			$cart.css("display", "inline");
+			$("#cart-count").text(cart_count);
 		}
 
 		if(cart_count) {
 			$badge.html(cart_count);
+
+			if (animate) {
+				$cart.addClass("cart-animate");
+				setTimeout(() => {
+					$cart.removeClass("cart-animate");
+				}, 500);
+			}
 		} else {
 			$badge.remove();
 		}
 	},
 
 	shopping_cart_update: function({item_code, qty, cart_dropdown, additional_notes}) {
-		frappe.freeze();
 		shopping_cart.update_cart({
 			item_code,
 			qty,
@@ -143,10 +159,12 @@
 			with_items: 1,
 			btn: this,
 			callback: function(r) {
-				frappe.unfreeze();
 				if(!r.exc) {
 					$(".cart-items").html(r.message.items);
-					$(".cart-tax-items").html(r.message.taxes);
+					$(".cart-tax-items").html(r.message.total);
+					$(".payment-summary").html(r.message.taxes_and_totals);
+					shopping_cart.set_cart_count();
+
 					if (cart_dropdown != true) {
 						$(".cart-icon").hide();
 					}
@@ -155,35 +173,71 @@
 		});
 	},
 
-
-	bind_dropdown_cart_buttons: function () {
-		$(".cart-icon").on('click', '.number-spinner button', function () {
-			var btn = $(this),
-				input = btn.closest('.number-spinner').find('input'),
-				oldValue = input.val().trim(),
-				newVal = 0;
-
-			if (btn.attr('data-dir') == 'up') {
-				newVal = parseInt(oldValue) + 1;
-			} else {
-				if (oldValue > 1) {
-					newVal = parseInt(oldValue) - 1;
-				}
-			}
-			input.val(newVal);
-			var item_code = input.attr("data-item-code");
-			shopping_cart.shopping_cart_update({item_code, qty: newVal, cart_dropdown: true});
-			return false;
-		});
-
-	},
-
 	show_cart_navbar: function () {
 		frappe.call({
-			method: "erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings.is_cart_enabled",
+			method: "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.is_cart_enabled",
 			callback: function(r) {
 				$(".shopping-cart").toggleClass('hidden', r.message ? false : true);
 			}
 		});
+	},
+
+	toggle_button_class(button, remove, add) {
+		button.removeClass(remove);
+		button.addClass(add);
+	},
+
+	bind_add_to_cart_action() {
+		$('.page_content').on('click', '.btn-add-to-cart-list', (e) => {
+			const $btn = $(e.currentTarget);
+			$btn.prop('disabled', true);
+
+			if (frappe.session.user==="Guest") {
+				if (localStorage) {
+					localStorage.setItem("last_visited", window.location.pathname);
+				}
+				frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => {
+					window.location.href = res.message || "/login";
+				});
+				return;
+			}
+
+			$btn.addClass('hidden');
+			$btn.closest('.cart-action-container').addClass('d-flex');
+			$btn.parent().find('.go-to-cart').removeClass('hidden');
+			$btn.parent().find('.go-to-cart-grid').removeClass('hidden');
+			$btn.parent().find('.cart-indicator').removeClass('hidden');
+
+			const item_code = $btn.data('item-code');
+			erpnext.e_commerce.shopping_cart.update_cart({
+				item_code,
+				qty: 1
+			});
+
+		});
+	},
+
+	freeze() {
+		if (window.location.pathname !== "/cart") return;
+
+		if (!$('#freeze').length) {
+			let freeze = $('<div id="freeze" class="modal-backdrop fade"></div>')
+				.appendTo("body");
+
+			setTimeout(function() {
+				freeze.addClass("show");
+			}, 1);
+		} else {
+			$("#freeze").addClass("show");
+		}
+	},
+
+	unfreeze() {
+		if ($('#freeze').length) {
+			let freeze = $('#freeze').removeClass("show");
+			setTimeout(function() {
+				freeze.remove();
+			}, 1);
+		}
 	}
 });
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 597d77c..08270bd 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -592,6 +592,6 @@
 		&& doc.fg_completed_qty
 		&& erpnext.stock.bom
 		&& erpnext.stock.bom.name === doc.bom_no;
-	const itemChecks = !!item;
+	const itemChecks = !!item  && !item.allow_alternative_item;
 	return docChecks && itemChecks;
 }
diff --git a/erpnext/public/js/wishlist.js b/erpnext/public/js/wishlist.js
new file mode 100644
index 0000000..f6599e9
--- /dev/null
+++ b/erpnext/public/js/wishlist.js
@@ -0,0 +1,204 @@
+frappe.provide("erpnext.e_commerce.wishlist");
+var wishlist = erpnext.e_commerce.wishlist;
+
+frappe.provide("erpnext.e_commerce.shopping_cart");
+var shopping_cart = erpnext.e_commerce.shopping_cart;
+
+$.extend(wishlist, {
+	set_wishlist_count: function(animate=false) {
+		// set badge count for wishlist icon
+		var wish_count = frappe.get_cookie("wish_count");
+		if (frappe.session.user==="Guest") {
+			wish_count = 0;
+		}
+
+		if (wish_count) {
+			$(".wishlist").toggleClass('hidden', false);
+		}
+
+		var $wishlist = $('.wishlist-icon');
+		var $badge = $wishlist.find("#wish-count");
+
+		if (parseInt(wish_count) === 0 || wish_count === undefined) {
+			$wishlist.css("display", "none");
+		} else {
+			$wishlist.css("display", "inline");
+		}
+		if (wish_count) {
+			$badge.html(wish_count);
+			if (animate) {
+				$wishlist.addClass('cart-animate');
+				setTimeout(() => {
+					$wishlist.removeClass('cart-animate');
+				}, 500);
+			}
+		} else {
+			$badge.remove();
+		}
+	},
+
+	bind_move_to_cart_action: function() {
+		// move item to cart from wishlist
+		$('.page_content').on("click", ".btn-add-to-cart", (e) => {
+			const $move_to_cart_btn = $(e.currentTarget);
+			let item_code = $move_to_cart_btn.data("item-code");
+
+			shopping_cart.shopping_cart_update({
+				item_code,
+				qty: 1,
+				cart_dropdown: true
+			});
+
+			let success_action = function() {
+				const $card_wrapper = $move_to_cart_btn.closest(".wishlist-card");
+				$card_wrapper.addClass("wish-removed");
+			};
+			let args = { item_code: item_code };
+			this.add_remove_from_wishlist("remove", args, success_action, null, true);
+		});
+	},
+
+	bind_remove_action: function() {
+		// remove item from wishlist
+		let me = this;
+
+		$('.page_content').on("click", ".remove-wish", (e) => {
+			const $remove_wish_btn = $(e.currentTarget);
+			let item_code = $remove_wish_btn.data("item-code");
+
+			let success_action = function() {
+				const $card_wrapper = $remove_wish_btn.closest(".wishlist-card");
+				$card_wrapper.addClass("wish-removed");
+				if (frappe.get_cookie("wish_count") == 0) {
+					$(".page_content").empty();
+					me.render_empty_state();
+				}
+			};
+			let args = { item_code: item_code };
+			this.add_remove_from_wishlist("remove", args, success_action);
+		});
+	},
+
+	bind_wishlist_action() {
+		// 'wish'('like') or 'unwish' item in product listing
+		$('.page_content').on('click', '.like-action, .like-action-list', (e) => {
+			const $btn = $(e.currentTarget);
+			this.wishlist_action($btn);
+		});
+	},
+
+	wishlist_action(btn) {
+		const $wish_icon = btn.find('.wish-icon');
+		let me = this;
+
+		if (frappe.session.user==="Guest") {
+			if (localStorage) {
+				localStorage.setItem("last_visited", window.location.pathname);
+			}
+			this.redirect_guest();
+			return;
+		}
+
+		let success_action = function() {
+			erpnext.e_commerce.wishlist.set_wishlist_count(true);
+		};
+
+		if ($wish_icon.hasClass('wished')) {
+			// un-wish item
+			btn.removeClass("like-animate");
+			btn.addClass("like-action-wished");
+			this.toggle_button_class($wish_icon, 'wished', 'not-wished');
+
+			let args = { item_code: btn.data('item-code') };
+			let failure_action = function() {
+				me.toggle_button_class($wish_icon, 'not-wished', 'wished');
+			};
+			this.add_remove_from_wishlist("remove", args, success_action, failure_action);
+		} else {
+			// wish item
+			btn.addClass("like-animate");
+			btn.addClass("like-action-wished");
+			this.toggle_button_class($wish_icon, 'not-wished', 'wished');
+
+			let args = {item_code: btn.data('item-code')};
+			let failure_action = function() {
+				me.toggle_button_class($wish_icon, 'wished', 'not-wished');
+			};
+			this.add_remove_from_wishlist("add", args, success_action, failure_action);
+		}
+	},
+
+	toggle_button_class(button, remove, add) {
+		button.removeClass(remove);
+		button.addClass(add);
+	},
+
+	add_remove_from_wishlist(action, args, success_action, failure_action, async=false) {
+		/*	AJAX call to add or remove Item from Wishlist
+			action: "add" or "remove"
+			args: args for method (item_code, price, formatted_price),
+			success_action: method to execute on successs,
+			failure_action: method to execute on failure,
+			async: make call asynchronously (true/false).	*/
+		if (frappe.session.user==="Guest") {
+			if (localStorage) {
+				localStorage.setItem("last_visited", window.location.pathname);
+			}
+			this.redirect_guest();
+		} else {
+			let method = "erpnext.e_commerce.doctype.wishlist.wishlist.add_to_wishlist";
+			if (action === "remove") {
+				method = "erpnext.e_commerce.doctype.wishlist.wishlist.remove_from_wishlist";
+			}
+
+			frappe.call({
+				async: async,
+				type: "POST",
+				method: method,
+				args: args,
+				callback: function (r) {
+					if (r.exc) {
+						if (failure_action && (typeof failure_action === 'function')) {
+							failure_action();
+						}
+						frappe.msgprint({
+							message: __("Sorry, something went wrong. Please refresh."),
+							indicator: "red", title: __("Note")
+						});
+					} else if (success_action && (typeof success_action === 'function')) {
+						success_action();
+					}
+				}
+			});
+		}
+	},
+
+	redirect_guest() {
+		frappe.call('erpnext.e_commerce.api.get_guest_redirect_on_action').then((res) => {
+			window.location.href = res.message || "/login";
+		});
+	},
+
+	render_empty_state() {
+		$(".page_content").append(`
+			<div class="cart-empty frappe-card">
+				<div class="cart-empty-state">
+					<img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
+				</div>
+				<div class="cart-empty-message mt-4">${ __('Wishlist is empty !') }</p>
+			</div>
+		`);
+	}
+
+});
+
+frappe.ready(function() {
+	if (window.location.pathname !== "/wishlist") {
+		$(".wishlist").toggleClass('hidden', true);
+		wishlist.set_wishlist_count();
+	} else {
+		wishlist.bind_move_to_cart_action();
+		wishlist.bind_remove_action();
+	}
+
+});
\ No newline at end of file
diff --git a/erpnext/public/scss/shopping_cart.scss b/erpnext/public/scss/shopping_cart.scss
index fef1e76..666043b 100644
--- a/erpnext/public/scss/shopping_cart.scss
+++ b/erpnext/public/scss/shopping_cart.scss
@@ -1,16 +1,17 @@
 @import "frappe/public/scss/common/mixins";
 
-body.product-page {
-	background: var(--gray-50);
+:root {
+	--green-info: #38A160;
+	--product-bg-color: white;
+	--body-bg-color:  var(--gray-50);
 }
 
+body.product-page {
+	background: var(--body-bg-color);
+}
 
 .item-breadcrumbs {
 	.breadcrumb-container {
-		ol.breadcrumb {
-			background-color: var(--gray-50) !important;
-		}
-
 		a {
 			color: var(--gray-900);
 		}
@@ -71,9 +72,21 @@
 	}
 }
 
+.no-image-item {
+	height: 340px;
+	width: 340px;
+	background: var(--gray-100);
+	border-radius: var(--border-radius);
+	font-size: 2rem;
+	color: var(--gray-500);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
 .item-card-group-section {
 	.card {
-		height: 360px;
+		height: 100%;
 		align-items: center;
 		justify-content: center;
 
@@ -83,6 +96,19 @@
 		}
 	}
 
+	.card:hover, .card:focus-within {
+		.btn-add-to-cart-list {
+			visibility: visible;
+		}
+		.like-action {
+			visibility: visible;
+		}
+		.btn-explore-variants {
+			visibility: visible;
+		}
+	}
+
+
 	.card-img-container {
 		height: 210px;
 		width: 100%;
@@ -96,14 +122,28 @@
 
 	.no-image {
 		@include flex(flex, center, center, null);
-		height: 200px;
-		margin: 0 auto;
-		margin-top: var(--margin-xl);
+		height: 220px;
 		background: var(--gray-100);
-		width: 80%;
+		width: 100%;
+		border-radius: var(--border-radius) var(--border-radius) 0 0;
+		font-size: 2rem;
+		color: var(--gray-500);
+	}
+
+	.no-image-list {
+		@include flex(flex, center, center, null);
+		height: 150px;
+		background: var(--gray-100);
 		border-radius: var(--border-radius);
 		font-size: 2rem;
 		color: var(--gray-500);
+		margin-top: 15px;
+		margin-bottom: 15px;
+	}
+
+	.card-body-flex {
+		display: flex;
+		flex-direction: column;
 	}
 
 	.product-title {
@@ -136,15 +176,75 @@
 		font-weight: 600;
 		color: var(--text-color);
 		margin: var(--margin-sm) 0;
+		margin-bottom: auto !important;
+
+		.striked-price {
+			font-weight: 500;
+			font-size: 15px;
+			color: var(--gray-500);
+		}
+	}
+
+	.product-info-green {
+		color: var(--green-info);
+		font-weight: 600;
 	}
 
 	.item-card {
 		padding: var(--padding-sm);
+		min-width: 300px;
+	}
+
+	.wishlist-card {
+		padding: var(--padding-sm);
+		min-width: 260px;
+		.card-body-flex {
+			display: flex;
+			flex-direction: column;
+		}
+	}
+}
+
+#products-list-area, #products-grid-area {
+	padding: 0 5px;
+}
+
+.list-row {
+	background-color: white;
+	padding-bottom: 1rem;
+	padding-top: 1.5rem !important;
+	border-radius: 8px;
+	border-bottom: 1px solid var(--gray-50);
+
+	&:hover, &:focus-within {
+		box-shadow: 0px 16px 60px rgba(0, 0, 0, 0.08), 0px 8px 30px -20px rgba(0, 0, 0, 0.04);
+		transition: box-shadow 400ms;
+
+		.btn-add-to-cart-list {
+			visibility: visible;
+		}
+		.like-action-list {
+			visibility: visible;
+		}
+		.btn-explore-variants {
+			visibility: visible;
+		}
+	}
+
+	.product-code {
+		padding-top: 0 !important;
+	}
+
+	.btn-explore-variants {
+		min-width: 135px;
+		max-height: 30px;
+		float: right;
+		padding: 0.25rem 1rem;
 	}
 }
 
 [data-doctype="Item Group"],
-#page-all-products {
+#page-index {
 	.page-header {
 		font-size: 20px;
 		font-weight: 700;
@@ -184,28 +284,76 @@
 	}
 }
 
+.product-filter {
+	width: 14px !important;
+	height: 14px !important;
+}
+
+.discount-filter {
+	&:before {
+		width: 14px !important;
+		height: 14px !important;
+	}
+}
+
+.list-image {
+	border: none !important;
+	overflow: hidden;
+	max-height: 200px;
+	background-color: white;
+}
+
 .product-container {
 	@include card($padding: var(--padding-md));
-	min-height: 70vh;
+	background-color: var(--product-bg-color) !important;
+	min-height: fit-content;
 
 	.product-details {
-		max-width: 40%;
-		margin-left: -30px;
+		max-width: 50%;
 
 		.btn-add-to-cart {
-			font-size: var(--text-base);
+			font-size: 14px;
+		}
+	}
+
+	&.item-main {
+		.product-image {
+			width: 100%;
+		}
+	}
+
+	.expand {
+		max-width: 100% !important; // expand in absence of slideshow
+	}
+
+	@media (max-width: 789px) {
+		.product-details {
+			max-width: 90% !important;
+
+			.btn-add-to-cart {
+				font-size: 14px;
+			}
+		}
+	}
+
+	.btn-add-to-wishlist {
+		svg use {
+			--icon-stroke: #F47A7A;
+		}
+	}
+
+	.btn-view-in-wishlist {
+		svg use {
+			fill: #F47A7A;
+			--icon-stroke: none;
 		}
 	}
 
 	.product-title {
-		font-size: 24px;
+		font-size: 16px;
 		font-weight: 600;
 		color: var(--text-color);
-	}
-
-	.product-code {
-		color: var(--text-muted);
-		font-size: 13px;
+		padding: 0 !important;
 	}
 
 	.product-description {
@@ -242,7 +390,7 @@
 			max-height: 430px;
 		}
 
-		overflow: scroll;
+		overflow: auto;
 	}
 
 	.item-slideshow-image {
@@ -261,29 +409,116 @@
 
 	.item-cart {
 		.product-price {
-			font-size: 20px;
+			font-size: 22px;
 			color: var(--text-color);
 			font-weight: 600;
 
 			.formatted-price {
 				color: var(--text-muted);
-				font-size: var(--text-base);
+				font-size: 14px;
 			}
 		}
 
 		.no-stock {
 			font-size: var(--text-base);
 		}
+
+		.offers-heading {
+			font-size: 16px !important;
+			color: var(--text-color);
+			.tag-icon {
+				--icon-stroke: var(--gray-500);
+			}
+		}
+
+		.w-30-40 {
+			width: 30%;
+
+			@media (max-width: 992px) {
+				width: 40%;
+			}
+		}
+	}
+
+	.tab-content {
+		font-size: 14px;
+	}
+}
+
+// Item Recommendations
+.recommended-item-section {
+	padding-right: 0;
+
+	.recommendation-header {
+		font-size: 16px;
+		font-weight: 500
+	}
+
+	.recommendation-container {
+		padding: .5rem;
+		min-height: 0px;
+
+		.r-item-image {
+			min-height: 100px;
+			width: 40%;
+
+			.r-product-image {
+				padding: 2px 15px;
+			}
+
+			.no-image-r-item {
+				display: flex; justify-content: center;
+				background-color: var(--gray-200);
+				align-items: center;
+				color: var(--gray-400);
+				margin-top: .15rem;
+				border-radius: 6px;
+				height: 100%;
+				font-size: 24px;
+			}
+		}
+
+		.r-item-info {
+			font-size: 14px;
+			padding-right: 0;
+			padding-left: 10px;
+			width: 60%;
+
+			a {
+				color: var(--gray-800);
+				font-weight: 400;
+			}
+
+			.item-price {
+				font-size: 15px;
+				font-weight: 600;
+				color: var(--text-color);
+			}
+
+			.striked-item-price {
+				font-weight: 500;
+				color: var(--gray-500);
+			}
+		}
+	}
+}
+
+.product-code {
+	padding: .5rem 0;
+	color: var(--text-muted);
+	font-size: 14px;
+	.product-item-group {
+		padding-right: .25rem;
+		border-right: solid 1px var(--text-muted);
+	}
+
+	.product-item-code {
+		padding-left: .5rem;
 	}
 }
 
 .item-configurator-dialog {
-	.modal-header {
-		padding: var(--padding-md) var(--padding-xl);
-	}
-
 	.modal-body {
-		padding: 0 var(--padding-xl);
 		padding-bottom: var(--padding-xl);
 
 		.status-area {
@@ -323,20 +558,73 @@
 	}
 }
 
-.cart-icon {
-	.cart-badge {
-		position: relative;
-		top: -10px;
-		left: -12px;
-		background: var(--red-600);
-		width: 16px;
-		align-items: center;
-		height: 16px;
-		font-size: 10px;
-		border-radius: 50%;
+.sub-category-container {
+	padding-bottom: .5rem;
+	margin-bottom: 1.25rem;
+	border-bottom: 1px solid var(--table-border-color);
+
+	.heading {
+		color: var(--gray-500);
 	}
 }
 
+.scroll-categories {
+	white-space: nowrap;
+	overflow-x: auto;
+
+	.category-pill {
+		margin: 0px 4px;
+		display: inline-block;
+		padding: 6px 12px;
+		background-color: #ecf5fe;
+		width: fit-content;
+		font-size: 14px;
+		border-radius: 18px;
+		color: var(--blue-500);
+	}
+}
+
+
+.shopping-badge {
+	position: relative;
+	top: -10px;
+	left: -12px;
+	background: var(--red-600);
+	align-items: center;
+	height: 16px;
+	font-size: 10px;
+	border-radius: 50%;
+}
+
+
+.cart-animate {
+	animation: wiggle 0.5s linear;
+}
+@keyframes wiggle {
+	8%,
+	41% {
+		transform: translateX(-10px);
+	}
+	25%,
+	58% {
+		transform: translate(10px);
+	}
+	75% {
+		transform: translate(-5px);
+	}
+	92% {
+		transform: translate(5px);
+	}
+	0%,
+	100% {
+		transform: translate(0);
+	}
+}
+
+.total-discount {
+	font-size: 14px;
+	color: var(--primary-color) !important;
+}
 
 #page-cart {
 	.shopping-cart-header {
@@ -350,6 +638,7 @@
 			display: flex;
 			flex-direction: column;
 			justify-content: space-between;
+			height: fit-content;
 		}
 
 		.cart-items-header {
@@ -357,6 +646,10 @@
 		}
 
 		.cart-table {
+			tr {
+				margin-bottom: 1rem;
+			}
+
 			th, tr, td {
 				border-color: var(--border-color);
 				border-width: 1px;
@@ -374,71 +667,200 @@
 				color: var(--text-color);
 			}
 
+			.cart-item-image {
+				width: 20%;
+				min-width: 100px;
+				img {
+					max-height: 112px;
+				}
+			}
+
 			.cart-items {
 				.item-title {
-					font-size: var(--text-base);
+					width: 80%;
+					font-size: 14px;
 					font-weight: 500;
 					color: var(--text-color);
 				}
 
 				.item-subtitle {
 					color: var(--text-muted);
-					font-size: var(--text-md);
+					font-size: 13px;
 				}
 
 				.item-subtotal {
-					font-size: var(--text-base);
+					font-size: 14px;
 					font-weight: 500;
 				}
 
+				.sm-item-subtotal {
+					font-size: 14px;
+					font-weight: 500;
+					display: none;
+
+					@media (max-width: 992px) {
+						display: unset !important;
+					}
+				}
+
 				.item-rate {
-					font-size: var(--text-md);
+					font-size: 13px;
 					color: var(--text-muted);
 				}
 
-				textarea {
-					width: 40%;
+				.free-tag {
+					padding: 4px 8px;
+					border-radius: 4px;
+					background-color: var(--dark-green-50);
 				}
+
+				textarea {
+					width: 80%;
+					height: 60px;
+					font-size: 14px;
+				}
+
 			}
 
 			.cart-tax-items {
 				.item-grand-total {
 					font-size: 16px;
-					font-weight: 600;
+					font-weight: 700;
 					color: var(--text-color);
 				}
 			}
+
+			.column-sm-view {
+				@media (max-width: 992px) {
+					display: none !important;
+				}
+			}
+
+			.item-column {
+				width: 50%;
+				@media (max-width: 992px) {
+					width: 70%;
+				}
+			}
+
+			.remove-cart-item {
+				border-radius: 6px;
+				border: 1px solid var(--gray-100);
+				width: 28px;
+				height: 28px;
+				font-weight: 300;
+				color: var(--gray-700);
+				background-color: var(--gray-100);
+				float: right;
+				cursor: pointer;
+				margin-top: .25rem;
+				justify-content: center;
+			}
+
+			.remove-cart-item-logo {
+				margin-top: 2px;
+				margin-left: 2.2px;
+				fill: var(--gray-700) !important;
+			}
 		}
 
-		.cart-addresses {
+		.cart-payment-addresses {
 			hr {
 				border-color: var(--border-color);
 			}
 		}
 
+		.payment-summary {
+			h6 {
+				padding-bottom: 1rem;
+				border-bottom: solid 1px var(--gray-200);
+			}
+
+			table {
+				font-size: 14px;
+				td {
+					padding: 0;
+					padding-top: 0.35rem !important;
+					border: none !important;
+				}
+
+				&.grand-total {
+					border-top: solid 1px var(--gray-200);
+				}
+			}
+
+			.bill-label {
+				color: var(--gray-600);
+			}
+
+			.bill-content {
+				font-weight: 500;
+				&.net-total {
+					font-size: 16px;
+					font-weight: 600;
+				}
+			}
+
+			.btn-coupon-code {
+				font-size: 14px;
+				border: dashed 1px var(--gray-400);
+				box-shadow: none;
+			}
+		}
+
 		.number-spinner {
 			width: 75%;
+			min-width: 105px;
 			.cart-btn {
 				border: none;
 				background: var(--gray-100);
 				box-shadow: none;
+				width: 24px;
 				height: 28px;
 				align-items: center;
+				justify-content: center;
 				display: flex;
+				font-size: 20px;
+				font-weight: 300;
+				color: var(--gray-700);
 			}
 
 			.cart-qty {
 				height: 28px;
-				font-size: var(--text-md);
+				font-size: 13px;
+				&:disabled {
+					background: var(--gray-100);
+					opacity: 0.65;
+				}
 			}
 		}
 
 		.place-order-container {
 			.btn-place-order {
-				width: 62%;
+				float: right;
 			}
 		}
 	}
+
+	.t-and-c-container {
+		padding: 1.5rem;
+	}
+
+	.t-and-c-terms {
+		font-size: 14px;
+	}
+}
+
+.no-image-cart-item {
+	max-height: 112px;
+	display: flex; justify-content: center;
+	background-color: var(--gray-200);
+	align-items: center;
+	color: var(--gray-400);
+	margin-top: .15rem;
+	border-radius: 6px;
+	height: 100%;
+	font-size: 24px;
 }
 
 .cart-empty.frappe-card {
@@ -454,7 +876,7 @@
 
 .address-card {
 	.card-title {
-		font-size: var(--text-base);
+		font-size: 14px;
 		font-weight: 500;
 	}
 
@@ -463,27 +885,37 @@
 	}
 
 	.card-text {
-		font-size: var(--text-md);
+		font-size: 13px;
 		color: var(--gray-700);
 	}
 
 	.card-link {
-		font-size: var(--text-md);
+		font-size: 13px;
 
 		svg use {
-			stroke: var(--blue-500);
+			stroke: var(--primary-color);
 		}
 	}
 
 	.btn-change-address {
-		color: var(--blue-500);
+		border: 1px solid var(--primary-color);
+		color: var(--primary-color);
+		box-shadow: none;
 	}
 }
 
+.address-header {
+	margin-top: .15rem;padding: 0;
+}
+
+.btn-new-address {
+	float: right;
+	font-size: 15px !important;
+	color: var(--primary-color) !important;
+}
+
 .btn-new-address:hover, .btn-change-address:hover {
-	box-shadow: none;
-	color: var(--blue-500) !important;
-	border: 1px solid var(--blue-500);
+	color: var(--primary-color) !important;
 }
 
 .modal .address-card {
@@ -493,3 +925,451 @@
 		border: 1px solid var(--dark-border-color);
 	}
 }
+
+.cart-indicator {
+	position: absolute;
+	text-align: center;
+	width: 22px;
+	height: 22px;
+	left: calc(100% - 40px);
+	top: 22px;
+
+	border-radius: 66px;
+	box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+	background: white;
+	color: var(--primary-color);
+	font-size: 14px;
+
+	&.list-indicator {
+		position: unset;
+		margin-left: auto;
+	}
+}
+
+
+.like-action {
+	visibility: hidden;
+	text-align: center;
+	position: absolute;
+	cursor: pointer;
+	width: 28px;
+	height: 28px;
+	left: 20px;
+	top: 20px;
+
+	/* White */
+	background: white;
+	box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+	border-radius: 66px;
+
+	&.like-action-wished {
+		visibility: visible !important;
+	}
+
+	@media (max-width: 992px) {
+		visibility: visible !important;
+	}
+}
+
+.like-action-list {
+	visibility: hidden;
+	text-align: center;
+	position: absolute;
+	cursor: pointer;
+	width: 28px;
+	height: 28px;
+	left: 20px;
+	top: 0;
+
+	/* White */
+	background: white;
+	box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+	border-radius: 66px;
+
+	&.like-action-wished {
+		visibility: visible !important;
+	}
+
+	@media (max-width: 992px) {
+		visibility: visible !important;
+	}
+}
+
+.like-action-item-fp {
+	visibility: visible !important;
+	position: unset;
+	float: right;
+}
+
+.like-animate {
+	animation: expand cubic-bezier(0.04, 0.4, 0.5, 0.95) 1.6s forwards 1;
+}
+
+@keyframes expand {
+	30% {
+	  transform: scale(1.3);
+	}
+	50% {
+	  transform: scale(0.8);
+	}
+	70% {
+		transform: scale(1.1);
+	}
+	100% {
+	  transform: scale(1);
+	}
+  }
+
+.not-wished {
+	cursor: pointer;
+	--icon-stroke: #F47A7A !important;
+
+	&:hover {
+		fill: #F47A7A;
+	}
+}
+
+.wished {
+	--icon-stroke: none;
+	fill: #F47A7A !important;
+}
+
+.list-row-checkbox {
+	&:before {
+		display: none;
+	}
+
+	&:checked:before {
+		display: block;
+		z-index: 1;
+	}
+}
+
+#pay-for-order {
+	padding: .5rem 1rem; // Pay button in SO
+}
+
+.btn-explore-variants {
+	visibility: hidden;
+	box-shadow: none;
+	margin: var(--margin-sm) 0;
+	width: 90px;
+	max-height: 50px; // to avoid resizing on window resize
+	flex: none;
+	transition: 0.3s ease;
+
+	color: white;
+	background-color: var(--orange-500);
+	border: 1px solid var(--orange-500);
+	font-size: 13px;
+
+	&:hover {
+		color: white;
+	}
+}
+
+.btn-add-to-cart-list{
+	visibility: hidden;
+	box-shadow: none;
+	margin: var(--margin-sm) 0;
+	// margin-top: auto !important;
+	max-height: 50px; // to avoid resizing on window resize
+	flex: none;
+	transition: 0.3s ease;
+
+	font-size: 13px;
+
+	&:hover {
+		color: white;
+	}
+
+	@media (max-width: 992px) {
+		visibility: visible !important;
+	}
+}
+
+.go-to-cart-grid {
+	max-height: 30px;
+	margin-top: 1rem !important;
+}
+
+.go-to-cart {
+	max-height: 30px;
+	float: right;
+}
+
+.remove-wish {
+	background-color: white;
+	position: absolute;
+	cursor: pointer;
+	top:10px;
+	right: 20px;
+	width: 32px;
+	height: 32px;
+
+	border-radius: 50%;
+	border: 1px solid var(--gray-100);
+	box-shadow: 0px 2px 6px rgba(17, 43, 66, 0.08), 0px 1px 4px rgba(17, 43, 66, 0.1);
+}
+
+.wish-removed {
+	display: none;
+}
+
+.item-website-specification {
+	font-size: .875rem;
+	.product-title {
+		font-size: 18px;
+	}
+
+	.table {
+		width: 70%;
+	}
+
+	td {
+		border: none !important;
+	}
+
+	.spec-label {
+		color: var(--gray-600);
+	}
+
+	.spec-content {
+		color: var(--gray-800);
+	}
+}
+
+.reviews-full-page {
+	padding: 1rem 2rem;
+}
+
+.ratings-reviews-section {
+	border-top: 1px solid #E2E6E9;
+	padding: .5rem 1rem;
+}
+
+.reviews-header {
+	font-size: 20px;
+	font-weight: 600;
+	color: var(--gray-800);
+	display: flex;
+	align-items: center;
+	padding: 0;
+}
+
+.btn-write-review {
+	float: right;
+	padding: .5rem 1rem;
+	font-size: 14px;
+	font-weight: 400;
+	border: none !important;
+	box-shadow: none;
+
+	color: var(--gray-900);
+	background-color: var(--gray-100);
+
+	&:hover {
+		box-shadow: var(--btn-shadow);
+	}
+}
+
+.btn-view-more {
+	font-size: 14px;
+}
+
+.rating-summary-section {
+	display: flex;
+}
+
+.rating-summary-title {
+	margin-top: 0.15rem;
+	font-size: 18px;
+}
+
+.rating-summary-numbers {
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+
+	border-right: solid 1px var(--gray-100);
+}
+
+.user-review-title {
+	margin-top: 0.15rem;
+	font-size: 15px;
+	font-weight: 600;
+}
+
+.rating {
+	--star-fill: var(--gray-300);
+	.star-hover {
+		--star-fill: var(--yellow-100);
+	}
+	.star-click {
+		--star-fill: var(--yellow-300);
+	}
+}
+
+.ratings-pill {
+	background-color: var(--gray-100);
+	padding: .5rem 1rem;
+	border-radius: 66px;
+}
+
+.review {
+	max-width: 80%;
+	line-height: 1.6;
+	padding-bottom: 0.5rem;
+	border-bottom: 1px solid #E2E6E9;
+}
+
+.review-signature {
+	display: flex;
+	font-size: 13px;
+	color: var(--gray-500);
+	font-weight: 400;
+
+	.reviewer {
+		padding-right: 8px;
+		color: var(--gray-600);
+	}
+}
+
+.rating-progress-bar-section {
+	padding-bottom: 2rem;
+
+	.rating-bar-title {
+		margin-left: -15px;
+	}
+
+	.rating-progress-bar {
+		margin-bottom: 4px;
+		height: 7px;
+		margin-top: 6px;
+
+		.progress-bar-cosmetic {
+			background-color: var(--gray-600);
+			border-radius: var(--border-radius);
+		}
+	}
+}
+
+.offer-container {
+	font-size: 14px;
+}
+
+#search-results-container {
+	border: 1px solid var(--gray-200);
+	padding: .25rem 1rem;
+
+	.category-chip {
+		background-color: var(--gray-100);
+		border: none !important;
+		box-shadow: none;
+	}
+
+	.recent-search {
+		padding: .5rem .5rem;
+		border-radius: var(--border-radius);
+
+		&:hover {
+			background-color: var(--gray-100);
+		}
+	}
+}
+
+#search-box {
+	background-color: white;
+	height: 100%;
+	padding-left: 2.5rem;
+	border: 1px solid var(--gray-200);
+}
+
+.search-icon {
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 2.5rem;
+	height: 100%;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	padding-bottom: 1px;
+}
+
+#toggle-view {
+	float: right;
+
+	.btn-primary {
+		background-color: var(--gray-600);
+		box-shadow: 0 0 0 0.2rem var(--gray-400);
+	}
+}
+
+.placeholder-div {
+	height:80%;
+	width: -webkit-fill-available;
+	padding: 50px;
+	text-align: center;
+	background-color: #F9FAFA;
+	border-top-left-radius: calc(0.75rem - 1px);
+	border-top-right-radius: calc(0.75rem - 1px);
+}
+.placeholder {
+	font-size: 72px;
+}
+
+[data-path="cart"] {
+	.modal-backdrop {
+		background-color: var(--gray-50); // lighter backdrop only on cart freeze
+	}
+}
+
+.item-thumb {
+	height: 50px;
+	max-width: 80px;
+	min-width: 80px;
+	object-fit: cover;
+}
+
+.brand-line {
+	color: gray;
+}
+
+.btn-next, .btn-prev {
+	font-size: 14px;
+}
+
+.alert-error {
+	color: #e27a84;
+	background-color: #fff6f7;
+	border-color: #f5c6cb;
+}
+
+.font-md {
+	font-size: 14px !important;
+}
+
+.in-green {
+	color: var(--green-info) !important;
+	font-weight: 500;
+}
+
+.has-stock {
+	font-weight: 400 !important;
+}
+
+.out-of-stock {
+	font-weight: 400;
+	font-size: 14px;
+	line-height: 20px;
+	color: #F47A7A;
+}
+
+.mt-minus-2 {
+	margin-top: -2rem;
+}
+
+.mt-minus-1 {
+	margin-top: -1rem;
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py
similarity index 100%
rename from erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py
rename to erpnext/regional/doctype/e_invoice_request_log/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
new file mode 100644
index 0000000..7b7ba96
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Request Log', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
new file mode 100644
index 0000000..3034370
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
@@ -0,0 +1,102 @@
+{
+ "actions": [],
+ "autoname": "EINV-REQ-.#####",
+ "creation": "2020-12-08 12:54:08.175992",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "user",
+  "url",
+  "headers",
+  "response",
+  "column_break_7",
+  "timestamp",
+  "reference_invoice",
+  "data"
+ ],
+ "fields": [
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "label": "User",
+   "options": "User"
+  },
+  {
+   "fieldname": "reference_invoice",
+   "fieldtype": "Data",
+   "label": "Reference Invoice"
+  },
+  {
+   "fieldname": "headers",
+   "fieldtype": "Code",
+   "label": "Headers",
+   "options": "JSON"
+  },
+  {
+   "fieldname": "data",
+   "fieldtype": "Code",
+   "label": "Data",
+   "options": "JSON"
+  },
+  {
+   "default": "Now",
+   "fieldname": "timestamp",
+   "fieldtype": "Datetime",
+   "label": "Timestamp"
+  },
+  {
+   "fieldname": "response",
+   "fieldtype": "Code",
+   "label": "Response",
+   "options": "JSON"
+  },
+  {
+   "fieldname": "url",
+   "fieldtype": "Data",
+   "label": "URL"
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-01-13 12:06:57.253111",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Request Log",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
similarity index 75%
copy from erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
copy to erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
index dcf0e3b..c89552d 100644
--- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
@@ -1,10 +1,10 @@
+# -*- coding: utf-8 -*-
 # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-
 # import frappe
 from frappe.model.document import Document
 
 
-class DistributedCostCenter(Document):
+class EInvoiceRequestLog(Document):
 	pass
diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
new file mode 100644
index 0000000..091cc88e
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+
+class TestEInvoiceRequestLog(unittest.TestCase):
+	pass
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py
similarity index 100%
copy from erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py
copy to erpnext/regional/doctype/e_invoice_settings/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
new file mode 100644
index 0000000..54e4886
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Settings', {
+	refresh(frm) {
+		const docs_link = 'https://docs.erpnext.com/docs/v13/user/manual/en/regional/india/setup-e-invoicing';
+		frm.dashboard.set_headline(
+			__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
+		);
+	}
+});
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
new file mode 100644
index 0000000..16b2963
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -0,0 +1,97 @@
+{
+ "actions": [],
+ "creation": "2020-09-24 16:23:16.235722",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "enable",
+  "section_break_2",
+  "sandbox_mode",
+  "applicable_from",
+  "credentials",
+  "advanced_settings_section",
+  "client_id",
+  "column_break_8",
+  "client_secret",
+  "auth_token",
+  "token_expiry"
+ ],
+ "fields": [
+  {
+   "default": "0",
+   "fieldname": "enable",
+   "fieldtype": "Check",
+   "label": "Enable"
+  },
+  {
+   "depends_on": "enable",
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "auth_token",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "token_expiry",
+   "fieldtype": "Datetime",
+   "hidden": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "credentials",
+   "fieldtype": "Table",
+   "label": "Credentials",
+   "mandatory_depends_on": "enable",
+   "options": "E Invoice User"
+  },
+  {
+   "default": "0",
+   "fieldname": "sandbox_mode",
+   "fieldtype": "Check",
+   "label": "Sandbox Mode"
+  },
+  {
+   "fieldname": "applicable_from",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Applicable From",
+   "reqd": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "advanced_settings_section",
+   "fieldtype": "Section Break",
+   "label": "Advanced Settings"
+  },
+  {
+   "fieldname": "client_id",
+   "fieldtype": "Data",
+   "label": "Client ID"
+  },
+  {
+   "fieldname": "client_secret",
+   "fieldtype": "Password",
+   "label": "Client Secret"
+  },
+  {
+   "fieldname": "column_break_8",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2021-11-16 19:50:28.029517",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Settings",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
new file mode 100644
index 0000000..342d583
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, 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 EInvoiceSettings(Document):
+	def validate(self):
+		if self.enable and not self.credentials:
+			frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.'))
diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
new file mode 100644
index 0000000..10770de
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+
+class TestEInvoiceSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py
similarity index 100%
copy from erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py
copy to erpnext/regional/doctype/e_invoice_user/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
new file mode 100644
index 0000000..a65b1ca
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
@@ -0,0 +1,57 @@
+{
+ "actions": [],
+ "creation": "2020-12-22 15:02:46.229474",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "company",
+  "gstin",
+  "username",
+  "password"
+ ],
+ "fields": [
+  {
+   "fieldname": "gstin",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "GSTIN",
+   "reqd": 1
+  },
+  {
+   "fieldname": "username",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Username",
+   "reqd": 1
+  },
+  {
+   "fieldname": "password",
+   "fieldtype": "Password",
+   "in_list_view": 1,
+   "label": "Password",
+   "reqd": 1
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-03-22 12:16:56.365616",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice User",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
similarity index 76%
rename from erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
rename to erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
index dcf0e3b..4e0e89c 100644
--- a/erpnext/accounts/doctype/distributed_cost_center/distributed_cost_center.py
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
@@ -1,10 +1,10 @@
+# -*- coding: utf-8 -*-
 # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
-
 # import frappe
 from frappe.model.document import Document
 
 
-class DistributedCostCenter(Document):
+class EInvoiceUser(Document):
 	pass
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index d48cd67..cb79cf8 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -295,6 +295,10 @@
 		inter_state_supply_details = {}
 
 		for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
+			gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
+			place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply') or '00-Other Territory'
+			export_type = self.invoice_detail_map.get(inv, {}).get('export_type')
+
 			for rate, items in items_based_on_rate.items():
 				for item_code, taxable_value in self.invoice_items.get(inv).items():
 					if item_code in items:
@@ -302,9 +306,8 @@
 							self.report_dict['sup_details']['osup_nil_exmp']['txval'] += taxable_value
 						elif item_code in self.is_non_gst:
 							self.report_dict['sup_details']['osup_nongst']['txval'] += taxable_value
-						elif rate == 0:
+						elif rate == 0 or (gst_category == 'Overseas' and export_type == 'Without Payment of Tax'):
 							self.report_dict['sup_details']['osup_zero']['txval'] += taxable_value
-							#self.report_dict['sup_details']['osup_zero'][key] += tax_amount
 						else:
 							if inv in self.cgst_sgst_invoices:
 								tax_rate = rate/2
@@ -315,9 +318,6 @@
 								self.report_dict['sup_details']['osup_det']['iamt'] += (taxable_value * rate /100)
 								self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
 
-								gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
-								place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply') or '00-Other Territory'
-
 								if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \
 								self.gst_details.get("gst_state") != place_of_supply.split("-")[1]:
 									inter_state_supply_details.setdefault((gst_category, place_of_supply), {
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js
deleted file mode 100644
index 54cde9c..0000000
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.js
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Tax Exemption 80G Certificate', {
-	refresh: function(frm) {
-		if (frm.doc.donor) {
-			frm.set_query('donation', function() {
-				return {
-					filters: {
-						docstatus: 1,
-						donor: frm.doc.donor
-					}
-				};
-			});
-		}
-	},
-
-	recipient: function(frm) {
-		if (frm.doc.recipient === 'Donor') {
-			frm.set_value({
-				'member': '',
-				'member_name': '',
-				'member_email': '',
-				'member_pan_number': '',
-				'fiscal_year': '',
-				'total': 0,
-				'payments': []
-			});
-		} else {
-			frm.set_value({
-				'donor': '',
-				'donor_name': '',
-				'donor_email': '',
-				'donor_pan_number': '',
-				'donation': '',
-				'date_of_donation': '',
-				'amount': 0,
-				'mode_of_payment': '',
-				'razorpay_payment_id': ''
-			});
-		}
-	},
-
-	get_payments: function(frm) {
-		frm.call({
-			doc: frm.doc,
-			method: 'get_payments',
-			freeze: true
-		});
-	},
-
-	company: function(frm) {
-		if ((frm.doc.member || frm.doc.donor) && frm.doc.company) {
-			frm.call({
-				doc: frm.doc,
-				method: 'set_company_address',
-				freeze: true
-			});
-		}
-	},
-
-	donation: function(frm) {
-		if (frm.doc.recipient === 'Donor' && !frm.doc.donor) {
-			frappe.msgprint(__('Please select donor first'));
-		}
-	}
-});
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json
deleted file mode 100644
index 9eee722..0000000
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.json
+++ /dev/null
@@ -1,297 +0,0 @@
-{
- "actions": [],
- "autoname": "naming_series:",
- "creation": "2021-02-15 12:37:21.577042",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "naming_series",
-  "recipient",
-  "member",
-  "member_name",
-  "member_email",
-  "member_pan_number",
-  "donor",
-  "donor_name",
-  "donor_email",
-  "donor_pan_number",
-  "column_break_4",
-  "date",
-  "fiscal_year",
-  "section_break_11",
-  "company",
-  "company_address",
-  "company_address_display",
-  "column_break_14",
-  "company_pan_number",
-  "company_80g_number",
-  "company_80g_wef",
-  "title",
-  "section_break_6",
-  "get_payments",
-  "payments",
-  "total",
-  "donation_details_section",
-  "donation",
-  "date_of_donation",
-  "amount",
-  "column_break_27",
-  "mode_of_payment",
-  "razorpay_payment_id"
- ],
- "fields": [
-  {
-   "fieldname": "recipient",
-   "fieldtype": "Select",
-   "in_list_view": 1,
-   "label": "Certificate Recipient",
-   "options": "Member\nDonor",
-   "reqd": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Member\";",
-   "fieldname": "member",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Member",
-   "mandatory_depends_on": "eval:doc.recipient === \"Member\";",
-   "options": "Member"
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Member\";",
-   "fetch_from": "member.member_name",
-   "fieldname": "member_name",
-   "fieldtype": "Data",
-   "label": "Member Name",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Donor\";",
-   "fieldname": "donor",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Donor",
-   "mandatory_depends_on": "eval:doc.recipient === \"Donor\";",
-   "options": "Donor"
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "date",
-   "fieldtype": "Date",
-   "label": "Date",
-   "reqd": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Member\";",
-   "fieldname": "section_break_6",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "payments",
-   "fieldtype": "Table",
-   "label": "Payments",
-   "options": "Tax Exemption 80G Certificate Detail"
-  },
-  {
-   "fieldname": "total",
-   "fieldtype": "Currency",
-   "in_list_view": 1,
-   "label": "Total",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Member\";",
-   "fieldname": "fiscal_year",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Fiscal Year",
-   "options": "Fiscal Year"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "label": "Company",
-   "options": "Company",
-   "reqd": 1
-  },
-  {
-   "fieldname": "get_payments",
-   "fieldtype": "Button",
-   "label": "Get Memberships"
-  },
-  {
-   "fieldname": "naming_series",
-   "fieldtype": "Select",
-   "label": "Naming Series",
-   "options": "NPO-80G-.YYYY.-"
-  },
-  {
-   "fieldname": "section_break_11",
-   "fieldtype": "Section Break",
-   "label": "Company Details"
-  },
-  {
-   "fieldname": "company_address",
-   "fieldtype": "Link",
-   "label": "Company Address",
-   "options": "Address"
-  },
-  {
-   "fieldname": "column_break_14",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fetch_from": "company.pan_details",
-   "fieldname": "company_pan_number",
-   "fieldtype": "Data",
-   "label": "PAN Number",
-   "read_only": 1
-  },
-  {
-   "fieldname": "company_address_display",
-   "fieldtype": "Small Text",
-   "hidden": 1,
-   "label": "Company Address Display",
-   "print_hide": 1,
-   "read_only": 1
-  },
-  {
-   "fetch_from": "company.company_80g_number",
-   "fieldname": "company_80g_number",
-   "fieldtype": "Data",
-   "label": "80G Number",
-   "read_only": 1
-  },
-  {
-   "fetch_from": "company.with_effect_from",
-   "fieldname": "company_80g_wef",
-   "fieldtype": "Date",
-   "label": "80G With Effect From",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Donor\";",
-   "fieldname": "donation_details_section",
-   "fieldtype": "Section Break",
-   "label": "Donation Details"
-  },
-  {
-   "fieldname": "donation",
-   "fieldtype": "Link",
-   "label": "Donation",
-   "mandatory_depends_on": "eval:doc.recipient === \"Donor\";",
-   "options": "Donation"
-  },
-  {
-   "fetch_from": "donation.amount",
-   "fieldname": "amount",
-   "fieldtype": "Currency",
-   "label": "Amount",
-   "read_only": 1
-  },
-  {
-   "fetch_from": "donation.mode_of_payment",
-   "fieldname": "mode_of_payment",
-   "fieldtype": "Link",
-   "label": "Mode of Payment",
-   "options": "Mode of Payment",
-   "read_only": 1
-  },
-  {
-   "fetch_from": "donation.razorpay_payment_id",
-   "fieldname": "razorpay_payment_id",
-   "fieldtype": "Data",
-   "label": "RazorPay Payment ID",
-   "read_only": 1
-  },
-  {
-   "fetch_from": "donation.date",
-   "fieldname": "date_of_donation",
-   "fieldtype": "Date",
-   "label": "Date of Donation",
-   "read_only": 1
-  },
-  {
-   "fieldname": "column_break_27",
-   "fieldtype": "Column Break"
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Donor\";",
-   "fetch_from": "donor.donor_name",
-   "fieldname": "donor_name",
-   "fieldtype": "Data",
-   "label": "Donor Name",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Donor\";",
-   "fetch_from": "donor.email",
-   "fieldname": "donor_email",
-   "fieldtype": "Data",
-   "label": "Email",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Member\";",
-   "fetch_from": "member.email_id",
-   "fieldname": "member_email",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Email",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Member\";",
-   "fetch_from": "member.pan_number",
-   "fieldname": "member_pan_number",
-   "fieldtype": "Data",
-   "label": "PAN Details",
-   "read_only": 1
-  },
-  {
-   "depends_on": "eval:doc.recipient === \"Donor\";",
-   "fetch_from": "donor.pan_number",
-   "fieldname": "donor_pan_number",
-   "fieldtype": "Data",
-   "label": "PAN Details",
-   "read_only": 1
-  },
-  {
-   "fieldname": "title",
-   "fieldtype": "Data",
-   "hidden": 1,
-   "label": "Title",
-   "print_hide": 1
-  }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-02-22 00:03:34.215633",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "Tax Exemption 80G Certificate",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "System Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "search_fields": "member, member_name",
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "title",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
deleted file mode 100644
index 0f08978..0000000
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
+++ /dev/null
@@ -1,104 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.contacts.doctype.address.address import get_company_address
-from frappe.model.document import Document
-from frappe.utils import flt, get_link_to_form, getdate
-
-from erpnext.accounts.utils import get_fiscal_year
-
-
-class TaxExemption80GCertificate(Document):
-	def validate(self):
-		self.validate_date()
-		self.validate_duplicates()
-		self.validate_company_details()
-		self.set_company_address()
-		self.calculate_total()
-		self.set_title()
-
-	def validate_date(self):
-		if self.recipient == 'Member':
-			if getdate(self.date):
-				fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True)
-
-				if not (fiscal_year.year_start_date <= getdate(self.date) \
-					<= fiscal_year.year_end_date):
-					frappe.throw(_('The Certificate Date is not in the Fiscal Year {0}').format(frappe.bold(self.fiscal_year)))
-
-	def validate_duplicates(self):
-		if self.recipient == 'Donor':
-			certificate = frappe.db.exists(self.doctype, {
-				'donation': self.donation,
-				'name': ('!=', self.name)
-			})
-			if certificate:
-				frappe.throw(_('An 80G Certificate {0} already exists for the donation {1}').format(
-					get_link_to_form(self.doctype, certificate), frappe.bold(self.donation)
-				), title=_('Duplicate Certificate'))
-
-	def validate_company_details(self):
-		fields = ['company_80g_number', 'with_effect_from', 'pan_details']
-		company_details = frappe.db.get_value('Company', self.company, fields, as_dict=True)
-		if not company_details.company_80g_number:
-			frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('80G Number'),
-				get_link_to_form('Company', self.company)))
-
-		if not company_details.pan_details:
-			frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'),
-				get_link_to_form('Company', self.company)))
-
-	@frappe.whitelist()
-	def set_company_address(self):
-		address = get_company_address(self.company)
-		self.company_address = address.company_address
-		self.company_address_display = address.company_address_display
-
-	def calculate_total(self):
-		if self.recipient == 'Donor':
-			return
-
-		total = 0
-		for entry in self.payments:
-			total += flt(entry.amount)
-		self.total = total
-
-	def set_title(self):
-		if self.recipient == 'Member':
-			self.title = self.member_name
-		else:
-			self.title = self.donor_name
-
-	@frappe.whitelist()
-	def get_payments(self):
-		if not self.member:
-			frappe.throw(_('Please select a Member first.'))
-
-		fiscal_year = get_fiscal_year(fiscal_year=self.fiscal_year, as_dict=True)
-
-		memberships = frappe.db.get_all('Membership', {
-			'member': self.member,
-			'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
-			'membership_status': ('!=', 'Cancelled')
-		}, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date')
-
-		if not memberships:
-			frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member))
-
-		total = 0
-		self.payments = []
-
-		for doc in memberships:
-			self.append('payments', {
-				'date': doc.from_date,
-				'amount': doc.amount,
-				'invoice_id': doc.invoice,
-				'razorpay_payment_id': doc.payment_id,
-				'membership': doc.name
-			})
-			total += flt(doc.amount)
-
-		self.total = total
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
deleted file mode 100644
index 6fa3b85..0000000
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/test_tax_exemption_80g_certificate.py
+++ /dev/null
@@ -1,106 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-from frappe.utils import getdate
-
-from erpnext.accounts.utils import get_fiscal_year
-from erpnext.non_profit.doctype.donation.donation import create_donation
-from erpnext.non_profit.doctype.donation.test_donation import (
-	create_donor,
-	create_donor_type,
-	create_mode_of_payment,
-)
-from erpnext.non_profit.doctype.member.member import create_member
-from erpnext.non_profit.doctype.membership.test_membership import make_membership, setup_membership
-
-
-class TestTaxExemption80GCertificate(unittest.TestCase):
-	def setUp(self):
-		frappe.db.sql('delete from `tabTax Exemption 80G Certificate`')
-		frappe.db.sql('delete from `tabMembership`')
-		create_donor_type()
-		settings = frappe.get_doc('Non Profit Settings')
-		settings.company = '_Test Company'
-		settings.donation_company = '_Test Company'
-		settings.default_donor_type = '_Test Donor'
-		settings.creation_user = 'Administrator'
-		settings.save()
-
-		company = frappe.get_doc('Company', '_Test Company')
-		company.pan_details = 'BBBTI3374C'
-		company.company_80g_number = 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087'
-		company.with_effect_from = getdate()
-		company.save()
-
-	def test_duplicate_donation_certificate(self):
-		donor = create_donor()
-		create_mode_of_payment()
-		payment = frappe._dict({
-			'amount': 100,
-			'method': 'Debit Card',
-			'id': 'pay_MeXAmsgeKOhq7O'
-		})
-		donation = create_donation(donor, payment)
-
-		args = frappe._dict({
-			'recipient': 'Donor',
-			'donor': donor.name,
-			'donation': donation.name
-		})
-		certificate = create_80g_certificate(args)
-		certificate.insert()
-
-		# check company details
-		self.assertEqual(certificate.company_pan_number, 'BBBTI3374C')
-		self.assertEqual(certificate.company_80g_number, 'NQ.CIT(E)I2018-19/DEL-IE28615-27062018/10087')
-
-		# check donation details
-		self.assertEqual(certificate.amount, donation.amount)
-
-		duplicate_certificate = create_80g_certificate(args)
-		# duplicate validation
-		self.assertRaises(frappe.ValidationError, duplicate_certificate.insert)
-
-	def test_membership_80g_certificate(self):
-		plan = setup_membership()
-
-		# make test member
-		member_doc = create_member(frappe._dict({
-			'fullname': "_Test_Member",
-			'email': "_test_member_erpnext@example.com",
-			'plan_id': plan.name
-		}))
-		member_doc.make_customer_and_link()
-		member = member_doc.name
-
-		membership = make_membership(member, { "from_date": getdate() })
-		invoice = membership.generate_invoice(save=True)
-
-		args = frappe._dict({
-			'recipient': 'Member',
-			'member': member,
-			'fiscal_year': get_fiscal_year(getdate(), as_dict=True).get('name')
-		})
-		certificate = create_80g_certificate(args)
-		certificate.get_payments()
-		certificate.insert()
-
-		self.assertEqual(len(certificate.payments), 1)
-		self.assertEqual(certificate.payments[0].amount, membership.amount)
-		self.assertEqual(certificate.payments[0].invoice_id, invoice.name)
-
-
-def create_80g_certificate(args):
-	certificate = frappe.get_doc({
-		'doctype': 'Tax Exemption 80G Certificate',
-		'recipient': args.recipient,
-		'date': getdate(),
-		'company': '_Test Company'
-	})
-
-	certificate.update(args)
-
-	return certificate
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json b/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json
deleted file mode 100644
index dfa817d..0000000
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate_detail/tax_exemption_80g_certificate_detail.json
+++ /dev/null
@@ -1,66 +0,0 @@
-{
- "actions": [],
- "creation": "2021-02-15 12:43:52.754124",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
-  "date",
-  "amount",
-  "invoice_id",
-  "column_break_4",
-  "razorpay_payment_id",
-  "membership"
- ],
- "fields": [
-  {
-   "fieldname": "date",
-   "fieldtype": "Date",
-   "in_list_view": 1,
-   "label": "Date",
-   "reqd": 1
-  },
-  {
-   "fieldname": "amount",
-   "fieldtype": "Currency",
-   "in_list_view": 1,
-   "label": "Amount",
-   "reqd": 1
-  },
-  {
-   "fieldname": "invoice_id",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Invoice ID",
-   "options": "Sales Invoice",
-   "reqd": 1
-  },
-  {
-   "fieldname": "razorpay_payment_id",
-   "fieldtype": "Data",
-   "label": "Razorpay Payment ID"
-  },
-  {
-   "fieldname": "membership",
-   "fieldtype": "Link",
-   "label": "Membership",
-   "options": "Membership"
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  }
- ],
- "index_web_pages_for_search": 1,
- "istable": 1,
- "links": [],
- "modified": "2021-02-15 16:35:10.777587",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "Tax Exemption 80G Certificate Detail",
- "owner": "Administrator",
- "permissions": [],
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/regional/germany/utils/datev/datev_csv.py b/erpnext/regional/germany/utils/datev/datev_csv.py
index 2d1e02e..ec271a1 100644
--- a/erpnext/regional/germany/utils/datev/datev_csv.py
+++ b/erpnext/regional/germany/utils/datev/datev_csv.py
@@ -1,11 +1,11 @@
 import datetime
 import zipfile
 from csv import QUOTE_NONNUMERIC
+from io import BytesIO
 
 import frappe
 import pandas as pd
 from frappe import _
-from six import BytesIO
 
 from .datev_constants import DataCategory
 
diff --git a/erpnext/accounts/print_format/gst_pos_invoice/__init__.py b/erpnext/regional/india/e_invoice/__init__.py
similarity index 100%
copy from erpnext/accounts/print_format/gst_pos_invoice/__init__.py
copy to erpnext/regional/india/e_invoice/__init__.py
diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json
new file mode 100644
index 0000000..78e5651
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_item_template.json
@@ -0,0 +1,31 @@
+{{
+    "SlNo": "{item.sr_no}",
+    "PrdDesc": "{item.description}",
+    "IsServc": "{item.is_service_item}",
+    "HsnCd": "{item.gst_hsn_code}",
+    "Barcde": "{item.barcode}",
+    "Unit": "{item.uom}",
+    "Qty": "{item.qty}",
+    "FreeQty": "{item.free_qty}",
+    "UnitPrice": "{item.unit_rate}",
+    "TotAmt": "{item.gross_amount}",
+    "Discount": "{item.discount_amount}",
+    "AssAmt": "{item.taxable_value}",
+    "PrdSlNo": "{item.serial_no}",
+    "GstRt": "{item.tax_rate}",
+    "IgstAmt": "{item.igst_amount}",
+    "CgstAmt": "{item.cgst_amount}",
+    "SgstAmt": "{item.sgst_amount}",
+    "CesRt": "{item.cess_rate}",
+    "CesAmt": "{item.cess_amount}",
+    "CesNonAdvlAmt": "{item.cess_nadv_amount}",
+    "StateCesRt": "{item.state_cess_rate}",
+    "StateCesAmt": "{item.state_cess_amount}",
+    "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
+    "OthChrg": "{item.other_charges}",
+    "TotItemVal": "{item.total_value}",
+    "BchDtls": {{
+        "Nm": "{item.batch_no}",
+        "ExpDt": "{item.batch_expiry_date}"
+    }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json
new file mode 100644
index 0000000..c2a28f2
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_template.json
@@ -0,0 +1,110 @@
+{{
+    "Version": "1.1",
+    "TranDtls": {{
+        "TaxSch": "{transaction_details.tax_scheme}",
+        "SupTyp": "{transaction_details.supply_type}",
+        "RegRev": "{transaction_details.reverse_charge}",
+        "EcmGstin": "{transaction_details.ecom_gstin}",
+        "IgstOnIntra": "{transaction_details.igst_on_intra}"
+    }},
+    "DocDtls": {{
+        "Typ": "{doc_details.invoice_type}",
+        "No": "{doc_details.invoice_name}",
+        "Dt": "{doc_details.invoice_date}"
+    }},
+    "SellerDtls": {{
+        "Gstin": "{seller_details.gstin}",
+        "LglNm": "{seller_details.legal_name}",
+        "TrdNm": "{seller_details.trade_name}",
+        "Loc": "{seller_details.location}",
+        "Pin": "{seller_details.pincode}",
+        "Stcd": "{seller_details.state_code}",
+        "Addr1": "{seller_details.address_line1}",
+        "Addr2": "{seller_details.address_line2}",
+        "Ph": "{seller_details.phone}",
+        "Em": "{seller_details.email}"
+    }},
+    "BuyerDtls": {{
+        "Gstin": "{buyer_details.gstin}",
+        "LglNm": "{buyer_details.legal_name}",
+        "TrdNm": "{buyer_details.trade_name}",
+        "Addr1": "{buyer_details.address_line1}",
+        "Addr2": "{buyer_details.address_line2}",
+        "Loc": "{buyer_details.location}",
+        "Pin": "{buyer_details.pincode}",
+        "Stcd": "{buyer_details.state_code}",
+        "Ph": "{buyer_details.phone}",
+        "Em": "{buyer_details.email}",
+        "Pos": "{buyer_details.place_of_supply}"
+    }},
+    "DispDtls": {{
+        "Nm": "{dispatch_details.legal_name}",
+        "Addr1": "{dispatch_details.address_line1}",
+        "Addr2": "{dispatch_details.address_line2}",
+        "Loc": "{dispatch_details.location}",
+        "Pin": "{dispatch_details.pincode}",
+        "Stcd": "{dispatch_details.state_code}"
+    }},
+    "ShipDtls": {{
+        "Gstin": "{shipping_details.gstin}",
+        "LglNm": "{shipping_details.legal_name}",
+        "TrdNm": "{shipping_details.trader_name}",
+        "Addr1": "{shipping_details.address_line1}",
+        "Addr2": "{shipping_details.address_line2}",
+        "Loc": "{shipping_details.location}",
+        "Pin": "{shipping_details.pincode}",
+        "Stcd": "{shipping_details.state_code}"
+    }},
+    "ItemList": [
+        {item_list}
+    ],
+    "ValDtls": {{
+        "AssVal": "{invoice_value_details.base_total}",
+        "CgstVal": "{invoice_value_details.total_cgst_amt}",
+        "SgstVal": "{invoice_value_details.total_sgst_amt}",
+        "IgstVal": "{invoice_value_details.total_igst_amt}",
+        "CesVal": "{invoice_value_details.total_cess_amt}",
+        "Discount": "{invoice_value_details.invoice_discount_amt}",
+        "RndOffAmt": "{invoice_value_details.round_off}",
+        "OthChrg": "{invoice_value_details.total_other_charges}",
+        "TotInvVal": "{invoice_value_details.base_grand_total}",
+        "TotInvValFc": "{invoice_value_details.grand_total}"
+    }},
+    "PayDtls": {{
+        "Nm": "{payment_details.payee_name}",
+        "AccDet": "{payment_details.account_no}",
+        "Mode": "{payment_details.mode_of_payment}",
+        "FinInsBr": "{payment_details.ifsc_code}",
+        "PayTerm": "{payment_details.terms}",
+        "PaidAmt": "{payment_details.paid_amount}",
+        "PaymtDue": "{payment_details.outstanding_amount}"
+    }},
+    "RefDtls": {{
+        "DocPerdDtls": {{
+            "InvStDt": "{period_details.start_date}",
+            "InvEndDt": "{period_details.end_date}"
+        }},
+        "PrecDocDtls": [{{
+            "InvNo": "{prev_doc_details.invoice_name}",
+            "InvDt": "{prev_doc_details.invoice_date}"
+        }}]
+    }},
+    "ExpDtls": {{
+        "ShipBNo": "{export_details.bill_no}",
+        "ShipBDt": "{export_details.bill_date}",
+        "Port": "{export_details.port}",
+        "ForCur": "{export_details.foreign_curr_code}",
+        "CntCode": "{export_details.country_code}",
+        "ExpDuty": "{export_details.export_duty}"
+    }},
+    "EwbDtls": {{
+        "TransId": "{eway_bill_details.gstin}",
+        "TransName": "{eway_bill_details.name}",
+        "TransMode": "{eway_bill_details.mode_of_transport}",
+        "Distance": "{eway_bill_details.distance}",
+        "TransDocNo": "{eway_bill_details.document_name}",
+        "TransDocDt": "{eway_bill_details.document_date}",
+        "VehNo": "{eway_bill_details.vehicle_no}",
+        "VehType": "{eway_bill_details.vehicle_type}"
+    }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
new file mode 100644
index 0000000..f4a3542
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -0,0 +1,957 @@
+{
+  "Version": {
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 6,
+    "description": "Version of the schema"
+  },
+  "Irn": {
+    "type": "string",
+    "minLength": 64,
+    "maxLength": 64,
+    "description": "Invoice Reference Number"
+  },
+  "TranDtls": {
+    "type": "object",
+    "properties": {
+      "TaxSch": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 10,
+        "enum": ["GST"],
+        "description": "GST- Goods and Services Tax Scheme"
+      },
+      "SupTyp": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 10,
+        "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"],
+        "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export"
+      },
+      "RegRev": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Y- whether the tax liability is payable under reverse charge"
+      },
+      "EcmGstin": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "pattern": "([0-9]{2}[0-9A-Z]{13})",
+        "description": "E-Commerce GSTIN",
+        "validationMsg": "E-Commerce GSTIN is invalid"
+      },
+      "IgstOnIntra": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Y- indicates the supply is intra state but chargeable to IGST"
+      }
+    },
+    "required": ["TaxSch", "SupTyp"]
+  },
+  "DocDtls": {
+    "type": "object",
+    "properties": {
+      "Typ": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 3,
+        "enum": ["INV", "CRN", "DBN"],
+        "description": "Document Type"
+      },
+      "No": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 16,
+        "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$",
+        "description": "Document Number",
+        "validationMsg": "Document Number should not be starting with 0, / and -"
+      },
+      "Dt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Document Date"
+      }
+    },
+    "required": ["Typ", "No", "Dt"]
+  },
+  "SellerDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "pattern": "([0-9]{2}[0-9A-Z]{13})",
+        "description": "Supplier GSTIN",
+        "validationMsg": "Company GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Tradename"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 50,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Supplier State Code"
+      },
+      "Ph": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 12,
+        "description": "Phone"
+      },
+      "Em": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 100,
+        "description": "Email-Id"
+      }
+    },
+    "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "BuyerDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 15,
+        "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+        "description": "Buyer GSTIN",
+        "validationMsg": "Customer GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Trade Name"
+      },
+      "Pos": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Place of Supply State code"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Buyer State Code"
+      },
+      "Ph": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 12,
+        "description": "Phone"
+      },
+      "Em": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 100,
+        "description": "Email-Id"
+      }
+    },
+    "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
+  },
+  "DispDtls": {
+    "type": "object",
+    "properties": {
+      "Nm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Dispatch Address Name"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "State Code"
+      }
+    },
+    "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "ShipDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "maxLength": 15,
+        "minLength": 3,
+        "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+        "description": "Shipping Address GSTIN",
+        "validationMsg": "Shipping Address GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Trade Name"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "State Code"
+      }
+    },
+    "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "ItemList": {
+    "type": "Array",
+    "properties": {
+      "SlNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 6,
+        "description": "Serial No. of Item"
+      },
+      "PrdDesc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 300,
+        "description": "Item Name"
+      },
+      "IsServc": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Is Service Item"
+      },
+      "HsnCd": {
+        "type": "string",
+        "minLength": 4,
+        "maxLength": 8,
+        "description": "HSN Code"
+      },
+      "Barcde": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 30,
+        "description": "Barcode"
+      },
+      "Qty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999999999.999,
+        "description": "Quantity"
+      },
+      "FreeQty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999999999.999,
+        "description": "Free Quantity"
+      },
+      "Unit": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 8,
+        "description": "UOM"
+      },
+      "UnitPrice": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.999,
+        "description": "Rate"
+      },
+      "TotAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Gross Amount"
+      },
+      "Discount": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Discount"
+      },
+      "PreTaxVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Pre tax value"
+      },
+      "AssAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Taxable Value"
+      },
+      "GstRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "GST Rate"
+      },
+      "IgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "IGST Amount"
+      },
+      "CgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "CGST Amount"
+      },
+      "SgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "SGST Amount"
+      },
+      "CesRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "Cess Rate"
+      },
+      "CesAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Cess Amount (Advalorem)"
+      },
+      "CesNonAdvlAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Cess Amount (Non-Advalorem)"
+      },
+      "StateCesRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "State CESS Rate"
+      },
+      "StateCesAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "State CESS Amount"
+      },
+      "StateCesNonAdvlAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "State CESS Amount (Non Advalorem)"
+      },
+      "OthChrg": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Other Charges"
+      },
+      "TotItemVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Total Item Value"
+      },
+      "OrdLineRef": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 50,
+        "description": "Order line reference"
+      },
+      "OrgCntry": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 2,
+        "description": "Origin Country"
+      },
+      "PrdSlNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 20,
+        "description": "Serial number"
+      },
+      "BchDtls": {
+        "type": "object",
+        "properties": {
+          "Nm": {
+            "type": "string",
+            "minLength": 3,
+            "maxLength": 20,
+            "description": "Batch number"
+          },
+          "ExpDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Batch Expiry Date"
+          },
+          "WrDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Warranty Date"
+          }
+        },
+        "required": ["Nm"]
+      },
+      "AttribDtls": {
+        "type": "Array",
+        "Attribute": {
+          "type": "object",
+          "properties": {
+            "Nm": {
+              "type": "string",
+              "minLength": 1,
+              "maxLength": 100,
+              "description": "Attribute name of the item"
+            },
+            "Val": {
+              "type": "string",
+              "minLength": 1,
+              "maxLength": 100,
+              "description": "Attribute value of the item"
+            }
+          }
+        }
+      }
+    },
+    "required": [
+      "SlNo",
+      "IsServc",
+      "HsnCd",
+      "UnitPrice",
+      "TotAmt",
+      "AssAmt",
+      "GstRt",
+      "TotItemVal"
+    ]
+  },
+  "ValDtls": {
+    "type": "object",
+    "properties": {
+      "AssVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total Assessable value of all items"
+      },
+      "CgstVal": {
+        "type": "number",
+        "maximum": 99999999999999.99,
+        "minimum": 0,
+        "description": "Total CGST value of all items"
+      },
+      "SgstVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total SGST value of all items"
+      },
+      "IgstVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total IGST value of all items"
+      },
+      "CesVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total CESS value of all items"
+      },
+      "StCesVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total State CESS value of all items"
+      },
+      "Discount": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Invoice Discount"
+      },
+      "OthChrg": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Other Charges"
+      },
+      "RndOffAmt": {
+        "type": "number",
+        "minimum": -99.99,
+        "maximum": 99.99,
+        "description": "Rounded off Amount"
+      },
+      "TotInvVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Final Invoice Value "
+      },
+      "TotInvValFc": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Final Invoice value in Foreign Currency"
+      }
+    },
+    "required": ["AssVal", "TotInvVal"]
+  },
+  "PayDtls": {
+    "type": "object",
+    "properties": {
+      "Nm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Payee Name"
+      },
+      "AccDet": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 18,
+        "description": "Bank Account Number of Payee"
+      },
+      "Mode": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 18,
+        "description": "Mode of Payment"
+      },
+      "FinInsBr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 11,
+        "description": "Branch or IFSC code"
+      },
+      "PayTerm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Terms of Payment"
+      },
+      "PayInstr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Payment Instruction"
+      },
+      "CrTrn": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Credit Transfer"
+      },
+      "DirDr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Direct Debit"
+      },
+      "CrDay": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999,
+        "description": "Credit Days"
+      },
+      "PaidAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Advance Amount"
+      },
+      "PaymtDue": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Outstanding Amount"
+      }
+    }
+  },
+  "RefDtls": {
+    "type": "object",
+    "properties": {
+      "InvRm": {
+        "type": "string",
+        "maxLength": 100,
+        "minLength": 3,
+        "pattern": "^[0-9A-Za-z/-]{3,100}$",
+        "description": "Remarks/Note"
+      },
+      "DocPerdDtls": {
+        "type": "object",
+        "properties": {
+          "InvStDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Invoice Period Start Date"
+          },
+          "InvEndDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Invoice Period End Date"
+          }
+        },
+        "required": ["InvStDt ", "InvEndDt "]
+      },
+      "PrecDocDtls": {
+        "type": "object",
+        "properties": {
+          "InvNo": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 16,
+            "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$",
+            "description": "Reference of Original Invoice"
+          },
+          "InvDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Date of Orginal Invoice"
+          },
+          "OthRefNo": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "description": "Other Reference"
+          }
+        }
+      },
+      "required": ["InvNo", "InvDt"],
+      "ContrDtls": {
+        "type": "object",
+        "properties": {
+          "RecAdvRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Receipt Advice No."
+          },
+          "RecAdvDt": {
+            "type": "string",
+            "minLength": 10,
+            "maxLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Date of receipt advice"
+          },
+          "TendRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Lot/Batch Reference No."
+          },
+          "ContrRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Contract Reference Number"
+          },
+          "ExtRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Any other reference"
+          },
+          "ProjRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Project Reference Number"
+          },
+          "PORefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 16,
+            "pattern": "^([0-9A-Za-z/-]){1,16}$",
+            "description": "PO Reference Number"
+          },
+          "PORefDt": {
+            "type": "string",
+            "minLength": 10,
+            "maxLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "PO Reference date"
+          }
+        }
+      }
+    }
+  },
+  "AddlDocDtls": {
+    "type": "Array",
+    "properties": {
+      "Url": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Supporting document URL"
+      },
+      "Docs": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 1000,
+        "description": "Supporting document in Base64 Format"
+      },
+      "Info": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 1000,
+        "description": "Any additional information"
+      }
+    }
+  },
+
+  "ExpDtls": {
+    "type": "object",
+    "properties": {
+      "ShipBNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 20,
+        "description": "Shipping Bill No."
+      },
+      "ShipBDt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Shipping Bill Date"
+      },
+      "Port": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 10,
+        "pattern": "^[0-9A-Za-z]{2,10}$",
+        "description": "Port Code. Refer the master"
+      },
+      "RefClm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "description": "Claiming Refund. Y/N"
+      },
+      "ForCur": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 16,
+        "description": "Additional Currency Code. Refer the master"
+      },
+      "CntCode": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 2,
+        "description": "Country Code. Refer the master"
+      },
+      "ExpDuty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Export Duty"
+      }
+    }
+  },
+  "EwbDtls": {
+    "type": "object",
+    "properties": {
+      "TransId": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "description": "Transporter GSTIN"
+      },
+      "TransName": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Transporter Name"
+      },
+      "TransMode": {
+        "type": "string",
+        "maxLength": 1,
+        "minLength": 1,
+        "enum": ["1", "2", "3", "4"],
+        "description": "Mode of Transport"
+      },
+      "Distance": {
+        "type": "number",
+        "minimum": 1,
+        "maximum": 9999,
+        "description": "Distance"
+      },
+      "TransDocNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 15,
+        "pattern": "^([0-9A-Z/-]){1,15}$",
+        "description": "Tranport Document Number",
+        "validationMsg": "Transport Receipt No is invalid"
+      },
+      "TransDocDt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Transport Document Date"
+      },
+      "VehNo": {
+        "type": "string",
+        "minLength": 4,
+        "maxLength": 20,
+        "description": "Vehicle Number"
+      },
+      "VehType": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["O", "R"],
+        "description": "Vehicle Type"
+      }
+    },
+    "required": ["Distance"]
+  },
+  "required": [
+    "Version",
+    "TranDtls",
+    "DocDtls",
+    "SellerDtls",
+    "BuyerDtls",
+    "ItemList",
+    "ValDtls"
+  ]
+}
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
new file mode 100644
index 0000000..348f0c6
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -0,0 +1,292 @@
+erpnext.setup_einvoice_actions = (doctype) => {
+	frappe.ui.form.on(doctype, {
+		async refresh(frm) {
+			if (frm.doc.docstatus == 2) return;
+
+			const res = await frappe.call({
+				method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
+				args: { doc: frm.doc }
+			});
+			const invoice_eligible = res.message;
+
+			if (!invoice_eligible) return;
+
+			const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
+
+			const add_custom_button = (label, action) => {
+				if (!frm.custom_buttons[label]) {
+					frm.add_custom_button(label, action, __('E Invoicing'));
+				}
+			};
+
+			if (!irn && !__unsaved) {
+				const action = () => {
+					if (frm.doc.__unsaved) {
+						frappe.throw(__('Please save the document to generate IRN.'));
+					}
+					frappe.call({
+						method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
+						args: { doctype, docname: name },
+						freeze: true,
+						callback: (res) => {
+							const einvoice = res.message;
+							show_einvoice_preview(frm, einvoice);
+						}
+					});
+				};
+
+				add_custom_button(__("Generate IRN"), action);
+			}
+
+			if (irn && !irn_cancelled && !ewaybill) {
+				const fields = [
+					{
+						"label": "Reason",
+						"fieldname": "reason",
+						"fieldtype": "Select",
+						"reqd": 1,
+						"default": "1-Duplicate",
+						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+					},
+					{
+						"label": "Remark",
+						"fieldname": "remark",
+						"fieldtype": "Data",
+						"reqd": 1
+					}
+				];
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __("Cancel IRN"),
+						fields: fields,
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
+								args: {
+									doctype,
+									docname: name,
+									irn: irn,
+									reason: data.reason.split('-')[0],
+									remark: data.remark
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+				add_custom_button(__("Cancel IRN"), action);
+			}
+
+			if (irn && !irn_cancelled && !ewaybill) {
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __('Generate E-Way Bill'),
+						size: "large",
+						fields: get_ewaybill_fields(frm),
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill',
+								args: {
+									doctype,
+									docname: name,
+									irn,
+									...data
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+
+				add_custom_button(__("Generate E-Way Bill"), action);
+			}
+
+			if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
+				const action = () => {
+					let message = __('Cancellation of e-way bill is currently not supported.') + ' ';
+					message += '<br><br>';
+					message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
+
+					const dialog = frappe.msgprint({
+						title: __('Update E-Way Bill Cancelled Status?'),
+						message: message,
+						indicator: 'orange',
+						primary_action: {
+							action: function() {
+								frappe.call({
+									method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+									args: { doctype, docname: name },
+									freeze: true,
+									callback: () => frm.reload_doc() || dialog.hide()
+								});
+							}
+						},
+						primary_action_label: __('Yes')
+					});
+				};
+				add_custom_button(__("Cancel E-Way Bill"), action);
+			}
+		}
+	});
+};
+
+const get_ewaybill_fields = (frm) => {
+	return [
+		{
+			'fieldname': 'transporter',
+			'label': 'Transporter',
+			'fieldtype': 'Link',
+			'options': 'Supplier',
+			'default': frm.doc.transporter
+		},
+		{
+			'fieldname': 'gst_transporter_id',
+			'label': 'GST Transporter ID',
+			'fieldtype': 'Data',
+			'fetch_from': 'transporter.gst_transporter_id',
+			'default': frm.doc.gst_transporter_id
+		},
+		{
+			'fieldname': 'driver',
+			'label': 'Driver',
+			'fieldtype': 'Link',
+			'options': 'Driver',
+			'default': frm.doc.driver
+		},
+		{
+			'fieldname': 'lr_no',
+			'label': 'Transport Receipt No',
+			'fieldtype': 'Data',
+			'default': frm.doc.lr_no
+		},
+		{
+			'fieldname': 'vehicle_no',
+			'label': 'Vehicle No',
+			'fieldtype': 'Data',
+			'default': frm.doc.vehicle_no
+		},
+		{
+			'fieldname': 'distance',
+			'label': 'Distance (in km)',
+			'fieldtype': 'Float',
+			'default': frm.doc.distance
+		},
+		{
+			'fieldname': 'transporter_col_break',
+			'fieldtype': 'Column Break',
+		},
+		{
+			'fieldname': 'transporter_name',
+			'label': 'Transporter Name',
+			'fieldtype': 'Data',
+			'fetch_from': 'transporter.name',
+			'read_only': 1,
+			'default': frm.doc.transporter_name
+		},
+		{
+			'fieldname': 'mode_of_transport',
+			'label': 'Mode of Transport',
+			'fieldtype': 'Select',
+			'options': `\nRoad\nAir\nRail\nShip`,
+			'default': frm.doc.mode_of_transport
+		},
+		{
+			'fieldname': 'driver_name',
+			'label': 'Driver Name',
+			'fieldtype': 'Data',
+			'fetch_from': 'driver.full_name',
+			'read_only': 1,
+			'default': frm.doc.driver_name
+		},
+		{
+			'fieldname': 'lr_date',
+			'label': 'Transport Receipt Date',
+			'fieldtype': 'Date',
+			'default': frm.doc.lr_date
+		},
+		{
+			'fieldname': 'gst_vehicle_type',
+			'label': 'GST Vehicle Type',
+			'fieldtype': 'Select',
+			'options': `Regular\nOver Dimensional Cargo (ODC)`,
+			'depends_on': 'eval:(doc.mode_of_transport === "Road")',
+			'default': frm.doc.gst_vehicle_type
+		}
+	];
+};
+
+const request_irn_generation = (frm) => {
+	frappe.call({
+		method: 'erpnext.regional.india.e_invoice.utils.generate_irn',
+		args: { doctype: frm.doc.doctype, docname: frm.doc.name },
+		freeze: true,
+		callback: () => frm.reload_doc()
+	});
+};
+
+const get_preview_dialog = (frm, action) => {
+	const dialog = new frappe.ui.Dialog({
+		title: __("Preview"),
+		size: "large",
+		fields: [
+			{
+				"label": "Preview",
+				"fieldname": "preview_html",
+				"fieldtype": "HTML"
+			}
+		],
+		primary_action: () => action(frm) || dialog.hide(),
+		primary_action_label: __('Generate IRN')
+	});
+	return dialog;
+};
+
+const show_einvoice_preview = (frm, einvoice) => {
+	const preview_dialog = get_preview_dialog(frm, request_irn_generation);
+
+	// initialize e-invoice fields
+	einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate();
+	frm.doc.signed_einvoice = JSON.stringify(einvoice);
+
+	// initialize preview wrapper
+	const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper;
+	$preview_wrapper.html(
+		`<div>
+			<div class="print-preview">
+				<div class="print-format"></div>
+			</div>
+			<div class="page-break-message text-muted text-center text-medium margin-top"></div>
+		</div>`
+	);
+
+	frappe.call({
+		method: "frappe.www.printview.get_html_and_style",
+		args: {
+			doc: frm.doc,
+			print_format: "GST E-Invoice",
+			no_letterhead: 1
+		},
+		callback: function (r) {
+			if (!r.exc) {
+				$preview_wrapper.find(".print-format").html(r.message.html);
+				const style = `
+					.print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; }
+					.print-preview { min-height: 0px; }
+					.modal-dialog { width: 720px; }`;
+
+				frappe.dom.set_style(style, "custom-print-style");
+				preview_dialog.show();
+			}
+		}
+	});
+};
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
new file mode 100644
index 0000000..e3f7e90
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -0,0 +1,1171 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import base64
+import io
+import json
+import os
+import re
+import sys
+import traceback
+
+import frappe
+import jwt
+from frappe import _, bold
+from frappe.core.page.background_jobs.background_jobs import get_info
+from frappe.integrations.utils import make_get_request, make_post_request
+from frappe.utils.background_jobs import enqueue
+from frappe.utils.data import (
+	add_to_date,
+	cint,
+	cstr,
+	flt,
+	format_date,
+	get_link_to_form,
+	getdate,
+	now_datetime,
+	time_diff_in_hours,
+	time_diff_in_seconds,
+)
+from frappe.utils.scheduler import is_scheduler_inactive
+from pyqrcode import create as qrcreate
+
+from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
+
+
+@frappe.whitelist()
+def validate_eligibility(doc):
+	if isinstance(doc, str):
+		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, skip_gstin_validation):
+	if ((not address.gstin and not skip_gstin_validation)
+		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')
+		)
+
+	if address.address_line2 and len(address.address_line2) < 2:
+		# to prevent "The field Address 2 must be a string with a minimum length of 3 and a maximum length of 100"
+		address.address_line2 = ""
+
+def get_party_details(address_name, skip_gstin_validation=False):
+	addr = frappe.get_doc('Address', address_name)
+
+	validate_address_fields(addr, skip_gstin_validation)
+
+	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['CgstVal']) + flt(value_details['SgstVal']) - total_item_cgst_value - total_item_sgst_value) > 1:
+		frappe.throw(_('CGST + SGST value of the items is not equal to total CGST + SGST value. Please review taxes for any correction.'))
+
+	if abs(flt(value_details['IgstVal']) - total_item_igst_value) > 1:
+		frappe.throw(_('IGST value of all items is not equal to total IGST value. Please review taxes 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, skip_gstin_validation=True)
+
+	dispatch_details = frappe._dict({})
+	if invoice.dispatch_address_name:
+		dispatch_details = get_party_details(invoice.dispatch_address_name, skip_gstin_validation=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
+	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)
+
+	try:
+		validate_totals(einvoice)
+	except Exception:
+		log_error(einvoice)
+		raise
+
+	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, str):
+		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
+	])
+	return 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 get_client_credentials(self):
+		if self.e_invoice_settings.client_id and self.e_invoice_settings.client_secret:
+			return self.e_invoice_settings.client_id, self.e_invoice_settings.get_password('client_secret')
+
+		return frappe.conf.einvoice_client_id, frappe.conf.einvoice_client_secret
+
+	def fetch_auth_token(self):
+		client_id, client_secret = self.get_client_credentials()
+		headers = {
+			'gspappid': client_id,
+			'gspappsecret': 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=None):
+		if errors is None:
+			errors = []
+		title = _('E Invoice Request Failed')
+		if errors:
+			frappe.throw(errors, title=title, as_list=1)
+		else:
+			link_to_error_list = '<a href="/app/error-log" target="_blank">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/india/setup.py b/erpnext/regional/india/setup.py
index 4b99421..12b10bb 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -53,14 +53,11 @@
 		hsn_code.description = d["description"]
 		hsn_code.hsn_code = d[code_field]
 		hsn_code.name = d[code_field]
-		try:
-			hsn_code.db_insert()
-		except frappe.DuplicateEntryError:
-			pass
+		hsn_code.db_insert(ignore_if_duplicate=True)
 
 def add_custom_roles_for_reports():
 	for report_name in ('GST Sales Register', 'GST Purchase Register',
-		'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'):
+		'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'):
 
 		if not frappe.db.get_value('Custom Role', dict(report=report_name)):
 			frappe.get_doc(dict(
@@ -99,7 +96,7 @@
 			)).insert()
 
 def add_permissions():
-	for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
+	for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'):
 		add_permission(doctype, 'All', 0)
 		for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
 			add_permission(doctype, role, 0)
@@ -114,10 +111,12 @@
 
 def add_print_formats():
 	frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
-	frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
+	frappe.reload_doc("selling", "print_format", "gst_pos_invoice")
+	frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
 
 	frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0)
 	frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
+	frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
 
 def make_property_setters(patch=False):
 	# GST rules do not allow for an invoice no. bigger than 16 characters
@@ -453,7 +452,7 @@
 			'fieldname': 'ewaybill',
 			'label': 'E-Way Bill No.',
 			'fieldtype': 'Data',
-			'depends_on': 'eval:(doc.docstatus === 1)',
+			'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)',
 			'allow_on_submit': 1,
 			'insert_after': 'tax_id',
 			'translatable': 0,
@@ -481,6 +480,46 @@
 			fetch_from='customer_address.gstin', print_hide=1, read_only=1)
 	]
 
+	si_einvoice_fields = [
+		dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+			depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+
+		dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+			depends_on='eval: doc.irn', allow_on_submit=1, insert_after='customer'),
+
+		dict(fieldname='eway_bill_validity', label='E-Way Bill Validity', fieldtype='Data', no_copy=1, print_hide=1,
+			depends_on='ewaybill', read_only=1, allow_on_submit=1, insert_after='ewaybill'),
+
+		dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+			depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+		dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+			print_hide=1, hidden=1),
+
+		dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+			no_copy=1, print_hide=1),
+
+		dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+		dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date',
+			no_copy=1, print_hide=1),
+
+		dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+			no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+			no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+			no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+			options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+			hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+	]
+
 	custom_fields = {
 		'Address': [
 			dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
@@ -493,7 +532,7 @@
 		'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields,
 		'Purchase Order': purchase_invoice_gst_fields,
 		'Purchase Receipt': purchase_invoice_gst_fields,
-		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
+		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
 		'POS Invoice': sales_invoice_gst_fields,
 		'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
 		'Payment Entry': payment_entry_fields,
@@ -568,15 +607,6 @@
 			dict(fieldname='hra_column_break', fieldtype='Column Break', insert_after='hra_component'),
 			dict(fieldname='arrear_component', label='Arrear Component',
 				fieldtype='Link', options='Salary Component', insert_after='hra_column_break'),
-			dict(fieldname='non_profit_section', label='Non Profit Settings',
-				fieldtype='Section Break', insert_after='arrear_component', collapsible=1),
-			dict(fieldname='company_80g_number', label='80G Number',
-				fieldtype='Data', insert_after='non_profit_section'),
-			dict(fieldname='with_effect_from', label='80G With Effect From',
-				fieldtype='Date', insert_after='company_80g_number'),
-			dict(fieldname='non_profit_column_break', fieldtype='Column Break', insert_after='with_effect_from'),
-			dict(fieldname='pan_details', label='PAN Number',
-				fieldtype='Data', insert_after='non_profit_column_break')
 		],
 		'Employee Tax Exemption Declaration':[
 			dict(fieldname='hra_section', label='HRA Exemption',
@@ -671,22 +701,6 @@
 				'mandatory_depends_on': 'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
 			}
 		],
-		'Member': [
-			{
-				'fieldname': 'pan_number',
-				'label': 'PAN Details',
-				'fieldtype': 'Data',
-				'insert_after': 'email_id'
-			}
-		],
-		'Donor': [
-			{
-				'fieldname': 'pan_number',
-				'label': 'PAN Details',
-				'fieldtype': 'Data',
-				'insert_after': 'email'
-			}
-		],
 		'Finance Book': [
 			{
 				'fieldname': 'for_income_tax',
diff --git a/erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json b/erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json
deleted file mode 100644
index a8da0bd..0000000
--- a/erpnext/regional/print_format/80g_certificate_for_donation/80g_certificate_for_donation.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "absolute_value": 0,
- "align_labels_right": 0,
- "creation": "2021-02-22 00:17:33.878581",
- "css": ".details {\n    font-size: 15px;\n    font-family: Tahoma, sans-serif;;\n    line-height: 150%;\n}\n\n.certificate-footer {\n    font-size: 15px;\n    font-family: Tahoma, sans-serif;\n    line-height: 140%;\n    margin-top: 120px;\n}\n\n.company-address {\n    color: #666666;\n    font-size: 15px;\n    font-family: Tahoma, sans-serif;;\n}",
- "custom_format": 1,
- "default_print_language": "en",
- "disabled": 0,
- "doc_type": "Tax Exemption 80G Certificate",
- "docstatus": 0,
- "doctype": "Print Format",
- "font": "Default",
- "html": "{% if letter_head and not no_letterhead -%}\n    <div class=\"letter-head\">{{ letter_head }}</div>\n{%- endif %}\n\n<div>\n    <h3 class=\"text-center\">{{ doc.company }} 80G Donor Certificate</h3>\n</div>\n<br><br>\n\n<div class=\"details\">\n    <p> <b>{{ _(\"Certificate No. : \") }}</b> {{ doc.name }} </p>\n    <p>\n    \t<b>{{ _(\"Date\") }} :</b> {{ doc.get_formatted(\"date\") }}<br>\n    </p>\n    <br><br>\n    \n    <div>\n\n        This is to confirm that the {{ doc.company }} received an amount of <b>{{doc.get_formatted(\"amount\")}}</b>\n        from <b>{{ doc.donor_name }}</b>\n        {% if doc.pan_number -%}\n            bearing PAN Number {{ doc.member_pan_number }}\n        {%- endif %}\n\n        via the Mode of Payment {{doc.mode_of_payment}}\n\n        {% if doc.razorpay_payment_id -%}\n            bearing RazorPay Payment ID {{ doc.razorpay_payment_id }}\n        {%- endif %}\n\n        on {{ doc.get_formatted(\"date_of_donation\") }}\n        <br><br>\n        \n        <p>\n            We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n        </p>\n\n    </div>\n</div>\n\n<br><br>\n<p class=\"company-address text-left\"> {{doc.company_address_display }}</p>\n\n<div class=\"certificate-footer text-center\">\n    <p><i>Computer generated receipt - Does not require signature</i></p><br>\n    \n    {% if doc.company_pan_number %}\n    <p>\n        <b>{{ doc.company }}'s PAN Account No :</b> {{ doc.company_pan_number }}\n    <p><br>\n    {% endif %}\n    \n    <p>\n        <b>80G Number : </b> {{ doc.company_80g_number }}\n        {% if doc.company_80g_wef %}\n            ( w.e.f. {{ doc.get_formatted('company_80g_wef') }} )\n        {% endif %}\n    </p><br>\n</div>",
- "idx": 0,
- "line_breaks": 0,
- "modified": "2021-02-22 00:20:08.516600",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "80G Certificate for Donation",
- "owner": "Administrator",
- "print_format_builder": 0,
- "print_format_type": "Jinja",
- "raw_printing": 0,
- "show_section_headings": 0,
- "standard": "Yes"
-}
\ No newline at end of file
diff --git a/erpnext/regional/print_format/80g_certificate_for_donation/__init__.py b/erpnext/regional/print_format/80g_certificate_for_donation/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/print_format/80g_certificate_for_donation/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json b/erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json
deleted file mode 100644
index f1b15aa..0000000
--- a/erpnext/regional/print_format/80g_certificate_for_membership/80g_certificate_for_membership.json
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- "absolute_value": 0,
- "align_labels_right": 0,
- "creation": "2021-02-15 16:53:55.026611",
- "css": ".details {\n    font-size: 15px;\n    font-family: Tahoma, sans-serif;;\n    line-height: 150%;\n}\n\n.certificate-footer {\n    font-size: 15px;\n    font-family: Tahoma, sans-serif;\n    line-height: 140%;\n    margin-top: 120px;\n}\n\n.company-address {\n    color: #666666;\n    font-size: 15px;\n    font-family: Tahoma, sans-serif;;\n}",
- "custom_format": 1,
- "default_print_language": "en",
- "disabled": 0,
- "doc_type": "Tax Exemption 80G Certificate",
- "docstatus": 0,
- "doctype": "Print Format",
- "font": "Default",
- "html": "{% if letter_head and not no_letterhead -%}\n    <div class=\"letter-head\">{{ letter_head }}</div>\n{%- endif %}\n\n<div>\n    <h3 class=\"text-center\">{{ doc.company }} Members 80G Donor Certificate</h3>\n    <h3 class=\"text-center\">Financial Cycle {{ doc.fiscal_year }}</h3>\n</div>\n<br><br>\n\n<div class=\"details\">\n    <p> <b>{{ _(\"Certificate No. : \") }}</b> {{ doc.name }} </p>\n    <p>\n    \t<b>{{ _(\"Date\") }} :</b> {{ doc.get_formatted(\"date\") }}<br>\n    </p>\n    <br><br>\n    \n    <div>\n        This is to confirm that the {{ doc.company }} received a total amount of <b>{{doc.get_formatted(\"total\")}}</b>\n        from <b>{{ doc.member_name }}</b>\n        {% if doc.pan_number -%}\n            bearing PAN Number {{ doc.member_pan_number }}\n        {%- endif %}\n        as per the payment details given below:\n        \n        <br><br>\n        <table class=\"table table-bordered table-condensed\">\n        \t<thead>\n        \t\t<tr>\n        \t\t\t<th >{{ _(\"Date\") }}</th>\n        \t\t\t<th class=\"text-right\">{{ _(\"Amount\") }}</th>\n        \t\t\t<th class=\"text-right\">{{ _(\"Invoice ID\") }}</th>\n        \t\t</tr>\n        \t</thead>\n        \t<tbody>\n        \t\t{%- for payment in doc.payments -%}\n        \t\t<tr>\n        \t\t\t<td> {{ payment.date }} </td>\n        \t\t\t<td class=\"text-right\">{{ payment.get_formatted(\"amount\") }}</td>\n        \t\t\t<td class=\"text-right\">{{ payment.invoice_id }}</td>\n        \t\t</tr>\n        \t\t{%- endfor -%}\n        \t</tbody>\n        </table>\n        \n        <br>\n        \n        <p>\n            We thank you for your contribution towards the corpus of the {{ doc.company }} and helping support our work.\n        </p>\n\n    </div>\n</div>\n\n<br><br>\n<p class=\"company-address text-left\"> {{doc.company_address_display }}</p>\n\n<div class=\"certificate-footer text-center\">\n    <p><i>Computer generated receipt - Does not require signature</i></p><br>\n    \n    {% if doc.company_pan_number %}\n    <p>\n        <b>{{ doc.company }}'s PAN Account No :</b> {{ doc.company_pan_number }}\n    <p><br>\n    {% endif %}\n    \n    <p>\n        <b>80G Number : </b> {{ doc.company_80g_number }}\n        {% if doc.company_80g_wef %}\n            ( w.e.f. {{ doc.get_formatted('company_80g_wef') }} )\n        {% endif %}\n    </p><br>\n</div>",
- "idx": 0,
- "line_breaks": 0,
- "modified": "2021-02-21 23:29:00.778973",
- "modified_by": "Administrator",
- "module": "Regional",
- "name": "80G Certificate for Membership",
- "owner": "Administrator",
- "print_format_builder": 0,
- "print_format_type": "Jinja",
- "raw_printing": 0,
- "show_section_headings": 0,
- "standard": "Yes"
-}
\ No newline at end of file
diff --git a/erpnext/regional/print_format/80g_certificate_for_membership/__init__.py b/erpnext/regional/print_format/80g_certificate_for_membership/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/regional/print_format/80g_certificate_for_membership/__init__.py
+++ /dev/null
diff --git a/erpnext/regional/report/datev/datev.js b/erpnext/regional/report/datev/datev.js
index 4124e3d..03c729e 100644
--- a/erpnext/regional/report/datev/datev.js
+++ b/erpnext/regional/report/datev/datev.js
@@ -40,7 +40,11 @@
 		});
 
 		query_report.page.add_menu_item(__("Download DATEV File"), () => {
-			const filters = JSON.stringify(query_report.get_values());
+			const filters = encodeURIComponent(
+				JSON.stringify(
+					query_report.get_values()
+				)
+			);
 			window.open(`/api/method/erpnext.regional.report.datev.datev.download_datev_csv?filters=${filters}`);
 		});
 
diff --git a/erpnext/regional/report/datev/test_datev.py b/erpnext/regional/report/datev/test_datev.py
index 14d5495..052fb2a 100644
--- a/erpnext/regional/report/datev/test_datev.py
+++ b/erpnext/regional/report/datev/test_datev.py
@@ -1,9 +1,9 @@
 import zipfile
+from io import BytesIO
 from unittest import TestCase
 
 import frappe
 from frappe.utils import cstr, now_datetime, today
-from six import BytesIO
 
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.regional.germany.utils.datev.datev_constants import (
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py
similarity index 100%
copy from erpnext/regional/doctype/tax_exemption_80g_certificate/__init__.py
copy to erpnext/regional/report/e_invoice_summary/__init__.py
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
new file mode 100644
index 0000000..4713217
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
@@ -0,0 +1,55 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["E-Invoice Summary"] = {
+	"filters": [
+		{
+			"fieldtype": "Link",
+			"options": "Company",
+			"reqd": 1,
+			"fieldname": "company",
+			"label": __("Company"),
+			"default": frappe.defaults.get_user_default("Company"),
+		},
+		{
+			"fieldtype": "Link",
+			"options": "Customer",
+			"fieldname": "customer",
+			"label": __("Customer")
+		},
+		{
+			"fieldtype": "Date",
+			"reqd": 1,
+			"fieldname": "from_date",
+			"label": __("From Date"),
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+		},
+		{
+			"fieldtype": "Date",
+			"reqd": 1,
+			"fieldname": "to_date",
+			"label": __("To Date"),
+			"default": frappe.datetime.get_today(),
+		},
+		{
+			"fieldtype": "Select",
+			"fieldname": "status",
+			"label": __("Status"),
+			"options": "\nPending\nGenerated\nCancelled\nFailed"
+		}
+	],
+
+	"formatter": function (value, row, column, data, default_formatter) {
+		value = default_formatter(value, row, column, data);
+
+		if (column.fieldname == "einvoice_status" && value) {
+			if (value == 'Pending') value = `<span class="bold" style="color: var(--text-on-orange)">${value}</span>`;
+			else if (value == 'Generated') value = `<span class="bold" style="color: var(--text-on-green)">${value}</span>`;
+			else if (value == 'Cancelled') value = `<span class="bold" style="color: var(--text-on-red)">${value}</span>`;
+			else if (value == 'Failed') value = `<span class="bold"  style="color: var(--text-on-red)">${value}</span>`;
+		}
+
+		return value;
+	}
+};
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
new file mode 100644
index 0000000..d0000ad
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-12 11:23:37.312294",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Logo",
+ "modified": "2021-03-13 12:36:48.689413",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E-Invoice Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Invoice",
+ "report_name": "E-Invoice Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Administrator"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
new file mode 100644
index 0000000..2110c44
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
@@ -0,0 +1,110 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+
+
+def execute(filters=None):
+	validate_filters(filters)
+
+	columns = get_columns()
+	data = get_data(filters)
+
+	return columns, data
+
+def validate_filters(filters=None):
+	if filters is None:
+		filters = {}
+	filters = frappe._dict(filters)
+
+	if not filters.company:
+		frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter'))
+	if filters.company:
+		# validate if company has e-invoicing enabled
+		pass
+	if not filters.from_date or not filters.to_date:
+		frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter'))
+	if filters.from_date > filters.to_date:
+		frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter'))
+
+def get_data(filters=None):
+	if filters is None:
+		filters = {}
+	query_filters = {
+		'posting_date': ['between', [filters.from_date, filters.to_date]],
+		'einvoice_status': ['is', 'set'],
+		'company': filters.company
+	}
+	if filters.customer:
+		query_filters['customer'] = filters.customer
+	if filters.status:
+		query_filters['einvoice_status'] = filters.status
+
+	data = frappe.get_all(
+		'Sales Invoice',
+		filters=query_filters,
+		fields=[d.get('fieldname') for d in get_columns()]
+	)
+
+	return data
+
+def get_columns():
+	return [
+		{
+			"fieldtype": "Date",
+			"fieldname": "posting_date",
+			"label": _("Posting Date"),
+			"width": 0
+		},
+		{
+			"fieldtype": "Link",
+			"fieldname": "name",
+			"label": _("Sales Invoice"),
+			"options": "Sales Invoice",
+			"width": 140
+		},
+		{
+			"fieldtype": "Data",
+			"fieldname": "einvoice_status",
+			"label": _("Status"),
+			"width": 100
+		},
+		{
+			"fieldtype": "Link",
+			"fieldname": "customer",
+			"options": "Customer",
+			"label": _("Customer")
+		},
+		{
+			"fieldtype": "Check",
+			"fieldname": "is_return",
+			"label": _("Is Return"),
+			"width": 85
+		},
+		{
+			"fieldtype": "Data",
+			"fieldname": "ack_no",
+			"label": "Ack. No.",
+			"width": 145
+		},
+		{
+			"fieldtype": "Data",
+			"fieldname": "ack_date",
+			"label": "Ack. Date",
+			"width": 165
+		},
+		{
+			"fieldtype": "Data",
+			"fieldname": "irn",
+			"label": _("IRN No."),
+			"width": 250
+		},
+		{
+			"fieldtype": "Currency",
+			"options": "Company:company:default_currency",
+			"fieldname": "base_grand_total",
+			"label": _("Grand Total"),
+			"width": 120
+		}
+	]
diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js
index 4b98978..9999a6d 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.js
+++ b/erpnext/regional/report/gstr_1/gstr_1.js
@@ -17,7 +17,7 @@
 			"fieldtype": "Link",
 			"options": "Address",
 			"get_query": function () {
-				var company = frappe.query_report.get_filter_value('company');
+				let company = frappe.query_report.get_filter_value('company');
 				if (company) {
 					return {
 						"query": 'frappe.contacts.doctype.address.address.address_query',
@@ -27,6 +27,11 @@
 			}
 		},
 		{
+			"fieldname": "company_gstin",
+			"label": __("Company GSTIN"),
+			"fieldtype": "Select"
+		},
+		{
 			"fieldname": "from_date",
 			"label": __("From Date"),
 			"fieldtype": "Date",
@@ -60,10 +65,21 @@
 		}
 	],
 	onload: function (report) {
+		let filters = report.get_values();
+
+		frappe.call({
+			method: 'erpnext.regional.report.gstr_1.gstr_1.get_company_gstins',
+			args: {
+				company: filters.company
+			},
+			callback: function(r) {
+				frappe.query_report.page.fields_dict.company_gstin.df.options = r.message;
+				frappe.query_report.page.fields_dict.company_gstin.refresh();
+			}
+		});
+
 
 		report.page.add_inner_button(__("Download as JSON"), function () {
-			var filters = report.get_values();
-
 			frappe.call({
 				method: 'erpnext.regional.report.gstr_1.gstr_1.get_json',
 				args: {
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index e50ff18..8fcb6bb 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -28,7 +28,7 @@
 			posting_date,
 			base_grand_total,
 			base_rounded_total,
-			COALESCE(NULLIF(customer_gstin,''), NULLIF(billing_address_gstin, '')) as customer_gstin,
+			NULLIF(billing_address_gstin, '') as billing_address_gstin,
 			place_of_supply,
 			ecommerce_gstin,
 			reverse_charge,
@@ -253,13 +253,14 @@
 		for opts in (("company", " and company=%(company)s"),
 			("from_date", " and posting_date>=%(from_date)s"),
 			("to_date", " and posting_date<=%(to_date)s"),
-			("company_address", " and company_address=%(company_address)s")):
+			("company_address", " and company_address=%(company_address)s"),
+			("company_gstin", " and company_gstin=%(company_gstin)s")):
 				if self.filters.get(opts[0]):
 					conditions += opts[1]
 
 
 		if self.filters.get("type_of_business") ==  "B2B":
-			conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1 AND is_debit_note !=1"
+			conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Registered Composition', 'Deemed Export', 'SEZ') AND is_return != 1 AND is_debit_note !=1"
 
 		if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
 			b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
@@ -383,7 +384,7 @@
 		for invoice, items in self.invoice_items.items():
 			if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
 				and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
-				and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
+				and self.invoices.get(invoice, {}).get('gst_category') in ("Overseas", "SEZ"):
 					self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
 
 	def get_columns(self):
@@ -409,7 +410,7 @@
 		if self.filters.get("type_of_business") ==  "B2B":
 			self.invoice_columns = [
 				{
-					"fieldname": "customer_gstin",
+					"fieldname": "billing_address_gstin",
 					"label": "GSTIN/UIN of Recipient",
 					"fieldtype": "Data",
 					"width": 150
@@ -516,7 +517,7 @@
 		elif self.filters.get("type_of_business") == "CDNR-REG":
 			self.invoice_columns = [
 				{
-					"fieldname": "customer_gstin",
+					"fieldname": "billing_address_gstin",
 					"label": "GSTIN/UIN of Recipient",
 					"fieldtype": "Data",
 					"width": 150
@@ -817,7 +818,7 @@
 	res = {}
 	if filters["type_of_business"] == "B2B":
 		for item in report_data[:-1]:
-			res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
+			res.setdefault(item["billing_address_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
 
 		out = get_b2b_json(res, gstin)
 		gst_json["b2b"] = out
@@ -841,7 +842,7 @@
 		gst_json["exp"] = out
 	elif filters["type_of_business"] == "CDNR-REG":
 		for item in report_data[:-1]:
-			res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
+			res.setdefault(item["billing_address_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
 
 		out = get_cdnr_reg_json(res, gstin)
 		gst_json["cdnr"] = out
@@ -875,7 +876,7 @@
 	}
 
 def get_b2b_json(res, gstin):
-	inv_type, out = {"Registered Regular": "R", "Deemed Export": "DE", "URD": "URD", "SEZ": "SEZ"}, []
+	out = []
 	for gst_in in res:
 		b2b_item, inv = {"ctin": gst_in, "inv": []}, []
 		if not gst_in: continue
@@ -889,7 +890,7 @@
 			inv_item = get_basic_invoice_detail(invoice[0])
 			inv_item["pos"] = "%02d" % int(invoice[0]["place_of_supply"].split('-')[0])
 			inv_item["rchrg"] = invoice[0]["reverse_charge"]
-			inv_item["inv_typ"] = inv_type.get(invoice[0].get("gst_category", ""),"")
+			inv_item["inv_typ"] = get_invoice_type(invoice[0])
 
 			if inv_item["pos"]=="00": continue
 			inv_item["itms"] = []
@@ -1044,7 +1045,7 @@
 				"ntty": invoice[0]["document_type"],
 				"pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]),
 				"rchrg": invoice[0]["reverse_charge"],
-				"inv_typ": get_invoice_type_for_cdnr(invoice[0])
+				"inv_typ": get_invoice_type(invoice[0])
 			}
 
 			inv_item["itms"] = []
@@ -1069,7 +1070,7 @@
 			"val": abs(flt(items[0]["invoice_value"])),
 			"ntty": items[0]["document_type"],
 			"pos": "%02d" % int(items[0]["place_of_supply"].split('-')[0]),
-			"typ": get_invoice_type_for_cdnrur(items[0])
+			"typ": get_invoice_type(items[0])
 		}
 
 		inv_item["itms"] = []
@@ -1110,29 +1111,21 @@
 
 	return out
 
-def get_invoice_type_for_cdnr(row):
-	if row.get('gst_category') == 'SEZ':
-		if row.get('export_type') == 'WPAY':
-			invoice_type = 'SEWP'
-		else:
-			invoice_type = 'SEWOP'
-	elif row.get('gst_category') == 'Deemed Export':
-		invoice_type = 'DE'
-	elif row.get('gst_category') == 'Registered Regular':
-		invoice_type = 'R'
+def get_invoice_type(row):
+	gst_category = row.get('gst_category')
 
-	return invoice_type
+	if gst_category == 'SEZ':
+		return 'SEWP' if row.get('export_type') == 'WPAY' else 'SEWOP'
 
-def get_invoice_type_for_cdnrur(row):
-	if row.get('gst_category') == 'Overseas':
-		if row.get('export_type') == 'WPAY':
-			invoice_type = 'EXPWP'
-		else:
-			invoice_type = 'EXPWOP'
-	elif row.get('gst_category') == 'Unregistered':
-		invoice_type = 'B2CL'
+	if gst_category == 'Overseas':
+		return 'EXPWP' if row.get('export_type') == 'WPAY' else 'EXPWOP'
 
-	return invoice_type
+	return ({
+		'Deemed Export': 'DE',
+		'Registered Regular': 'R',
+		'Registered Composition': 'R',
+		'Unregistered': 'B2CL'
+	}).get(gst_category)
 
 def get_basic_invoice_detail(row):
 	return {
@@ -1154,7 +1147,7 @@
 	# calculate tax amount added
 	tax = flt((row["taxable_value"]*rate)/100.0, 2)
 	frappe.errprint([tax, tax/2])
-	if row.get("customer_gstin") and gstin[0:2] == row["customer_gstin"][0:2]:
+	if row.get("billing_address_gstin") and gstin[0:2] == row["billing_address_gstin"][0:2]:
 		itm_det.update({"camt": flt(tax/2.0, 2), "samt": flt(tax/2.0, 2)})
 	else:
 		itm_det.update({"iamt": tax})
@@ -1199,4 +1192,24 @@
 	if invoice_detail.place_of_supply.split("-")[0] != invoice_detail.company_gstin[:2]:
 		return True
 	else:
-		return False
\ No newline at end of file
+		return False
+
+
+@frappe.whitelist()
+def get_company_gstins(company):
+	address = frappe.qb.DocType("Address")
+	links = frappe.qb.DocType("Dynamic Link")
+
+	addresses = frappe.qb.from_(address).inner_join(links).on(
+		address.name == links.parent
+	).select(
+		address.gstin
+	).where(
+		links.link_doctype == 'Company'
+	).where(
+		links.link_name == company
+	).run(as_dict=1)
+
+	address_list = [''] + [d.gstin for d in addresses]
+
+	return address_list
\ No newline at end of file
diff --git a/erpnext/regional/saudi_arabia/setup.py b/erpnext/regional/saudi_arabia/setup.py
index 15d524d..d2ef6f3 100644
--- a/erpnext/regional/saudi_arabia/setup.py
+++ b/erpnext/regional/saudi_arabia/setup.py
@@ -102,7 +102,7 @@
 		]
 	}
 
-	create_custom_fields(custom_fields, update=True)
+	create_custom_fields(custom_fields, ignore_validate=True, update=True)
 
 def update_regional_tax_settings(country, company):
 	create_ksa_vat_setting(company)
diff --git a/erpnext/restaurant/__init__.py b/erpnext/restaurant/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/__init__.py b/erpnext/restaurant/doctype/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant/__init__.py b/erpnext/restaurant/doctype/restaurant/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.js b/erpnext/restaurant/doctype/restaurant/restaurant.js
deleted file mode 100644
index 13fda73..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant.js
+++ /dev/null
@@ -1,10 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant', {
-	refresh: function(frm) {
-		frm.add_custom_button(__('Order Entry'), () => {
-			frappe.set_route('Form', 'Restaurant Order Entry');
-		});
-	}
-});
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant.json b/erpnext/restaurant/doctype/restaurant/restaurant.json
deleted file mode 100644
index 8572687..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant.json
+++ /dev/null
@@ -1,309 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "prompt", 
- "beta": 1, 
- "creation": "2017-09-15 12:40:41.546933", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "image", 
-   "fieldtype": "Attach Image", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Image", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 1, 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "company", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Company", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_customer", 
-   "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": "Default Customer", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Customer", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "invoice_series_prefix", 
-   "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": "Invoice Series Prefix", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_4", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "active_menu", 
-   "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": "Active Menu", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant Menu", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "default_tax_template", 
-   "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": "Default Tax Template", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Sales Taxes and Charges Template", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "address", 
-   "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": "Address", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Address", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_field": "image", 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:13:10.185496", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/restaurant/doctype/restaurant/restaurant.py b/erpnext/restaurant/doctype/restaurant/restaurant.py
deleted file mode 100644
index 67838d2..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class Restaurant(Document):
-	pass
diff --git a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py b/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py
deleted file mode 100644
index bfdd052..0000000
--- a/erpnext/restaurant/doctype/restaurant/restaurant_dashboard.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from frappe import _
-
-
-def get_data():
-	return {
-		'fieldname': 'restaurant',
-		'transactions': [
-			{
-				'label': _('Setup'),
-				'items': ['Restaurant Menu', 'Restaurant Table']
-			},
-			{
-				'label': _('Operations'),
-				'items': ['Restaurant Reservation', 'Sales Invoice']
-			}
-		]
-	}
diff --git a/erpnext/restaurant/doctype/restaurant/test_restaurant.py b/erpnext/restaurant/doctype/restaurant/test_restaurant.py
deleted file mode 100644
index f88f980..0000000
--- a/erpnext/restaurant/doctype/restaurant/test_restaurant.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_records = [
-	dict(doctype='Restaurant', name='Test Restaurant 1', company='_Test Company 1',
-		invoice_series_prefix='Test-Rest-1-Inv-', default_customer='_Test Customer 1'),
-	dict(doctype='Restaurant', name='Test Restaurant 2', company='_Test Company 1',
-		invoice_series_prefix='Test-Rest-2-Inv-', default_customer='_Test Customer 1'),
-]
-
-class TestRestaurant(unittest.TestCase):
-	pass
diff --git a/erpnext/restaurant/doctype/restaurant_menu/__init__.py b/erpnext/restaurant/doctype/restaurant_menu/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js
deleted file mode 100644
index da7d43f..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Menu', {
-	setup: function(frm) {
-		frm.add_fetch('item', 'standard_rate', 'rate');
-	},
-});
diff --git a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
deleted file mode 100644
index 1b1610d..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.json
+++ /dev/null
@@ -1,247 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "prompt", 
- "beta": 1, 
- "creation": "2017-09-15 12:48:29.818715", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "restaurant", 
-   "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": "Restaurant", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "1", 
-   "fieldname": "enabled", 
-   "fieldtype": "Check", 
-   "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": "Enabled", 
-   "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
-  }, 
-  {
-   "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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "price_list", 
-   "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": "Price List (Auto created)", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Price List", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "items_section", 
-   "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": "Items", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "items", 
-   "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": "Items", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant Menu 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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:13:13.684500", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant Menu", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Restaurant Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/restaurant/doctype/restaurant_menu/restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py
deleted file mode 100644
index 64eb40f..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/restaurant_menu.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import frappe
-from frappe.model.document import Document
-
-
-class RestaurantMenu(Document):
-	def validate(self):
-		for d in self.items:
-			if not d.rate:
-				d.rate = frappe.db.get_value('Item', d.item, 'standard_rate')
-
-	def on_update(self):
-		'''Sync Price List'''
-		self.make_price_list()
-
-	def on_trash(self):
-		'''clear prices'''
-		self.clear_item_price()
-
-	def clear_item_price(self, price_list=None):
-		'''clear all item prices for this menu'''
-		if not price_list:
-			price_list = self.get_price_list().name
-		frappe.db.sql('delete from `tabItem Price` where price_list = %s', price_list)
-
-	def make_price_list(self):
-		# create price list for menu
-		price_list = self.get_price_list()
-		self.db_set('price_list', price_list.name)
-
-		# delete old items
-		self.clear_item_price(price_list.name)
-
-		for d in self.items:
-			frappe.get_doc(dict(
-				doctype = 'Item Price',
-				price_list = price_list.name,
-				item_code = d.item,
-				price_list_rate = d.rate
-			)).insert()
-
-	def get_price_list(self):
-		'''Create price list for menu if missing'''
-		price_list_name = frappe.db.get_value('Price List', dict(restaurant_menu=self.name))
-		if price_list_name:
-			price_list = frappe.get_doc('Price List', price_list_name)
-		else:
-			price_list = frappe.new_doc('Price List')
-			price_list.restaurant_menu = self.name
-			price_list.price_list_name = self.name
-
-		price_list.enabled = 1
-		price_list.selling = 1
-		price_list.save()
-
-		return price_list
diff --git a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py b/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py
deleted file mode 100644
index 27020eb..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu/test_restaurant_menu.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-import frappe
-
-test_records = [
-	dict(doctype='Item', item_code='Food Item 1',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Item', item_code='Food Item 2',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Item', item_code='Food Item 3',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Item', item_code='Food Item 4',
-		item_group='Products', is_stock_item=0),
-	dict(doctype='Restaurant Menu', restaurant='Test Restaurant 1', name='Test Restaurant 1 Menu 1',
-		items = [
-			dict(item='Food Item 1', rate=400),
-			dict(item='Food Item 2', rate=300),
-			dict(item='Food Item 3', rate=200),
-			dict(item='Food Item 4', rate=100),
-		]),
-	dict(doctype='Restaurant Menu', restaurant='Test Restaurant 1', name='Test Restaurant 1 Menu 2',
-		items = [
-			dict(item='Food Item 1', rate=450),
-			dict(item='Food Item 2', rate=350),
-		])
-]
-
-class TestRestaurantMenu(unittest.TestCase):
-	def test_price_list_creation_and_editing(self):
-		menu1 = frappe.get_doc('Restaurant Menu', 'Test Restaurant 1 Menu 1')
-		menu1.save()
-
-		menu2 = frappe.get_doc('Restaurant Menu', 'Test Restaurant 1 Menu 2')
-		menu2.save()
-
-		self.assertTrue(frappe.db.get_value('Price List', 'Test Restaurant 1 Menu 1'))
-		self.assertEqual(frappe.db.get_value('Item Price',
-			dict(price_list = 'Test Restaurant 1 Menu 1', item_code='Food Item 1'), 'price_list_rate'), 400)
-		self.assertEqual(frappe.db.get_value('Item Price',
-			dict(price_list = 'Test Restaurant 1 Menu 2', item_code='Food Item 1'), 'price_list_rate'), 450)
-
-		menu1.items[0].rate = 401
-		menu1.save()
-
-		self.assertEqual(frappe.db.get_value('Item Price',
-			dict(price_list = 'Test Restaurant 1 Menu 1', item_code='Food Item 1'), 'price_list_rate'), 401)
-
-		menu1.items[0].rate = 400
-		menu1.save()
diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py b/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu_item/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json
deleted file mode 100644
index 87568bf..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 0, 
- "creation": "2017-09-15 12:49:36.072636", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "item", 
-   "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", 
-   "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
-  }, 
-  {
-   "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": "", 
-   "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
-  }
- ], 
- "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-09-15 14:18:55.145088", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant Menu Item", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py b/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py
deleted file mode 100644
index 98b245e..0000000
--- a/erpnext/restaurant/doctype/restaurant_menu_item/restaurant_menu_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class RestaurantMenuItem(Document):
-	pass
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py b/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
deleted file mode 100644
index 8df851c..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.js
+++ /dev/null
@@ -1,162 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Order Entry', {
-	setup: function(frm) {
-		let get_item_query = () => {
-			return {
-				query: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.item_query_restaurant',
-				filters: {
-					'table': frm.doc.restaurant_table
-				}
-			};
-		};
-		frm.set_query('item', 'items', get_item_query);
-		frm.set_query('add_item', get_item_query);
-	},
-	onload_post_render: function(frm) {
-		if(!frm.item_selector) {
-			frm.item_selector = new erpnext.ItemSelector({
-				frm: frm,
-				item_field: 'item',
-				item_query: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.item_query_restaurant',
-				get_filters: () => {
-					return {table: frm.doc.restaurant_table};
-				}
-			});
-		}
-
-		let $input = frm.get_field('add_item').$input;
-
-		$input.on('keyup', function(e) {
-			if (e.which===13) {
-				if (frm.clear_item_timeout) {
-					clearTimeout (frm.clear_item_timeout);
-				}
-
-				// clear the item input so user can enter a new item
-				frm.clear_item_timeout = setTimeout (() => {
-					frm.set_value('add_item', '');
-				}, 1000);
-
-				let item = $input.val();
-
-				if (!item) return;
-
-				var added = false;
-				(frm.doc.items || []).forEach((d) => {
-					if (d.item===item) {
-						d.qty += 1;
-						added = true;
-					}
-				});
-
-				return frappe.run_serially([
-					() => {
-						if (!added) {
-							return frm.add_child('items', {item: item, qty: 1});
-						}
-					},
-					() => frm.get_field("items").refresh()
-				]);
-			}
-		});
-	},
-	refresh: function(frm) {
-		frm.disable_save();
-		frm.add_custom_button(__('Update'), () => {
-			return frm.trigger('sync');
-		});
-		frm.add_custom_button(__('Clear'), () => {
-			return frm.trigger('clear');
-		});
-		frm.add_custom_button(__('Bill'), () => {
-			return frm.trigger('make_invoice');
-		});
-	},
-	clear: function(frm) {
-		frm.doc.add_item = '';
-		frm.doc.grand_total = 0;
-		frm.doc.items = [];
-		frm.refresh();
-		frm.get_field('add_item').$input.focus();
-	},
-	restaurant_table: function(frm) {
-		// select the open sales order items for this table
-		if (!frm.doc.restaurant_table) {
-			return;
-		}
-		return frappe.call({
-			method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.get_invoice',
-			args: {
-				table: frm.doc.restaurant_table
-			},
-			callback: (r) => {
-				frm.events.set_invoice_items(frm, r);
-			}
-		});
-	},
-	sync: function(frm) {
-		return frappe.call({
-			method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.sync',
-			args: {
-				table: frm.doc.restaurant_table,
-				items: frm.doc.items
-			},
-			callback: (r) => {
-				frm.events.set_invoice_items(frm, r);
-				frappe.show_alert({message: __('Saved'), indicator: 'green'});
-			}
-		});
-
-	},
-	make_invoice: function(frm) {
-		frm.trigger('sync').then(() => {
-			frappe.prompt([
-				{
-					fieldname: 'customer',
-					label: __('Customer'),
-					fieldtype: 'Link',
-					reqd: 1,
-					options: 'Customer',
-					'default': frm.invoice.customer
-				},
-				{
-					fieldname: 'mode_of_payment',
-					label: __('Mode of Payment'),
-					fieldtype: 'Link',
-					reqd: 1,
-					options: 'Mode of Payment',
-					'default': frm.mode_of_payment || ''
-				}
-			], (data) => {
-				// cache this for next entry
-				frm.mode_of_payment = data.mode_of_payment;
-				return frappe.call({
-					method: 'erpnext.restaurant.doctype.restaurant_order_entry.restaurant_order_entry.make_invoice',
-					args: {
-						table: frm.doc.restaurant_table,
-						customer: data.customer,
-						mode_of_payment: data.mode_of_payment
-					},
-					callback: (r) => {
-						frm.set_value('last_sales_invoice', r.message);
-						frm.trigger('clear');
-					}
-				});
-			},
-			__("Select Customer"));
-		});
-	},
-	set_invoice_items: function(frm, r) {
-		let invoice = r.message;
-		frm.doc.items = [];
-		(invoice.items || []).forEach((d) => {
-			frm.add_child('items', {item: d.item_code, qty: d.qty, rate: d.rate});
-		});
-		frm.set_value('grand_total', invoice.grand_total);
-		frm.set_value('last_sales_invoice', invoice.name);
-		frm.invoice = invoice;
-		frm.refresh();
-	}
-});
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json
deleted file mode 100644
index 3e4d593..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.json
+++ /dev/null
@@ -1,280 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 1, 
- "creation": "2017-09-15 15:10:24.530365", 
- "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": "restaurant_table", 
-   "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": "Restaurant Table", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant Table", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "restaurant_table", 
-   "description": "Click Enter To Add", 
-   "fieldname": "add_item", 
-   "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": "Add Item", 
-   "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": 0, 
-   "search_index": 0, 
-   "set_only_once": 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, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "grand_total", 
-   "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": "Grand Total", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 1, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "last_sales_invoice", 
-   "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": "Last Sales Invoice", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Sales Invoice", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "restaurant_table", 
-   "fieldname": "current_order", 
-   "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": "Current Order", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "restaurant_table", 
-   "fieldname": "items", 
-   "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": "Items", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant Order Entry 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": 0, 
-   "search_index": 0, 
-   "set_only_once": 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": 1, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-10-04 17:06:20.926999", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant Order Entry", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 0, 
-   "role": "Restaurant Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py b/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
deleted file mode 100644
index f9e75b4..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry/restaurant_order_entry.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import json
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-
-from erpnext.controllers.queries import item_query
-
-
-class RestaurantOrderEntry(Document):
-	pass
-
-@frappe.whitelist()
-def get_invoice(table):
-	'''returns the active invoice linked to the given table'''
-	invoice_name = frappe.get_value('Sales Invoice', dict(restaurant_table = table, docstatus=0))
-	restaurant, menu_name = get_restaurant_and_menu_name(table)
-	if invoice_name:
-		invoice = frappe.get_doc('Sales Invoice', invoice_name)
-	else:
-		invoice = frappe.new_doc('Sales Invoice')
-		invoice.naming_series = frappe.db.get_value('Restaurant', restaurant, 'invoice_series_prefix')
-		invoice.is_pos = 1
-		default_customer = frappe.db.get_value('Restaurant', restaurant, 'default_customer')
-		if not default_customer:
-			frappe.throw(_('Please set default customer in Restaurant Settings'))
-		invoice.customer = default_customer
-
-	invoice.taxes_and_charges = frappe.db.get_value('Restaurant', restaurant, 'default_tax_template')
-	invoice.selling_price_list = frappe.db.get_value('Price List', dict(restaurant_menu=menu_name, enabled=1))
-
-	return invoice
-
-@frappe.whitelist()
-def sync(table, items):
-	'''Sync the sales order related to the table'''
-	invoice = get_invoice(table)
-	items = json.loads(items)
-
-	invoice.items = []
-	invoice.restaurant_table = table
-	for d in items:
-		invoice.append('items', dict(
-			item_code = d.get('item'),
-			qty = d.get('qty')
-		))
-
-	invoice.save()
-	return invoice.as_dict()
-
-@frappe.whitelist()
-def make_invoice(table, customer, mode_of_payment):
-	'''Make table based on Sales Order'''
-	restaurant, menu = get_restaurant_and_menu_name(table)
-	invoice = get_invoice(table)
-	invoice.customer = customer
-	invoice.restaurant = restaurant
-	invoice.calculate_taxes_and_totals()
-	invoice.append('payments', dict(mode_of_payment=mode_of_payment, amount=invoice.grand_total))
-	invoice.save()
-	invoice.submit()
-
-	frappe.msgprint(_('Invoice Created'), indicator='green', alert=True)
-
-	return invoice.name
-
-@frappe.whitelist()
-def item_query_restaurant(doctype='Item', txt='', searchfield='name', start=0, page_len=20, filters=None, as_dict=False):
-	'''Return items that are selected in active menu of the restaurant'''
-	restaurant, menu = get_restaurant_and_menu_name(filters['table'])
-	items = frappe.db.get_all('Restaurant Menu Item', ['item'], dict(parent = menu))
-	del filters['table']
-	filters['name'] = ('in', [d.item for d in items])
-
-	return item_query('Item', txt, searchfield, start, page_len, filters, as_dict)
-
-def get_restaurant_and_menu_name(table):
-	if not table:
-		frappe.throw(_('Please select a table'))
-
-	restaurant = frappe.db.get_value('Restaurant Table', table, 'restaurant')
-	menu = frappe.db.get_value('Restaurant', restaurant, 'active_menu')
-
-	if not menu:
-		frappe.throw(_('Please set an active menu for Restaurant {0}').format(restaurant))
-
-	return restaurant, menu
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry_item/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json
deleted file mode 100644
index 0240013..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.json
+++ /dev/null
@@ -1,163 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2017-09-15 15:11:50.313241", 
- "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": "item", 
-   "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", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "qty", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "served", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Served", 
-   "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
-  }, 
-  {
-   "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, 
-   "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
-  }
- ], 
- "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-09-21 08:39:27.232175", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant Order Entry Item", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py b/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py
deleted file mode 100644
index 0d9c236..0000000
--- a/erpnext/restaurant/doctype/restaurant_order_entry_item/restaurant_order_entry_item.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from frappe.model.document import Document
-
-
-class RestaurantOrderEntryItem(Document):
-	pass
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/__init__.py b/erpnext/restaurant/doctype/restaurant_reservation/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js
deleted file mode 100644
index cebd105..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Reservation', {
-	setup: function(frm) {
-		frm.add_fetch('customer', 'customer_name', 'customer_name');
-	},
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
deleted file mode 100644
index 17df2b9..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.json
+++ /dev/null
@@ -1,355 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "RES-RES-.YYYY.-.#####", 
- "beta": 1, 
- "creation": "2017-09-15 13:05:51.063661", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Open\nWaitlisted\nCancelled\nNo Show\nSuccess", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "restaurant", 
-   "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": "Restaurant", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "no_of_people", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "No of People", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reservation_time", 
-   "fieldtype": "Datetime", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Reservation Time", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "reservation_end_time", 
-   "fieldtype": "Datetime", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Reservation End Time", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_4", 
-   "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_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "customer", 
-   "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": "Customer", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Customer", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "customer_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 1, 
-   "in_list_view": 1, 
-   "in_standard_filter": 1, 
-   "label": "Customer 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": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "contact_number", 
-   "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": "Contact Number", 
-   "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": 0, 
- "max_attachments": 0, 
- "modified": "2018-08-21 16:15:38.435656", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant Reservation", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Restaurant Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
-}
\ No newline at end of file
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py
deleted file mode 100644
index 02ffaf6..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-from datetime import timedelta
-
-from frappe.model.document import Document
-from frappe.utils import get_datetime
-
-
-class RestaurantReservation(Document):
-	def validate(self):
-		if not self.reservation_end_time:
-			self.reservation_end_time = get_datetime(self.reservation_time) + timedelta(hours=1)
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js b/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js
deleted file mode 100644
index fe3dc57..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/restaurant_reservation_calendar.js
+++ /dev/null
@@ -1,18 +0,0 @@
-frappe.views.calendar["Restaurant Reservation"] = {
-	field_map: {
-		"start": "reservation_time",
-		"end": "reservation_end_time",
-		"id": "name",
-		"title": "customer_name",
-		"allDay": "allDay",
-	},
-	gantt: true,
-	filters: [
-		{
-			"fieldtype": "Data",
-			"fieldname": "customer_name",
-			"label": __("Customer Name")
-		}
-	],
-	get_events_method: "frappe.desk.calendar.get_events"
-};
diff --git a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py b/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py
deleted file mode 100644
index 11a3541..0000000
--- a/erpnext/restaurant/doctype/restaurant_reservation/test_restaurant_reservation.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-
-class TestRestaurantReservation(unittest.TestCase):
-	pass
diff --git a/erpnext/restaurant/doctype/restaurant_table/__init__.py b/erpnext/restaurant/doctype/restaurant_table/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/__init__.py
+++ /dev/null
diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js
deleted file mode 100644
index a55605c..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Restaurant Table', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
deleted file mode 100644
index 5fc6e62..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.json
+++ /dev/null
@@ -1,156 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 1, 
- "creation": "2017-09-15 12:45:24.717355", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "restaurant", 
-   "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": "Restaurant", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Restaurant", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "no_of_seats", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "No of Seats", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "1", 
-   "fieldname": "minimum_seating", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Minimum Seating", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2017-12-09 12:13:24.382345", 
- "modified_by": "Administrator", 
- "module": "Restaurant", 
- "name": "Restaurant Table", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Restaurant Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Hospitality", 
- "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/restaurant/doctype/restaurant_table/restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py
deleted file mode 100644
index 29f8a1a..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/restaurant_table.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-
-import re
-
-from frappe.model.document import Document
-from frappe.model.naming import make_autoname
-
-
-class RestaurantTable(Document):
-	def autoname(self):
-		prefix = re.sub('-+', '-', self.restaurant.replace(' ', '-'))
-		self.name = make_autoname(prefix + '-.##')
diff --git a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py b/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py
deleted file mode 100644
index 00d14d2..0000000
--- a/erpnext/restaurant/doctype/restaurant_table/test_restaurant_table.py
+++ /dev/null
@@ -1,14 +0,0 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-import unittest
-
-test_records = [
-	dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
-	dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
-	dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
-	dict(restaurant='Test Restaurant 1', no_of_seats=5, minimum_seating=1),
-]
-
-class TestRestaurantTable(unittest.TestCase):
-	pass
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index b7f74df..7742f26 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -142,7 +142,7 @@
 			self.update_lead_status()
 
 		if self.flags.is_new_doc:
-			self.create_lead_address_contact()
+			self.link_lead_address_and_contact()
 
 		self.update_customer_groups()
 
@@ -176,62 +176,24 @@
 		if self.lead_name:
 			frappe.db.set_value("Lead", self.lead_name, "status", "Converted")
 
-	def create_lead_address_contact(self):
+	def link_lead_address_and_contact(self):
 		if self.lead_name:
-			# assign lead address to customer (if already not set)
-			address_names = frappe.get_all('Dynamic Link', filters={
-								"parenttype":"Address",
-								"link_doctype":"Lead",
-								"link_name":self.lead_name
-							}, fields=["parent as name"])
+			# assign lead address and contact to customer (if already not set)
+			linked_contacts_and_addresses = frappe.get_all(
+				"Dynamic Link",
+				filters=[
+					["parenttype", "in", ["Contact", "Address"]],
+					["link_doctype", "=", "Lead"],
+					["link_name", "=", self.lead_name],
+				],
+				fields=["parent as name", "parenttype as doctype"],
+			)
 
-			for address_name in address_names:
-				address = frappe.get_doc('Address', address_name.get('name'))
-				if not address.has_link('Customer', self.name):
-					address.append('links', dict(link_doctype='Customer', link_name=self.name))
-					address.save(ignore_permissions=self.flags.ignore_permissions)
-
-			lead = frappe.db.get_value("Lead", self.lead_name, ["company_name", "lead_name", "email_id", "phone", "mobile_no", "gender", "salutation"], as_dict=True)
-
-			if not lead.lead_name:
-				frappe.throw(_("Please mention the Lead Name in Lead {0}").format(self.lead_name))
-
-			contact_names = frappe.get_all('Dynamic Link', filters={
-								"parenttype":"Contact",
-								"link_doctype":"Lead",
-								"link_name":self.lead_name
-							}, fields=["parent as name"])
-
-			for contact_name in contact_names:
-				contact = frappe.get_doc('Contact', contact_name.get('name'))
-				if not contact.has_link('Customer', self.name):
-					contact.append('links', dict(link_doctype='Customer', link_name=self.name))
-					contact.save(ignore_permissions=self.flags.ignore_permissions)
-
-			if not contact_names:
-				lead.lead_name = lead.lead_name.lstrip().split(" ")
-				lead.first_name = lead.lead_name[0]
-				lead.last_name = " ".join(lead.lead_name[1:])
-
-				# create contact from lead
-				contact = frappe.new_doc('Contact')
-				contact.first_name = lead.first_name
-				contact.last_name = lead.last_name
-				contact.gender = lead.gender
-				contact.salutation = lead.salutation
-				contact.email_id = lead.email_id
-				contact.phone = lead.phone
-				contact.mobile_no = lead.mobile_no
-				contact.is_primary_contact = 1
-				contact.append('links', dict(link_doctype='Customer', link_name=self.name))
-				if lead.email_id:
-					contact.append('email_ids', dict(email_id=lead.email_id, is_primary=1))
-				if lead.mobile_no:
-					contact.append('phone_nos', dict(phone=lead.mobile_no, is_primary_mobile_no=1))
-				contact.flags.ignore_permissions = self.flags.ignore_permissions
-				contact.autoname()
-				if not frappe.db.exists("Contact", contact.name):
-					contact.insert()
+			for row in linked_contacts_and_addresses:
+				linked_doc = frappe.get_doc(row.doctype, row.name)
+				if not linked_doc.has_link('Customer', self.name):
+					linked_doc.append('links', dict(link_doctype='Customer', link_name=self.name))
+					linked_doc.save(ignore_permissions=self.flags.ignore_permissions)
 
 	def validate_name_with_customer_group(self):
 		if frappe.db.exists("Customer Group", self.name):
@@ -296,30 +258,6 @@
 				.format(frappe.bold(self.customer_name))
 			)
 
-	def create_onboarding_docs(self, args):
-		defaults = frappe.defaults.get_defaults()
-		company = defaults.get('company') or \
-			frappe.db.get_single_value('Global Defaults', 'default_company')
-
-		for i in range(1, args.get('max_count')):
-			customer = args.get('customer_name_' + str(i))
-			if customer:
-				try:
-					doc = frappe.get_doc({
-						'doctype': self.doctype,
-						'customer_name': customer,
-						'customer_type': 'Company',
-						'customer_group': _('Commercial'),
-						'territory': defaults.get('country'),
-						'company': company
-					}).insert()
-
-					if args.get('customer_email_' + str(i)):
-						create_contact(customer, self.doctype,
-							doc.name, args.get("customer_email_" + str(i)))
-				except frappe.NameError:
-					pass
-
 def create_contact(contact, party_type, party, email):
 	"""Create contact based on given contact name"""
 	contact = contact.split(' ')
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 5301fd0..165ee81 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -4,12 +4,13 @@
 
 import frappe
 from frappe.test_runner import make_test_records
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import flt
 
 from erpnext.accounts.party import get_due_date
 from erpnext.exceptions import PartyDisabled, PartyFrozen
 from erpnext.selling.doctype.customer.customer import get_credit_limit, get_customer_outstanding
-from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address
+from erpnext.tests.utils import create_test_contact_and_address
 
 test_ignore = ["Price List"]
 test_dependencies = ['Payment Term', 'Payment Terms Template']
@@ -17,7 +18,7 @@
 
 
 
-class TestCustomer(ERPNextTestCase):
+class TestCustomer(FrappeTestCase):
 	def setUp(self):
 		if not frappe.get_value('Item', '_Test Item'):
 			make_test_records('Item')
diff --git a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
index b951044..9b672b4 100644
--- a/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
+++ b/erpnext/selling/doctype/party_specific_item/test_party_specific_item.py
@@ -1,12 +1,10 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
-
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.controllers.queries import item_query
-from erpnext.tests.utils import ERPNextTestCase
 
 test_dependencies = ['Item', 'Customer', 'Supplier']
 
@@ -18,7 +16,7 @@
 	psi.based_on_value = args.get('based_on_value')
 	psi.insert()
 
-class TestPartySpecificItem(ERPNextTestCase):
+class TestPartySpecificItem(FrappeTestCase):
 	def setUp(self):
 		self.customer = frappe.get_last_doc("Customer")
 		self.supplier = frappe.get_last_doc("Supplier")
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index daab6fb..eebde76 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -287,7 +287,7 @@
 				customer = frappe.get_doc(customer_doclist)
 				customer.flags.ignore_permissions = ignore_permissions
 				if quotation.get("party_name") == "Shopping Cart":
-					customer.customer_group = frappe.db.get_value("Shopping Cart Settings", None,
+					customer.customer_group = frappe.db.get_value("E Commerce Settings", None,
 						"default_customer_group")
 
 				try:
diff --git a/erpnext/selling/doctype/quotation/quotation_list.js b/erpnext/selling/doctype/quotation/quotation_list.js
index b631685..4c8f9c4 100644
--- a/erpnext/selling/doctype/quotation/quotation_list.js
+++ b/erpnext/selling/doctype/quotation/quotation_list.js
@@ -12,6 +12,14 @@
 				};
 			};
 		}
+
+		listview.page.add_action_item(__("Sales Order"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Quotation", "Sales Order");
+		});
+
+		listview.page.add_action_item(__("Sales Invoice"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Quotation", "Sales Invoice");
+		});
 	},
 
 	get_indicator: function(doc) {
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 4357201..a749d9e 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -2,14 +2,13 @@
 # License: GNU General Public License v3. See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, add_months, flt, getdate, nowdate
 
-from erpnext.tests.utils import ERPNextTestCase
-
 test_dependencies = ["Product Bundle"]
 
 
-class TestQuotation(ERPNextTestCase):
+class TestQuotation(FrappeTestCase):
 	def test_make_quotation_without_terms(self):
 		quotation = make_quotation(do_not_save=1)
 		self.assertFalse(quotation.get('payment_schedule'))
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index 8b53902..31a9589 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -649,7 +649,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-02-23 01:13:54.670763",
+ "modified": "2021-07-15 12:40:51.074820",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation Item",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 79e9e17..eb98e6c 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -457,12 +457,8 @@
 	make_delivery_note_based_on_delivery_date() {
 		var me = this;
 
-		var delivery_dates = [];
-		$.each(this.frm.doc.items || [], function(i, d) {
-			if(!delivery_dates.includes(d.delivery_date)) {
-				delivery_dates.push(d.delivery_date);
-			}
-		});
+		var delivery_dates = this.frm.doc.items.map(i => i.delivery_date);
+		delivery_dates = [ ...new Set(delivery_dates) ];
 
 		var item_grid = this.frm.fields_dict["items"].grid;
 		if(!item_grid.get_selected().length && delivery_dates.length > 1) {
@@ -500,14 +496,7 @@
 
 				if(!dates) return;
 
-				$.each(dates, function(i, d) {
-					$.each(item_grid.grid_rows || [], function(j, row) {
-						if(row.doc.delivery_date == d) {
-							row.doc.__checked = 1;
-						}
-					});
-				})
-				me.make_delivery_note();
+				me.make_delivery_note(dates);
 				dialog.hide();
 			});
 			dialog.show();
@@ -516,10 +505,13 @@
 		}
 	}
 
-	make_delivery_note() {
+	make_delivery_note(delivery_dates) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
-			frm: this.frm
+			frm: this.frm,
+			args: {
+				delivery_dates
+			}
 		})
 	}
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index cc95185..0f5b1e3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -565,6 +565,13 @@
 	}
 
 	if not skip_item_mapping:
+		def condition(doc):
+			# make_mapped_doc sets js `args` into `frappe.flags.args`
+			if frappe.flags.args and frappe.flags.args.delivery_dates:
+				if cstr(doc.delivery_date) not in frappe.flags.args.delivery_dates:
+					return False
+			return abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+
 		mapper["Sales Order Item"] = {
 			"doctype": "Delivery Note Item",
 			"field_map": {
@@ -573,7 +580,7 @@
 				"parent": "against_sales_order",
 			},
 			"postprocess": update_item,
-			"condition": lambda doc: abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier!=1
+			"condition": condition
 		}
 
 	target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
diff --git a/erpnext/selling/doctype/sales_order/sales_order_list.js b/erpnext/selling/doctype/sales_order/sales_order_list.js
index 26d96d5..4691190 100644
--- a/erpnext/selling/doctype/sales_order/sales_order_list.js
+++ b/erpnext/selling/doctype/sales_order/sales_order_list.js
@@ -16,7 +16,7 @@
 				return [__("Overdue"), "red",
 					"per_delivered,<,100|delivery_date,<,Today|status,!=,Closed"];
 			} else if (flt(doc.grand_total) === 0) {
-				// not delivered (zero-amount order)
+				// not delivered (zeroount order)
 				return [__("To Deliver"), "orange",
 					"per_delivered,<,100|grand_total,=,0|status,!=,Closed"];
 			} else if (flt(doc.per_billed, 6) < 100) {
@@ -48,5 +48,17 @@
 			listview.call_for_selected_items(method, {"status": "Submitted"});
 		});
 
+		listview.page.add_action_item(__("Sales Invoice"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Sales Invoice");
+		});
+
+		listview.page.add_action_item(__("Delivery Note"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Delivery Note");
+		});
+
+		listview.page.add_action_item(__("Advance Payment"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Sales Order", "Advance Payment");
+		});
+
 	}
 };
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 42bc0b7..f5a34c0 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -6,7 +6,8 @@
 import frappe
 import frappe.permissions
 from frappe.core.doctype.user_permission.test_user_permission import create_user
-from frappe.utils import add_days, flt, getdate, nowdate
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days, flt, getdate, nowdate, today
 
 from erpnext.controllers.accounts_controller import update_child_qty_rate
 from erpnext.maintenance.doctype.maintenance_schedule.test_maintenance_schedule import (
@@ -27,10 +28,9 @@
 )
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestSalesOrder(ERPNextTestCase):
+class TestSalesOrder(FrappeTestCase):
 
 	@classmethod
 	def setUpClass(cls):
@@ -1375,6 +1375,72 @@
 
 		automatically_fetch_payment_terms(enable=0)
 
+	def test_zero_amount_sales_order_billing_status(self):
+		from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+
+		so = make_sales_order(uom="Nos", do_not_save=1)
+		so.items[0].rate = 0
+		so.save()
+		so.submit()
+
+		self.assertEqual(so.net_total, 0)
+		self.assertEqual(so.billing_status, 'Not Billed')
+
+		si = create_sales_invoice(qty=10, do_not_save=1)
+		si.price_list = '_Test Price List'
+		si.items[0].rate = 0
+		si.items[0].price_list_rate = 0
+		si.items[0].sales_order = so.name
+		si.items[0].so_detail = so.items[0].name
+		si.save()
+		si.submit()
+
+		self.assertEqual(si.net_total, 0)
+		so.load_from_db()
+		self.assertEqual(so.billing_status, 'Fully Billed')
+
+	def test_so_back_updated_from_wo_via_mr(self):
+		"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
+		from erpnext.manufacturing.doctype.work_order.work_order import (
+			make_stock_entry as make_se_from_wo,
+		)
+		from erpnext.stock.doctype.material_request.material_request import raise_work_orders
+
+		so = make_sales_order(item_list=[{"item_code": "_Test FG Item","qty": 2, "rate":100}])
+
+		mr = make_material_request(so.name)
+		mr.material_request_type = "Manufacture"
+		mr.schedule_date = today()
+		mr.submit()
+
+		# WO from MR
+		wo_name = raise_work_orders(mr.name)[0]
+		wo = frappe.get_doc("Work Order", wo_name)
+		wo.wip_warehouse = "Work In Progress - _TC"
+		wo.skip_transfer = True
+
+		self.assertEqual(wo.sales_order, so.name)
+		self.assertEqual(wo.sales_order_item, so.items[0].name)
+
+		wo.submit()
+		make_stock_entry(item_code="_Test Item", # Stock RM
+			target="Work In Progress - _TC",
+			qty=4, basic_rate=100
+		)
+		make_stock_entry(item_code="_Test Item Home Desktop 100", # Stock RM
+			target="Work In Progress - _TC",
+			qty=4, basic_rate=100
+		)
+
+		se = frappe.get_doc(make_se_from_wo(wo.name, "Manufacture", 2))
+		se.submit() # Finish WO
+
+		mr.reload()
+		wo.reload()
+		so.reload()
+		self.assertEqual(so.items[0].work_order_qty, wo.produced_qty)
+		self.assertEqual(mr.status, "Manufactured")
+
 def automatically_fetch_payment_terms(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
 	accounts_settings.automatically_fetch_payment_terms = enable
diff --git a/erpnext/selling/doctype/sales_order_item/sales_order_item.json b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
index 95f6c4e..7e55499 100644
--- a/erpnext/selling/doctype/sales_order_item/sales_order_item.json
+++ b/erpnext/selling/doctype/sales_order_item/sales_order_item.json
@@ -793,6 +793,7 @@
   },
   {
    "default": "0",
+   "fetch_from": "item_code.grant_commission",
    "fieldname": "grant_commission",
    "fieldtype": "Check",
    "label": "Grant Commission",
@@ -802,7 +803,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-10-05 12:27:25.014789",
+ "modified": "2022-02-24 14:41:57.325799",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order Item",
@@ -811,5 +812,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 27bc541..7c4a3f6 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -80,7 +80,7 @@
    "description": "How often should Project and Company be updated based on Sales Transactions?",
    "fieldname": "sales_update_frequency",
    "fieldtype": "Select",
-   "label": "Sales Update Frequency",
+   "label": "Sales Update Frequency in Company and Project",
    "options": "Each Transaction\nDaily\nMonthly",
    "reqd": 1
   },
@@ -171,7 +171,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-09-13 12:32:17.004404",
+ "modified": "2022-02-04 15:41:59.939261",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
@@ -189,5 +189,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py
index fb86e61..e1ef635 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.py
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.py
@@ -16,7 +16,7 @@
 		self.toggle_editable_rate_for_bundle_items()
 
 	def validate(self):
-		for key in ["cust_master_name", "campaign_naming_by", "customer_group", "territory",
+		for key in ["cust_master_name", "customer_group", "territory",
 			"maintain_same_sales_rate", "editable_price_list_rate", "selling_price_list"]:
 				frappe.db.set_default(key, self.get(key, ""))
 
diff --git a/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json b/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json
deleted file mode 100644
index 92d00bc..0000000
--- a/erpnext/selling/onboarding_slide/add_a_few_customers/add_a_few_customers.json
+++ /dev/null
@@ -1,49 +0,0 @@
-{
- "add_more_button": 1,
- "app": "ERPNext",
- "creation": "2019-11-15 14:44:10.065014",
- "docstatus": 0,
- "doctype": "Onboarding Slide",
- "domains": [],
- "help_links": [
-  {
-   "label": "Learn More",
-   "video_id": "zsrrVDk6VBs"
-  }
- ],
- "idx": 0,
- "image_src": "",
- "is_completed": 0,
- "max_count": 3,
- "modified": "2019-12-09 17:54:01.686006",
- "modified_by": "Administrator",
- "name": "Add A Few Customers",
- "owner": "Administrator",
- "ref_doctype": "Customer",
- "slide_desc": "",
- "slide_fields": [
-  {
-   "align": "",
-   "fieldname": "customer_name",
-   "fieldtype": "Data",
-   "label": "Customer Name",
-   "placeholder": "",
-   "reqd": 1
-  },
-  {
-   "align": "",
-   "fieldtype": "Column Break",
-   "reqd": 0
-  },
-  {
-   "align": "",
-   "fieldname": "customer_email",
-   "fieldtype": "Data",
-   "label": "Email ID",
-   "reqd": 1
-  }
- ],
- "slide_order": 40,
- "slide_title": "Add A Few Customers",
- "slide_type": "Create"
-}
\ No newline at end of file
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index db5b20e..993c61d 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -24,7 +24,7 @@
 			["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"],
 			as_dict=1)
 
-		item_stock_qty = get_stock_availability(item_code, warehouse)
+		item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
 		price_list_rate, currency = frappe.db.get_value('Item Price', {
 			'price_list': price_list,
 			'item_code': item_code
@@ -99,7 +99,6 @@
 		), {'warehouse': warehouse}, as_dict=1)
 
 	if items_data:
-		items_data = filter_service_items(items_data)
 		items = [d.item_code for d in items_data]
 		item_prices_data = frappe.get_all("Item Price",
 			fields = ["item_code", "price_list_rate", "currency"],
@@ -112,7 +111,7 @@
 		for item in items_data:
 			item_code = item.item_code
 			item_price = item_prices.get(item_code) or {}
-			item_stock_qty = get_stock_availability(item_code, warehouse)
+			item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
 
 			row = {}
 			row.update(item)
@@ -144,14 +143,6 @@
 
 	return {}
 
-def filter_service_items(items):
-	for item in items:
-		if not item['is_stock_item']:
-			if not frappe.db.exists('Product Bundle', item['item_code']):
-				items.remove(item)
-
-	return items
-
 def get_conditions(search_term):
 	condition = "("
 	condition += """item.name like {search_term}
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index ce74f6d..ea8459f 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -248,7 +248,7 @@
 
 				numpad_event: (value, action) => this.update_item_field(value, action),
 
-				checkout: () => this.payment.checkout(),
+				checkout: () => this.save_and_checkout(),
 
 				edit_cart: () => this.payment.edit_cart(),
 
@@ -630,18 +630,24 @@
 	}
 
 	async check_stock_availability(item_row, qty_needed, warehouse) {
-		const available_qty = (await this.get_available_stock(item_row.item_code, warehouse)).message;
+		const resp = (await this.get_available_stock(item_row.item_code, warehouse)).message;
+		const available_qty = resp[0];
+		const is_stock_item = resp[1];
 
 		frappe.dom.unfreeze();
 		const bold_item_code = item_row.item_code.bold();
 		const bold_warehouse = warehouse.bold();
 		const bold_available_qty = available_qty.toString().bold()
 		if (!(available_qty > 0)) {
-			frappe.model.clear_doc(item_row.doctype, item_row.name);
-			frappe.throw({
-				title: __("Not Available"),
-				message: __('Item Code: {0} is not available under warehouse {1}.', [bold_item_code, bold_warehouse])
-			})
+			if (is_stock_item) {
+				frappe.model.clear_doc(item_row.doctype, item_row.name);
+				frappe.throw({
+					title: __("Not Available"),
+					message: __('Item Code: {0} is not available under warehouse {1}.', [bold_item_code, bold_warehouse])
+				});
+			} else {
+				return;
+			}
 		} else if (available_qty < qty_needed) {
 			frappe.throw({
 				message: __('Stock quantity not enough for Item Code: {0} under warehouse {1}. Available quantity {2}.', [bold_item_code, bold_warehouse, bold_available_qty]),
@@ -675,8 +681,8 @@
 			},
 			callback(res) {
 				if (!me.item_stock_map[item_code])
-					me.item_stock_map[item_code] = {}
-				me.item_stock_map[item_code][warehouse] = res.message;
+					me.item_stock_map[item_code] = {};
+				me.item_stock_map[item_code][warehouse] = res.message[0];
 			}
 		});
 	}
@@ -707,4 +713,9 @@
 			})
 			.catch(e => console.log(e));
 	}
+
+	async save_and_checkout() {
+		this.frm.is_dirty() && await this.frm.save();
+		this.payment.checkout();
+	}
 };
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index 4920584..4a99f06 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -191,10 +191,10 @@
 			this.numpad_value = '';
 		});
 
-		this.$component.on('click', '.checkout-btn', function() {
+		this.$component.on('click', '.checkout-btn', async function() {
 			if ($(this).attr('style').indexOf('--blue-500') == -1) return;
 
-			me.events.checkout();
+			await me.events.checkout();
 			me.toggle_checkout_btn(false);
 
 			me.allow_discount_change && me.$add_discount_elem.removeClass("d-none");
@@ -985,6 +985,7 @@
 		$(frm.wrapper).off('refresh-fields');
 		$(frm.wrapper).on('refresh-fields', () => {
 			if (frm.doc.items.length) {
+				this.$cart_items_wrapper.html('');
 				frm.doc.items.forEach(item => {
 					this.update_item_html(item);
 				});
diff --git a/erpnext/selling/page/point_of_sale/pos_item_selector.js b/erpnext/selling/page/point_of_sale/pos_item_selector.js
index a30bcd7..1177615 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_selector.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_selector.js
@@ -79,14 +79,20 @@
 		const me = this;
 		// eslint-disable-next-line no-unused-vars
 		const { item_image, serial_no, batch_no, barcode, actual_qty, stock_uom, price_list_rate } = item;
-		const indicator_color = actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange";
 		const precision = flt(price_list_rate, 2) % 1 != 0 ? 2 : 0;
-
+		let indicator_color;
 		let qty_to_display = actual_qty;
 
-		if (Math.round(qty_to_display) > 999) {
-			qty_to_display = Math.round(qty_to_display)/1000;
-			qty_to_display = qty_to_display.toFixed(1) + 'K';
+		if (item.is_stock_item) {
+			indicator_color = (actual_qty > 10 ? "green" : actual_qty <= 0 ? "red" : "orange");
+
+			if (Math.round(qty_to_display) > 999) {
+				qty_to_display = Math.round(qty_to_display)/1000;
+				qty_to_display = qty_to_display.toFixed(1) + 'K';
+			}
+		} else {
+			indicator_color = '';
+			qty_to_display = '';
 		}
 
 		function get_item_image_html() {
diff --git a/erpnext/selling/page/point_of_sale/pos_payment.js b/erpnext/selling/page/point_of_sale/pos_payment.js
index b9b6559..1e9f6d7 100644
--- a/erpnext/selling/page/point_of_sale/pos_payment.js
+++ b/erpnext/selling/page/point_of_sale/pos_payment.js
@@ -169,6 +169,24 @@
       }
     });
 
+		frappe.ui.form.on('POS Invoice', 'coupon_code', (frm) => {
+			if (!frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
+				frappe.run_serially([
+					() => frm.doc.ignore_pricing_rule=1,
+					() => frm.trigger('ignore_pricing_rule'),
+					() => frm.doc.ignore_pricing_rule=0,
+					() => frm.trigger('apply_pricing_rule'),
+					() => frm.save(),
+					() => this.update_totals_section(frm.doc)
+				]);
+			} else if (frm.doc.ignore_pricing_rule && frm.doc.coupon_code) {
+				frappe.show_alert({
+					message: __("Ignore Pricing Rule is enabled. Cannot apply coupon code."),
+					indicator: "orange"
+				});
+			}
+		});
+
 		this.setup_listener_for_payments();
 
 		this.$payment_modes.on('click', '.shortcut', function() {
diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
index 777b02c..dd49f13 100644
--- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
+++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
@@ -23,19 +23,24 @@
 		row = []
 
 		outstanding_amt = get_customer_outstanding(d.name, filters.get("company"),
-			ignore_outstanding_sales_order=d.bypass_credit_limit_check_at_sales_order)
+			ignore_outstanding_sales_order=d.bypass_credit_limit_check)
 
 		credit_limit = get_credit_limit(d.name, filters.get("company"))
 
 		bal = flt(credit_limit) - flt(outstanding_amt)
 
 		if customer_naming_type == "Naming Series":
-			row = [d.name, d.customer_name, credit_limit, outstanding_amt, bal,
-				d.bypass_credit_limit_check, d.is_frozen,
-          d.disabled]
+			row = [
+				d.name, d.customer_name, credit_limit,
+				outstanding_amt, bal, d.bypass_credit_limit_check,
+				d.is_frozen, d.disabled
+			]
 		else:
-			row = [d.name, credit_limit, outstanding_amt, bal,
-          d.bypass_credit_limit_check_at_sales_order, d.is_frozen, d.disabled]
+			row = [
+				d.name, credit_limit, outstanding_amt, bal,
+				d.bypass_credit_limit_check, d.is_frozen,
+				d.disabled
+			]
 
 		if credit_limit:
 			data.append(row)
diff --git a/erpnext/shopping_cart/web_template/hero_slider/__init__.py b/erpnext/selling/report/payment_terms_status_for_sales_order/__init__.py
similarity index 100%
copy from erpnext/shopping_cart/web_template/hero_slider/__init__.py
copy to erpnext/selling/report/payment_terms_status_for_sales_order/__init__.py
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
new file mode 100644
index 0000000..0e36b3f
--- /dev/null
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.js
@@ -0,0 +1,84 @@
+// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+function get_filters() {
+	let filters = [
+		{
+			"fieldname":"company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"default": frappe.defaults.get_user_default("Company"),
+			"reqd": 1
+		},
+		{
+			"fieldname":"period_start_date",
+			"label": __("Start Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1)
+		},
+		{
+			"fieldname":"period_end_date",
+			"label": __("End Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.get_today()
+		},
+		{
+			"fieldname":"sales_order",
+			"label": __("Sales Order"),
+			"fieldtype": "MultiSelectList",
+			"width": 100,
+			"options": "Sales Order",
+			"get_data": function(txt) {
+				return frappe.db.get_link_options("Sales Order", txt, this.filters());
+			},
+			"filters": () => {
+				return {
+					docstatus: 1,
+					payment_terms_template: ['not in', ['']],
+					company: frappe.query_report.get_filter_value("company"),
+					transaction_date: ['between', [frappe.query_report.get_filter_value("period_start_date"), frappe.query_report.get_filter_value("period_end_date")]]
+				}
+			},
+			on_change: function(){
+				frappe.query_report.refresh();
+			}
+		}
+	]
+
+	return filters;
+}
+
+frappe.query_reports["Payment Terms Status for Sales Order"] = {
+	"filters": get_filters(),
+	"formatter": function(value, row, column, data, default_formatter){
+		if(column.fieldname == 'invoices' && value) {
+			invoices = value.split(',');
+			const invoice_formatter = (prev_value, curr_value) => {
+				if(prev_value != "") {
+					return prev_value + ", " + default_formatter(curr_value, row, column, data);
+				}
+				else {
+					return default_formatter(curr_value, row, column, data);
+				}
+			}
+			return invoices.reduce(invoice_formatter, "")
+		}
+		else if (column.fieldname == 'paid_amount' && value){
+			formatted_value = default_formatter(value, row, column, data);
+			if(value > 0) {
+				formatted_value = "<span style='color:green;'>" + formatted_value + "</span>"
+			}
+			return formatted_value;
+		}
+		else if (column.fieldname == 'status' && value == 'Completed'){
+			return "<span style='color:green;'>" + default_formatter(value, row, column, data) + "</span>";
+		}
+
+		return default_formatter(value, row, column, data);
+	},
+
+};
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.json b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.json
new file mode 100644
index 0000000..850fa4d
--- /dev/null
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.json
@@ -0,0 +1,38 @@
+{
+ "add_total_row": 1,
+ "columns": [],
+ "creation": "2021-12-28 10:39:34.533964",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-12-30 10:42:06.058457",
+ "modified_by": "Administrator",
+ "module": "Selling",
+ "name": "Payment Terms Status for Sales Order",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Order",
+ "report_name": "Payment Terms Status for Sales Order",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Sales User"
+  },
+  {
+   "role": "Sales Manager"
+  },
+  {
+   "role": "Maintenance User"
+  },
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Stock User"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
new file mode 100644
index 0000000..e6a56ee
--- /dev/null
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/payment_terms_status_for_sales_order.py
@@ -0,0 +1,205 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# License: MIT. See LICENSE
+
+import frappe
+from frappe import _, qb, query_builder
+from frappe.query_builder import functions
+
+
+def get_columns():
+	columns = [
+		{
+			"label": _("Sales Order"),
+			"fieldname": "name",
+			"fieldtype": "Link",
+			"options": "Sales Order",
+		},
+		{
+			"label": _("Posting Date"),
+			"fieldname": "submitted",
+			"fieldtype": "Date",
+		},
+		{
+			"label": _("Payment Term"),
+			"fieldname": "payment_term",
+			"fieldtype": "Data",
+		},
+		{
+			"label": _("Description"),
+			"fieldname": "description",
+			"fieldtype": "Data",
+		},
+		{
+			"label": _("Due Date"),
+			"fieldname": "due_date",
+			"fieldtype": "Date",
+		},
+		{
+			"label": _("Invoice Portion"),
+			"fieldname": "invoice_portion",
+			"fieldtype": "Percent",
+		},
+		{
+			"label": _("Payment Amount"),
+			"fieldname": "base_payment_amount",
+			"fieldtype": "Currency",
+			"options": "currency",
+		},
+		{
+			"label": _("Paid Amount"),
+			"fieldname": "paid_amount",
+			"fieldtype": "Currency",
+			"options": "currency",
+		},
+		{
+			"label": _("Invoices"),
+			"fieldname": "invoices",
+			"fieldtype": "Link",
+			"options": "Sales Invoice",
+		},
+		{
+			"label": _("Status"),
+			"fieldname": "status",
+			"fieldtype": "Data",
+		},
+		{
+			"label": _("Currency"),
+			"fieldname": "currency",
+			"fieldtype": "Currency",
+			"hidden": 1
+		}
+	]
+	return columns
+
+
+def get_conditions(filters):
+	"""
+	Convert filter options to conditions used in query
+	"""
+	filters = frappe._dict(filters) if filters else frappe._dict({})
+	conditions = frappe._dict({})
+
+	conditions.company = filters.company or frappe.defaults.get_user_default("company")
+	conditions.end_date = filters.period_end_date or frappe.utils.today()
+	conditions.start_date = filters.period_start_date or frappe.utils.add_months(
+		conditions.end_date, -1
+	)
+	conditions.sales_order = filters.sales_order or []
+
+	return conditions
+
+
+def get_so_with_invoices(filters):
+	"""
+	Get Sales Order with payment terms template with their associated Invoices
+	"""
+	sorders = []
+
+	so = qb.DocType("Sales Order")
+	ps = qb.DocType("Payment Schedule")
+	datediff = query_builder.CustomFunction("DATEDIFF", ["cur_date", "due_date"])
+	ifelse = query_builder.CustomFunction("IF", ["condition", "then", "else"])
+
+	conditions = get_conditions(filters)
+	query_so = (
+		qb.from_(so)
+		.join(ps)
+		.on(ps.parent == so.name)
+		.select(
+			so.name,
+			so.transaction_date.as_("submitted"),
+			ifelse(datediff(ps.due_date, functions.CurDate()) < 0, "Overdue", "Unpaid").as_("status"),
+			ps.payment_term,
+			ps.description,
+			ps.due_date,
+			ps.invoice_portion,
+			ps.base_payment_amount,
+			ps.paid_amount,
+		)
+		.where(
+			(so.docstatus == 1)
+			& (so.payment_terms_template != "NULL")
+			& (so.company == conditions.company)
+			& (so.transaction_date[conditions.start_date : conditions.end_date])
+		)
+		.orderby(so.name, so.transaction_date, ps.due_date)
+	)
+
+	if conditions.sales_order != []:
+		query_so = query_so.where(so.name.isin(conditions.sales_order))
+
+	sorders = query_so.run(as_dict=True)
+
+	invoices = []
+	if sorders != []:
+		soi = qb.DocType("Sales Order Item")
+		si = qb.DocType("Sales Invoice")
+		sii = qb.DocType("Sales Invoice Item")
+		query_inv = (
+			qb.from_(sii)
+			.right_join(si)
+			.on(si.name == sii.parent)
+			.inner_join(soi)
+			.on(soi.name == sii.so_detail)
+			.select(sii.sales_order, sii.parent.as_("invoice"), si.base_grand_total.as_("invoice_amount"))
+			.where((sii.sales_order.isin([x.name for x in sorders])) & (si.docstatus == 1))
+			.groupby(sii.parent)
+		)
+		invoices = query_inv.run(as_dict=True)
+
+	return sorders, invoices
+
+
+def set_payment_terms_statuses(sales_orders, invoices, filters):
+	"""
+	compute status for payment terms with associated sales invoice using FIFO
+	"""
+
+	for so in sales_orders:
+		so.currency = frappe.get_cached_value('Company', filters.get('company'), 'default_currency')
+		so.invoices = ""
+		for inv in [x for x in invoices if x.sales_order == so.name and x.invoice_amount > 0]:
+			if so.base_payment_amount - so.paid_amount > 0:
+				amount = so.base_payment_amount - so.paid_amount
+				if inv.invoice_amount >= amount:
+					inv.invoice_amount -= amount
+					so.paid_amount += amount
+					so.invoices += "," + inv.invoice
+					so.status = "Completed"
+					break
+				else:
+					so.paid_amount += inv.invoice_amount
+					inv.invoice_amount = 0
+					so.invoices += "," + inv.invoice
+					so.status = "Partly Paid"
+
+	return sales_orders, invoices
+
+
+def prepare_chart(s_orders):
+	if len(set([x.name for x in s_orders])) == 1:
+		chart = {
+			"data": {
+				"labels": [term.payment_term for term in s_orders],
+				"datasets": [
+					{"name": "Payment Amount", "values": [x.base_payment_amount for x in s_orders],},
+					{"name": "Paid Amount", "values": [x.paid_amount for x in s_orders],},
+				],
+			},
+			"type": "bar",
+		}
+		return chart
+
+
+def execute(filters=None):
+	columns = get_columns()
+	sales_orders, so_invoices = get_so_with_invoices(filters)
+	sales_orders, so_invoices = set_payment_terms_statuses(sales_orders, so_invoices, filters)
+
+	prepare_chart(sales_orders)
+
+	data = sales_orders
+	message = []
+	chart = prepare_chart(sales_orders)
+
+	return columns, data, message, chart
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
new file mode 100644
index 0000000..f7f8a5d
--- /dev/null
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
@@ -0,0 +1,198 @@
+import datetime
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_days
+
+from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.selling.report.payment_terms_status_for_sales_order.payment_terms_status_for_sales_order import (
+	execute,
+)
+from erpnext.stock.doctype.item.test_item import create_item
+
+test_dependencies = ["Sales Order", "Item", "Sales Invoice", "Payment Terms Template"]
+
+
+class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
+	def create_payment_terms_template(self):
+		# create template for 50-50 payments
+		template = None
+		if frappe.db.exists("Payment Terms Template", "_Test 50-50"):
+			template = frappe.get_doc("Payment Terms Template", "_Test 50-50")
+		else:
+			template = frappe.get_doc(
+				{
+					"doctype": "Payment Terms Template",
+					"template_name": "_Test 50-50",
+					"terms": [
+						{
+							"doctype": "Payment Terms Template Detail",
+							"due_date_based_on": "Day(s) after invoice date",
+							"payment_term_name": "_Test 50% on 15 Days",
+							"description": "_Test 50-50",
+							"invoice_portion": 50,
+							"credit_days": 15,
+						},
+						{
+							"doctype": "Payment Terms Template Detail",
+							"due_date_based_on": "Day(s) after invoice date",
+							"payment_term_name": "_Test 50% on 30 Days",
+							"description": "_Test 50-50",
+							"invoice_portion": 50,
+							"credit_days": 30,
+						},
+					],
+				}
+			)
+			template.insert()
+		self.template = template
+
+	def test_payment_terms_status(self):
+		self.create_payment_terms_template()
+		item = create_item(item_code="_Test Excavator", is_stock_item=0)
+		so = make_sales_order(
+			transaction_date="2021-06-15",
+			delivery_date=add_days("2021-06-15", -30),
+			item=item.item_code,
+			qty=10,
+			rate=100000,
+			do_not_save=True,
+		)
+		so.po_no = ""
+		so.taxes_and_charges = ""
+		so.taxes = ""
+		so.payment_terms_template = self.template.name
+		so.save()
+		so.submit()
+
+		# make invoice with 60% of the total sales order value
+		sinv = make_sales_invoice(so.name)
+		sinv.taxes_and_charges = ""
+		sinv.taxes = ""
+		sinv.items[0].qty = 6
+		sinv.insert()
+		sinv.submit()
+		columns, data, message, chart = execute(
+			{
+				"company": "_Test Company",
+				"period_start_date": "2021-06-01",
+				"period_end_date": "2021-06-30",
+				"sales_order": [so.name],
+			}
+		)
+
+		expected_value = [
+			{
+				"name": so.name,
+				"submitted": datetime.date(2021, 6, 15),
+				"status": "Completed",
+				"payment_term": None,
+				"description": "_Test 50-50",
+				"due_date": datetime.date(2021, 6, 30),
+				"invoice_portion": 50.0,
+				"currency": "INR",
+				"base_payment_amount": 500000.0,
+				"paid_amount": 500000.0,
+				"invoices": ","+sinv.name,
+			},
+			{
+				"name": so.name,
+				"submitted": datetime.date(2021, 6, 15),
+				"status": "Partly Paid",
+				"payment_term": None,
+				"description": "_Test 50-50",
+				"due_date": datetime.date(2021, 7, 15),
+				"invoice_portion": 50.0,
+				"currency": "INR",
+				"base_payment_amount": 500000.0,
+				"paid_amount": 100000.0,
+				"invoices": ","+sinv.name,
+			},
+		]
+		self.assertEqual(data, expected_value)
+
+	def create_exchange_rate(self, date):
+		# make an entry in Currency Exchange list. serves as a static exchange rate
+		if frappe.db.exists({'doctype': "Currency Exchange",'date': date,'from_currency': 'USD', 'to_currency':'INR'}):
+			return
+		else:
+			doc = frappe.get_doc({
+				'doctype': "Currency Exchange",
+				'date': date,
+				'from_currency': 'USD',
+				'to_currency': frappe.get_cached_value("Company", '_Test Company','default_currency'),
+				'exchange_rate': 70,
+				'for_buying': True,
+				'for_selling': True
+			})
+			doc.insert()
+
+	def test_alternate_currency(self):
+		transaction_date = "2021-06-15"
+		self.create_payment_terms_template()
+		self.create_exchange_rate(transaction_date)
+		item = create_item(item_code="_Test Excavator", is_stock_item=0)
+		so = make_sales_order(
+			transaction_date=transaction_date,
+			currency="USD",
+			delivery_date=add_days(transaction_date, -30),
+			item=item.item_code,
+			qty=10,
+			rate=10000,
+			do_not_save=True,
+		)
+		so.po_no = ""
+		so.taxes_and_charges = ""
+		so.taxes = ""
+		so.payment_terms_template = self.template.name
+		so.save()
+		so.submit()
+
+		# make invoice with 60% of the total sales order value
+		sinv = make_sales_invoice(so.name)
+		sinv.currency = "USD"
+		sinv.taxes_and_charges = ""
+		sinv.taxes = ""
+		sinv.items[0].qty = 6
+		sinv.insert()
+		sinv.submit()
+		columns, data, message, chart = execute(
+			{
+				"company": "_Test Company",
+				"period_start_date": "2021-06-01",
+				"period_end_date": "2021-06-30",
+				"sales_order": [so.name],
+			}
+		)
+
+		# report defaults to company currency.
+		expected_value = [
+			{
+				"name": so.name,
+				"submitted": datetime.date(2021, 6, 15),
+				"status": "Completed",
+				"payment_term": None,
+				"description": "_Test 50-50",
+				"due_date": datetime.date(2021, 6, 30),
+				"invoice_portion": 50.0,
+				"currency": frappe.get_cached_value("Company", '_Test Company','default_currency'),
+				"base_payment_amount": 3500000.0,
+				"paid_amount": 3500000.0,
+				"invoices": ","+sinv.name,
+			},
+			{
+				"name": so.name,
+				"submitted": datetime.date(2021, 6, 15),
+				"status": "Partly Paid",
+				"payment_term": None,
+				"description": "_Test 50-50",
+				"due_date": datetime.date(2021, 7, 15),
+				"invoice_portion": 50.0,
+				"currency": frappe.get_cached_value("Company", '_Test Company','default_currency'),
+				"base_payment_amount": 3500000.0,
+				"paid_amount": 700000.0,
+				"invoices": ","+sinv.name,
+			},
+		]
+		self.assertEqual(data, expected_value)
diff --git a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
index d62915f..16162ac 100644
--- a/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
+++ b/erpnext/selling/report/pending_so_items_for_purchase_request/test_pending_so_items_for_purchase_request.py
@@ -2,6 +2,7 @@
 # For license information, please see license.txt
 
 
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_months, nowdate
 
 from erpnext.selling.doctype.sales_order.sales_order import make_material_request
@@ -9,10 +10,9 @@
 from erpnext.selling.report.pending_so_items_for_purchase_request.pending_so_items_for_purchase_request import (
 	execute,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestPendingSOItemsForPurchaseRequest(ERPNextTestCase):
+class TestPendingSOItemsForPurchaseRequest(FrappeTestCase):
     def test_result_for_partial_material_request(self):
         so = make_sales_order()
         mr=make_material_request(so.name)
diff --git a/erpnext/selling/report/sales_analytics/test_analytics.py b/erpnext/selling/report/sales_analytics/test_analytics.py
index f56cce2..564f48f 100644
--- a/erpnext/selling/report/sales_analytics/test_analytics.py
+++ b/erpnext/selling/report/sales_analytics/test_analytics.py
@@ -3,13 +3,13 @@
 
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.selling.report.sales_analytics.sales_analytics import execute
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestAnalytics(ERPNextTestCase):
+class TestAnalytics(FrappeTestCase):
 	def test_sales_analytics(self):
 		frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
 
diff --git a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
index b2bf546..3e22d0f 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.py
@@ -85,7 +85,7 @@
 			and so.docstatus = 1
 			{conditions}
 		GROUP BY soi.name
-		ORDER BY so.transaction_date ASC
+		ORDER BY so.transaction_date ASC, soi.item_code ASC
 	""".format(conditions=conditions), filters, as_dict=1)
 
 	return data
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 540aca2..98131f9 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -227,11 +227,11 @@
 					},
 					callback:function(r){
 						if (in_list(['Delivery Note', 'Sales Invoice'], doc.doctype)) {
-
 							if (doc.doctype === 'Sales Invoice' && (!doc.update_stock)) return;
-
-							me.set_batch_number(cdt, cdn);
-							me.batch_no(doc, cdt, cdn);
+							if (has_batch_no) {
+								me.set_batch_number(cdt, cdn);
+								me.batch_no(doc, cdt, cdn);
+							}
 						}
 					}
 				});
@@ -486,7 +486,7 @@
 					"options": "Competitor Detail"
 				},
 				{
-					"fieldtype": "Text",
+					"fieldtype": "Small Text",
 					"label": __("Detailed Reason"),
 					"fieldname": "detailed_reason"
 				},
@@ -499,7 +499,7 @@
 					method: 'declare_enquiry_lost',
 					args: {
 						'lost_reasons_list': values.lost_reason,
-						'competitors': values.competitors,
+						'competitors': values.competitors ? values.competitors : [],
 						'detailed_reason': values.detailed_reason
 					},
 					callback: function(r) {
diff --git a/erpnext/setup/doctype/brand/brand.json b/erpnext/setup/doctype/brand/brand.json
index a8f0674..45b4db8 100644
--- a/erpnext/setup/doctype/brand/brand.json
+++ b/erpnext/setup/doctype/brand/brand.json
@@ -1,270 +1,111 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "field:brand", 
- "beta": 0, 
- "creation": "2013-02-22 01:27:54", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Setup", 
- "editable_grid": 0, 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:brand",
+ "creation": "2013-02-22 01:27:54",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+  "brand",
+  "image",
+  "description",
+  "defaults",
+  "brand_defaults"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 1, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "brand", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Brand Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "brand", 
-   "oldfieldtype": "Data", 
-   "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, 
+   "allow_in_quick_entry": 1,
+   "fieldname": "brand",
+   "fieldtype": "Data",
+   "label": "Brand Name",
+   "oldfieldname": "brand",
+   "oldfieldtype": "Data",
+   "reqd": 1,
    "unique": 1
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "description", 
-   "oldfieldtype": "Text", 
-   "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": "description",
+   "fieldtype": "Text",
+   "in_list_view": 1,
+   "label": "Description",
+   "oldfieldname": "description",
+   "oldfieldtype": "Text",
    "width": "300px"
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "defaults", 
-   "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": "Defaults", 
-   "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": "defaults",
+   "fieldtype": "Section Break",
+   "label": "Defaults"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "brand_defaults", 
-   "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": "Brand Defaults", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Item Default", 
-   "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": "brand_defaults",
+   "fieldtype": "Table",
+   "label": "Brand Defaults",
+   "options": "Item Default"
+  },
+  {
+   "fieldname": "image",
+   "fieldtype": "Attach Image",
+   "hidden": 1,
+   "label": "Image"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "icon": "fa fa-certificate", 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-10-23 23:18:06.067612", 
- "modified_by": "Administrator", 
- "module": "Setup", 
- "name": "Brand", 
- "owner": "Administrator", 
+ ],
+ "icon": "fa fa-certificate",
+ "idx": 1,
+ "image_field": "image",
+ "links": [],
+ "modified": "2021-03-01 15:57:30.005783",
+ "modified_by": "Administrator",
+ "module": "Setup",
+ "name": "Brand",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 1, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Item Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "import": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Item Manager",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Stock User", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
-  }, 
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Stock User"
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Sales User", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
-  }, 
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales User"
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Purchase User", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
-  }, 
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Purchase User"
+  },
   {
-   "amend": 0, 
-   "cancel": 0, 
-   "create": 0, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 0, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Accounts User", 
-   "set_user_permissions": 0, 
-   "share": 0, 
-   "submit": 0, 
-   "write": 0
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User"
   }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 1, 
- "sort_order": "ASC", 
- "track_changes": 0, 
- "track_seen": 0, 
- "track_views": 0
-}
+ ],
+ "quick_entry": 1,
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "ASC"
+}
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 63d96bf..370a327 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -3,7 +3,7 @@
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "field:company_name",
- "creation": "2013-04-10 08:35:39",
+ "creation": "2022-01-25 10:29:55.938239",
  "description": "Legal Entity / Subsidiary with a separate Chart of Accounts belonging to the Organization.",
  "doctype": "DocType",
  "document_type": "Setup",
@@ -77,13 +77,13 @@
   "default_finance_book",
   "auto_accounting_for_stock_settings",
   "enable_perpetual_inventory",
-  "enable_perpetual_inventory_for_non_stock_items",
+  "enable_provisional_accounting_for_non_stock_items",
   "default_inventory_account",
   "stock_adjustment_account",
   "default_in_transit_warehouse",
   "column_break_32",
   "stock_received_but_not_billed",
-  "service_received_but_not_billed",
+  "default_provisional_account",
   "expenses_included_in_valuation",
   "fixed_asset_defaults",
   "accumulated_depreciation_account",
@@ -685,20 +685,6 @@
    "options": "Terms and Conditions"
   },
   {
-   "fieldname": "service_received_but_not_billed",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Service Received But Not Billed",
-   "no_copy": 1,
-   "options": "Account"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_perpetual_inventory_for_non_stock_items",
-   "fieldtype": "Check",
-   "label": "Enable Perpetual Inventory For Non Stock Items"
-  },
-  {
    "fieldname": "default_in_transit_warehouse",
    "fieldtype": "Link",
    "label": "Default In-Transit Warehouse",
@@ -741,6 +727,20 @@
    "fieldname": "section_break_28",
    "fieldtype": "Section Break",
    "label": "Chart of Accounts"
+  },
+  {
+   "default": "0",
+   "fieldname": "enable_provisional_accounting_for_non_stock_items",
+   "fieldtype": "Check",
+   "label": "Enable Provisional Accounting For Non Stock Items"
+  },
+  {
+   "fieldname": "default_provisional_account",
+   "fieldtype": "Link",
+   "ignore_user_permissions": 1,
+   "label": "Default Provisional Account",
+   "no_copy": 1,
+   "options": "Account"
   }
  ],
  "icon": "fa fa-building",
@@ -748,7 +748,7 @@
  "image_field": "company_logo",
  "is_tree": 1,
  "links": [],
- "modified": "2021-10-04 12:09:25.833133",
+ "modified": "2022-01-25 10:33:16.826067",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Company",
@@ -809,5 +809,6 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 0a02bcd..95b1e8b 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -10,6 +10,7 @@
 from frappe import _
 from frappe.cache_manager import clear_defaults_cache
 from frappe.contacts.address_and_contact import load_address_and_contact
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.utils import cint, formatdate, get_timestamp, today
 from frappe.utils.nestedset import NestedSet
 
@@ -45,7 +46,7 @@
 		self.validate_currency()
 		self.validate_coa_input()
 		self.validate_perpetual_inventory()
-		self.validate_perpetual_inventory_for_non_stock_items()
+		self.validate_provisional_account_for_non_stock_items()
 		self.check_country_change()
 		self.check_parent_changed()
 		self.set_chart_of_accounts()
@@ -187,11 +188,14 @@
 				frappe.msgprint(_("Set default inventory account for perpetual inventory"),
 					alert=True, indicator='orange')
 
-	def validate_perpetual_inventory_for_non_stock_items(self):
+	def validate_provisional_account_for_non_stock_items(self):
 		if not self.get("__islocal"):
-			if cint(self.enable_perpetual_inventory_for_non_stock_items) == 1 and not self.service_received_but_not_billed:
-				frappe.throw(_("Set default {0} account for perpetual inventory for non stock items").format(
-					frappe.bold('Service Received But Not Billed')))
+			if cint(self.enable_provisional_accounting_for_non_stock_items) == 1 and not self.default_provisional_account:
+				frappe.throw(_("Set default {0} account for non stock items").format(
+					frappe.bold('Provisional Account')))
+
+			make_property_setter("Purchase Receipt", "provisional_expense_account", "hidden",
+				not self.enable_provisional_accounting_for_non_stock_items, "Check", validate_fields_for_doctype=False)
 
 	def check_country_change(self):
 		frappe.flags.country_change = False
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index c94b346..4f92240 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -1,21 +1,17 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import copy
+from urllib.parse import quote
 
 import frappe
 from frappe import _
-from frappe.utils import cint, cstr, nowdate
+from frappe.utils import cint
 from frappe.utils.nestedset import NestedSet
 from frappe.website.utils import clear_cache
 from frappe.website.website_generator import WebsiteGenerator
-from six.moves.urllib.parse import quote
 
-from erpnext.shopping_cart.filters import ProductFiltersBuilder
-from erpnext.shopping_cart.product_info import set_product_info_for_website
-from erpnext.shopping_cart.product_query import ProductQuery
-from erpnext.utilities.product import get_qty_in_stock
+from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
 
 
 class ItemGroup(NestedSet, WebsiteGenerator):
@@ -67,30 +63,11 @@
 		self.delete_child_item_groups_key()
 
 	def get_context(self, context):
-		context.show_search=True
-		context.page_length = cint(frappe.db.get_single_value('Products Settings', 'products_per_page')) or 6
+		context.show_search = True
+		context.body_class = "product-page"
+		context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page')) or 6
 		context.search_link = '/product_search'
 
-		if frappe.form_dict:
-			search = frappe.form_dict.search
-			field_filters = frappe.parse_json(frappe.form_dict.field_filters)
-			attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters)
-			start = frappe.parse_json(frappe.form_dict.start)
-		else:
-			search = None
-			attribute_filters = None
-			field_filters = {}
-			start = 0
-
-		if not field_filters:
-			field_filters = {}
-
-		# Ensure the query remains within current item group & sub group
-		field_filters['item_group'] = [ig[0] for ig in get_child_groups(self.name)]
-
-		engine = ProductQuery()
-		context.items = engine.query(attribute_filters, field_filters, search, start, item_group=self.name)
-
 		filter_engine = ProductFiltersBuilder(self.name)
 
 		context.field_filters = filter_engine.get_field_filters()
@@ -114,15 +91,16 @@
 				values[f"slide_{index + 1}_image"] = slide.image
 				values[f"slide_{index + 1}_title"] = slide.heading
 				values[f"slide_{index + 1}_subtitle"] = slide.description
-				values[f"slide_{index + 1}_theme"] = slide.theme or "Light"
-				values[f"slide_{index + 1}_content_align"] = slide.content_align or "Centre"
-				values[f"slide_{index + 1}_primary_action_label"] = slide.label
+				values[f"slide_{index + 1}_theme"] = slide.get("theme") or "Light"
+				values[f"slide_{index + 1}_content_align"] = slide.get("content_align") or "Centre"
 				values[f"slide_{index + 1}_primary_action"] = slide.url
 
 			context.slideshow = values
 
-		context.breadcrumbs = 0
+		context.no_breadcrumbs = False
 		context.title = self.website_title or self.name
+		context.name = self.name
+		context.item_group_name = self.item_group_name
 
 		return context
 
@@ -133,91 +111,24 @@
 		from erpnext.stock.doctype.item.item import validate_item_default_company_links
 		validate_item_default_company_links(self.item_group_defaults)
 
-@frappe.whitelist(allow_guest=True)
-def get_product_list_for_group(product_group=None, start=0, limit=10, search=None):
-	if product_group:
-		item_group = frappe.get_cached_doc('Item Group', product_group)
-		if item_group.is_group:
-			# return child item groups if the type is of "Is Group"
-			return get_child_groups_for_list_in_html(item_group, start, limit, search)
+def get_child_groups_for_website(item_group_name, immediate=False):
+	"""Returns child item groups *excluding* passed group."""
+	item_group = frappe.get_cached_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
+	filters = {
+		"lft": [">", item_group.lft],
+		"rgt": ["<", item_group.rgt],
+		"show_in_website": 1
+	}
 
-	child_groups = ", ".join(frappe.db.escape(i[0]) for i in get_child_groups(product_group))
+	if immediate:
+		filters["parent_item_group"] = item_group_name
 
-	# base query
-	query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
-			I.description, I.web_long_description as website_description, I.is_stock_item,
-			case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock, I.website_warehouse,
-			I.has_batch_no
-		from `tabItem` I
-		left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse
-		where I.show_in_website = 1
-			and I.disabled = 0
-			and (I.end_of_life is null or I.end_of_life='0000-00-00' or I.end_of_life > %(today)s)
-			and (I.variant_of = '' or I.variant_of is null)
-			and (I.item_group in ({child_groups})
-			or I.name in (select parent from `tabWebsite Item Group` where item_group in ({child_groups})))
-			""".format(child_groups=child_groups)
-	# search term condition
-	if search:
-		query += """ and (I.web_long_description like %(search)s
-				or I.item_name like %(search)s
-				or I.name like %(search)s)"""
-		search = "%" + cstr(search) + "%"
-
-	query += """order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (cint(start), cint(limit))
-
-	data = frappe.db.sql(query, {"product_group": product_group,"search": search, "today": nowdate()}, as_dict=1)
-	data = adjust_qty_for_expired_items(data)
-
-	if cint(frappe.db.get_single_value("Shopping Cart Settings", "enabled")):
-		for item in data:
-			set_product_info_for_website(item)
-
-	return data
-
-def get_child_groups_for_list_in_html(item_group, start, limit, search):
-	search_filters = None
-	if search_filters:
-		search_filters = [
-			dict(name = ('like', '%{}%'.format(search))),
-			dict(description = ('like', '%{}%'.format(search)))
-		]
-	data = frappe.db.get_all('Item Group',
-		fields = ['name', 'route', 'description', 'image'],
-		filters = dict(
-			show_in_website = 1,
-			parent_item_group = item_group.name,
-			lft = ('>', item_group.lft),
-			rgt = ('<', item_group.rgt),
-		),
-		or_filters = search_filters,
-		order_by = 'weightage desc, name asc',
-		start = start,
-		limit = limit
+	return frappe.get_all(
+		"Item Group",
+		filters=filters,
+		fields=["name", "route"]
 	)
 
-	return data
-
-def adjust_qty_for_expired_items(data):
-	adjusted_data = []
-
-	for item in data:
-		if item.get('has_batch_no') and item.get('website_warehouse'):
-			stock_qty_dict = get_qty_in_stock(
-				item.get('name'), 'website_warehouse', item.get('website_warehouse'))
-			qty = stock_qty_dict.stock_qty[0][0] if stock_qty_dict.stock_qty else 0
-			item['in_stock'] = 1 if qty else 0
-		adjusted_data.append(item)
-
-	return adjusted_data
-
-
-def get_child_groups(item_group_name):
-	item_group = frappe.get_doc("Item Group", item_group_name)
-	return frappe.db.sql("""select name
-		from `tabItem Group` where lft>=%(lft)s and rgt<=%(rgt)s
-			and show_in_website = 1""", {"lft": item_group.lft, "rgt": item_group.rgt})
-
 def get_child_item_groups(item_group_name):
 	item_group = frappe.get_cached_value("Item Group",
 		item_group_name, ["lft", "rgt"], as_dict=1)
@@ -233,31 +144,33 @@
 	if (context.get("website_image") or "").startswith("files/"):
 		context["website_image"] = "/" + quote(context["website_image"])
 
-	context["show_availability_status"] = cint(frappe.db.get_single_value('Products Settings',
+	context["show_availability_status"] = cint(frappe.db.get_single_value('E Commerce Settings',
 		'show_availability_status'))
 
 	products_template = 'templates/includes/products_as_list.html'
 
 	return frappe.get_template(products_template).render(context)
 
-def get_group_item_count(item_group):
-	child_groups = ", ".join('"' + i[0] + '"' for i in get_child_groups(item_group))
-	return frappe.db.sql("""select count(*) from `tabItem`
-		where docstatus = 0 and show_in_website = 1
-		and (item_group in (%s)
-			or name in (select parent from `tabWebsite Item Group`
-				where item_group in (%s))) """ % (child_groups, child_groups))[0][0]
 
+def get_parent_item_groups(item_group_name, from_item=False):
+	base_nav_page = {"name": _("Shop by Category"), "route":"/shop-by-category"}
 
-def get_parent_item_groups(item_group_name):
+	if from_item and frappe.request.environ.get("HTTP_REFERER"):
+		# base page after 'Home' will vary on Item page
+		last_page = frappe.request.environ["HTTP_REFERER"].split('/')[-1]
+		if last_page and last_page in ("shop-by-category", "all-products"):
+			base_nav_page_title = " ".join(last_page.split("-")).title()
+			base_nav_page = {"name": _(base_nav_page_title), "route":"/"+last_page}
+
 	base_parents = [
-		{"name": frappe._("Home"), "route":"/"},
-		{"name": frappe._("All Products"), "route":"/all-products"},
+		{"name": _("Home"), "route":"/"},
+		base_nav_page,
 	]
+
 	if not item_group_name:
 		return base_parents
 
-	item_group = frappe.get_doc("Item Group", item_group_name)
+	item_group = frappe.db.get_value("Item Group", item_group_name, ["lft", "rgt"], as_dict=1)
 	parent_groups = frappe.db.sql("""select name, route from `tabItem Group`
 		where lft <= %s and rgt >= %s
 		and show_in_website=1
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index bafaab8..1d7bad2 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -189,7 +189,7 @@
 
 	user_type_limit = {}
 	for user_type, data in user_types.items():
-		user_type_limit.setdefault(frappe.scrub(user_type), 10)
+		user_type_limit.setdefault(frappe.scrub(user_type), 20)
 
 	update_site_config('user_type_doctype_limit', user_type_limit)
 
@@ -204,15 +204,33 @@
 			'apply_user_permission_on': 'Employee',
 			'user_id_field': 'user_id',
 			'doctypes': {
-				'Salary Slip': ['read'],
+				# masters
+				'Holiday List': ['read'],
 				'Employee': ['read', 'write'],
+				# payroll
+				'Salary Slip': ['read'],
+				'Employee Benefit Application': ['read', 'write', 'create', 'delete'],
+				# expenses
 				'Expense Claim': ['read', 'write', 'create', 'delete'],
+				'Employee Advance': ['read', 'write', 'create', 'delete'],
+				# leave and attendance
 				'Leave Application': ['read', 'write', 'create', 'delete'],
 				'Attendance Request': ['read', 'write', 'create', 'delete'],
 				'Compensatory Leave Request': ['read', 'write', 'create', 'delete'],
+				# tax
 				'Employee Tax Exemption Declaration': ['read', 'write', 'create', 'delete'],
 				'Employee Tax Exemption Proof Submission': ['read', 'write', 'create', 'delete'],
-				'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend']
+				# projects
+				'Timesheet': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
+				# trainings
+				'Training Program': ['read'],
+				'Training Feedback': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
+				# shifts
+				'Shift Request': ['read', 'write', 'create', 'delete', 'submit', 'cancel', 'amend'],
+				# misc
+				'Employee Grievance': ['read', 'write', 'create', 'delete'],
+				'Employee Referral': ['read', 'write', 'create', 'delete'],
+				'Travel Request': ['read', 'write', 'create', 'delete']
 			}
 		}
 	}
diff --git "a/erpnext/setup/onboarding_slide/welcome_back_to_erpnext\041/welcome_back_to_erpnext\041.json" "b/erpnext/setup/onboarding_slide/welcome_back_to_erpnext\041/welcome_back_to_erpnext\041.json"
deleted file mode 100644
index f00dc94..0000000
--- "a/erpnext/setup/onboarding_slide/welcome_back_to_erpnext\041/welcome_back_to_erpnext\041.json"
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "add_more_button": 0,
- "app": "ERPNext",
- "creation": "2019-12-04 19:21:39.995776",
- "docstatus": 0,
- "doctype": "Onboarding Slide",
- "domains": [],
- "help_links": [],
- "idx": 0,
- "image_src": "",
- "is_completed": 0,
- "max_count": 3,
- "modified": "2019-12-09 17:53:53.849953",
- "modified_by": "Administrator",
- "name": "Welcome back to ERPNext!",
- "owner": "Administrator",
- "slide_desc": "<p>Let's continue where you left from!</p>",
- "slide_fields": [],
- "slide_module": "Setup",
- "slide_order": 0,
- "slide_title": "Welcome back to ERPNext!",
- "slide_type": "Continue"
-}
\ No newline at end of file
diff --git "a/erpnext/setup/onboarding_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json" "b/erpnext/setup/onboarding_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
deleted file mode 100644
index 37eb67b..0000000
--- "a/erpnext/setup/onboarding_slide/welcome_to_erpnext\041/welcome_to_erpnext\041.json"
+++ /dev/null
@@ -1,23 +0,0 @@
-{
- "add_more_button": 0,
- "app": "ERPNext",
- "creation": "2019-11-26 17:01:26.671859",
- "docstatus": 0,
- "doctype": "Onboarding Slide",
- "domains": [],
- "help_links": [],
- "idx": 0,
- "image_src": "",
- "is_completed": 0,
- "max_count": 0,
- "modified": "2019-12-22 21:26:28.414597",
- "modified_by": "Administrator",
- "name": "Welcome to ERPNext!",
- "owner": "Administrator",
- "slide_desc": "<div class=\"text center\">Setting up an ERP can be overwhelming. But don't worry, we have got your back! This wizard will help you onboard to ERPNext in a short time!</div>",
- "slide_fields": [],
- "slide_module": "Setup",
- "slide_order": 1,
- "slide_title": "Welcome to ERPNext!",
- "slide_type": "Information"
-}
\ No newline at end of file
diff --git a/erpnext/setup/setup_wizard/operations/company_setup.py b/erpnext/setup/setup_wizard/operations/company_setup.py
index 358b921..74c1bd8 100644
--- a/erpnext/setup/setup_wizard/operations/company_setup.py
+++ b/erpnext/setup/setup_wizard/operations/company_setup.py
@@ -29,10 +29,10 @@
 			'domain': args.get('domains')[0]
 		}).insert()
 
-def enable_shopping_cart(args):
+def enable_shopping_cart(args): # nosemgrep
 	# Needs price_lists
 	frappe.get_doc({
-		"doctype": "Shopping Cart Settings",
+		"doctype": "E Commerce Settings",
 		"enabled": 1,
 		'company': args.get('company_name')	,
 		'price_list': frappe.db.get_value("Price List", {"selling": 1}),
diff --git a/erpnext/setup/setup_wizard/operations/install_fixtures.py b/erpnext/setup/setup_wizard/operations/install_fixtures.py
index 9dbf49e..cefa0f3 100644
--- a/erpnext/setup/setup_wizard/operations/install_fixtures.py
+++ b/erpnext/setup/setup_wizard/operations/install_fixtures.py
@@ -195,10 +195,8 @@
 		{'doctype': "Party Type", "party_type": "Customer", "account_type": "Receivable"},
 		{'doctype': "Party Type", "party_type": "Supplier", "account_type": "Payable"},
 		{'doctype': "Party Type", "party_type": "Employee", "account_type": "Payable"},
-		{'doctype': "Party Type", "party_type": "Member", "account_type": "Receivable"},
 		{'doctype': "Party Type", "party_type": "Shareholder", "account_type": "Payable"},
 		{'doctype': "Party Type", "party_type": "Student", "account_type": "Receivable"},
-		{'doctype': "Party Type", "party_type": "Donor", "account_type": "Receivable"},
 
 		{'doctype': "Opportunity Type", "name": _("Sales")},
 		{'doctype': "Opportunity Type", "name": _("Support")},
@@ -535,8 +533,8 @@
 			# bank account same as a CoA entry
 			pass
 
-def update_shopping_cart_settings(args):
-	shopping_cart = frappe.get_doc("Shopping Cart Settings")
+def update_shopping_cart_settings(args): # nosemgrep
+	shopping_cart = frappe.get_doc("E Commerce Settings")
 	shopping_cart.update({
 		"enabled": 1,
 		'company': args.company_name,
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index 4441bb9..a4f2207 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -155,7 +155,7 @@
 		doc = frappe.new_doc(r.get("doctype"))
 		doc.update(r)
 		try:
-			doc.insert(ignore_permissions=True)
+			doc.insert(ignore_permissions=True, ignore_if_duplicate=True)
 		except frappe.DuplicateEntryError as e:
 			# pass DuplicateEntryError and continue
 			if e.args and e.args[0]==doc.doctype and e.args[1]==doc.name:
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/__init__.py
+++ /dev/null
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
deleted file mode 100644
index b38828e..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.js
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ui.form.on("Shopping Cart Settings", {
-	onload: function(frm) {
-		if(frm.doc.__onload && frm.doc.__onload.quotation_series) {
-			frm.fields_dict.quotation_series.df.options = frm.doc.__onload.quotation_series;
-			frm.refresh_field("quotation_series");
-		}
-
-		frm.set_query('payment_gateway_account', function() {
-			return { 'filters': { 'payment_channel': "Email" } };
-		});
-	},
-	refresh: function(frm) {
-		if (frm.doc.enabled) {
-			frm.get_field('store_page_docs').$wrapper.removeClass('hide-control').html(
-				`<div>${__("Follow these steps to create a landing page for your store")}:
-					<a href="https://docs.erpnext.com/docs/user/manual/en/website/store-landing-page"
-						style="color: var(--gray-600)">
-						docs/store-landing-page
-					</a>
-				</div>`
-			);
-		}
-	},
-	enabled: function(frm) {
-		if (frm.doc.enabled === 1) {
-			frm.set_value('enable_variants', 1);
-		}
-		else {
-			frm.set_value('company', '');
-			frm.set_value('price_list', '');
-			frm.set_value('default_customer_group', '');
-			frm.set_value('quotation_series', '');
-		}
-	}
-});
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
deleted file mode 100644
index 7a4bb20..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.json
+++ /dev/null
@@ -1,212 +0,0 @@
-{
- "actions": [],
- "creation": "2013-06-19 15:57:32",
- "description": "Default settings for Shopping Cart",
- "doctype": "DocType",
- "document_type": "System",
- "engine": "InnoDB",
- "field_order": [
-  "enabled",
-  "store_page_docs",
-  "display_settings",
-  "show_attachments",
-  "show_price",
-  "show_stock_availability",
-  "enable_variants",
-  "column_break_7",
-  "show_contact_us_button",
-  "show_quantity_in_website",
-  "show_apply_coupon_code_in_website",
-  "allow_items_not_in_stock",
-  "section_break_2",
-  "company",
-  "price_list",
-  "column_break_4",
-  "default_customer_group",
-  "quotation_series",
-  "section_break_8",
-  "enable_checkout",
-  "save_quotations_as_draft",
-  "column_break_11",
-  "payment_gateway_account",
-  "payment_success_url"
- ],
- "fields": [
-  {
-   "default": "0",
-   "fieldname": "enabled",
-   "fieldtype": "Check",
-   "in_list_view": 1,
-   "label": "Enable Shopping Cart"
-  },
-  {
-   "fieldname": "display_settings",
-   "fieldtype": "Section Break",
-   "label": "Display Settings"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_attachments",
-   "fieldtype": "Check",
-   "label": "Show Public Attachments"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_price",
-   "fieldtype": "Check",
-   "label": "Show Price"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_stock_availability",
-   "fieldtype": "Check",
-   "label": "Show Stock Availability"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_contact_us_button",
-   "fieldtype": "Check",
-   "label": "Show Contact Us Button"
-  },
-  {
-   "default": "0",
-   "depends_on": "show_stock_availability",
-   "fieldname": "show_quantity_in_website",
-   "fieldtype": "Check",
-   "label": "Show Stock Quantity"
-  },
-  {
-   "default": "0",
-   "fieldname": "show_apply_coupon_code_in_website",
-   "fieldtype": "Check",
-   "label": "Show Apply Coupon Code"
-  },
-  {
-   "default": "0",
-   "fieldname": "allow_items_not_in_stock",
-   "fieldtype": "Check",
-   "label": "Allow items not in stock to be added to cart"
-  },
-  {
-   "depends_on": "enabled",
-   "fieldname": "section_break_2",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Company",
-   "mandatory_depends_on": "eval: doc.enabled === 1",
-   "options": "Company",
-   "remember_last_selected_value": 1
-  },
-  {
-   "description": "Prices will not be shown if Price List is not set",
-   "fieldname": "price_list",
-   "fieldtype": "Link",
-   "label": "Price List",
-   "mandatory_depends_on": "eval: doc.enabled === 1",
-   "options": "Price List"
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "default_customer_group",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Default Customer Group",
-   "mandatory_depends_on": "eval: doc.enabled === 1",
-   "options": "Customer Group"
-  },
-  {
-   "fieldname": "quotation_series",
-   "fieldtype": "Select",
-   "label": "Quotation Series",
-   "mandatory_depends_on": "eval: doc.enabled === 1"
-  },
-  {
-   "collapsible": 1,
-   "collapsible_depends_on": "eval:doc.enable_checkout",
-   "depends_on": "enabled",
-   "fieldname": "section_break_8",
-   "fieldtype": "Section Break",
-   "label": "Checkout Settings"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_checkout",
-   "fieldtype": "Check",
-   "label": "Enable Checkout"
-  },
-  {
-   "default": "Orders",
-   "depends_on": "enable_checkout",
-   "description": "After payment completion redirect user to selected page.",
-   "fieldname": "payment_success_url",
-   "fieldtype": "Select",
-   "label": "Payment Success Url",
-   "mandatory_depends_on": "enable_checkout",
-   "options": "\nOrders\nInvoices\nMy Account"
-  },
-  {
-   "fieldname": "column_break_11",
-   "fieldtype": "Column Break"
-  },
-  {
-   "depends_on": "enable_checkout",
-   "fieldname": "payment_gateway_account",
-   "fieldtype": "Link",
-   "label": "Payment Gateway Account",
-   "mandatory_depends_on": "enable_checkout",
-   "options": "Payment Gateway Account"
-  },
-  {
-   "fieldname": "column_break_7",
-   "fieldtype": "Column Break"
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_variants",
-   "fieldtype": "Check",
-   "label": "Enable Variants"
-  },
-  {
-   "default": "0",
-   "depends_on": "eval: doc.enable_checkout == 0",
-   "fieldname": "save_quotations_as_draft",
-   "fieldtype": "Check",
-   "label": "Save Quotations as Draft"
-  },
-  {
-   "depends_on": "doc.enabled",
-   "fieldname": "store_page_docs",
-   "fieldtype": "HTML"
-  }
- ],
- "icon": "fa fa-shopping-cart",
- "idx": 1,
- "issingle": 1,
- "links": [],
- "modified": "2021-03-02 17:34:57.642565",
- "modified_by": "Administrator",
- "module": "Shopping Cart",
- "name": "Shopping Cart Settings",
- "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "email": 1,
-   "print": 1,
-   "read": 1,
-   "role": "Website Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
- "sort_field": "modified",
- "sort_order": "ASC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py b/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
deleted file mode 100644
index 4a75599..0000000
--- a/erpnext/shopping_cart/doctype/shopping_cart_settings/shopping_cart_settings.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-# For license information, please see license.txt
-
-
-import frappe
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import flt
-
-
-class ShoppingCartSetupError(frappe.ValidationError): pass
-
-class ShoppingCartSettings(Document):
-	def onload(self):
-		self.get("__onload").quotation_series = frappe.get_meta("Quotation").get_options("naming_series")
-
-	def validate(self):
-		if self.enabled:
-			self.validate_price_list_exchange_rate()
-
-	def validate_price_list_exchange_rate(self):
-		"Check if exchange rate exists for Price List currency (to Company's currency)."
-		from erpnext.setup.utils import get_exchange_rate
-
-		if not self.enabled or not self.company or not self.price_list:
-			return # this function is also called from hooks, check values again
-
-		company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
-		price_list_currency = frappe.db.get_value("Price List", self.price_list, "currency")
-
-		if not company_currency:
-			msg = f"Please specify currency in Company {self.company}"
-			frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
-
-		if not price_list_currency:
-			msg = f"Please specify currency in Price List {frappe.bold(self.price_list)}"
-			frappe.throw(_(msg), title=_("Missing Currency"), exc=ShoppingCartSetupError)
-
-		if price_list_currency != company_currency:
-			from_currency, to_currency = price_list_currency, company_currency
-
-			# Get exchange rate checks Currency Exchange Records too
-			exchange_rate = get_exchange_rate(from_currency, to_currency, args="for_selling")
-
-			if not flt(exchange_rate):
-				msg = f"Missing Currency Exchange Rates for {from_currency}-{to_currency}"
-				frappe.throw(_(msg), title=_("Missing"), exc=ShoppingCartSetupError)
-
-	def validate_tax_rule(self):
-		if not frappe.db.get_value("Tax Rule", {"use_for_shopping_cart" : 1}, "name"):
-			frappe.throw(frappe._("Set Tax Rule for shopping cart"), ShoppingCartSetupError)
-
-	def get_tax_master(self, billing_territory):
-		tax_master = self.get_name_from_territory(billing_territory, "sales_taxes_and_charges_masters",
-			"sales_taxes_and_charges_master")
-		return tax_master and tax_master[0] or None
-
-	def get_shipping_rules(self, shipping_territory):
-		return self.get_name_from_territory(shipping_territory, "shipping_rules", "shipping_rule")
-
-def validate_cart_settings(doc=None, method=None):
-	frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings").run_method("validate")
-
-def get_shopping_cart_settings():
-	if not getattr(frappe.local, "shopping_cart_settings", None):
-		frappe.local.shopping_cart_settings = frappe.get_doc("Shopping Cart Settings", "Shopping Cart Settings")
-
-	return frappe.local.shopping_cart_settings
-
-@frappe.whitelist(allow_guest=True)
-def is_cart_enabled():
-	return get_shopping_cart_settings().enabled
-
-def show_quantity_in_website():
-	return get_shopping_cart_settings().show_quantity_in_website
-
-def check_shopping_cart_enabled():
-	if not get_shopping_cart_settings().enabled:
-		frappe.throw(_("You need to enable Shopping Cart"), ShoppingCartSetupError)
-
-def show_attachments():
-	return get_shopping_cart_settings().show_attachments
diff --git a/erpnext/shopping_cart/filters.py b/erpnext/shopping_cart/filters.py
deleted file mode 100644
index ef0badc..0000000
--- a/erpnext/shopping_cart/filters.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-
-class ProductFiltersBuilder:
-	def __init__(self, item_group=None):
-		if not item_group or item_group == "Products Settings":
-			self.doc = frappe.get_doc("Products Settings")
-		else:
-			self.doc = frappe.get_doc("Item Group", item_group)
-
-		self.item_group = item_group
-
-	def get_field_filters(self):
-		filter_fields = [row.fieldname for row in self.doc.filter_fields]
-
-		meta = frappe.get_meta('Item')
-		fields = [df for df in meta.fields if df.fieldname in filter_fields]
-
-		filter_data = []
-		for df in fields:
-			filters, or_filters = {}, []
-			if df.fieldtype == "Link":
-				if self.item_group:
-					or_filters.extend([
-						["item_group", "=", self.item_group],
-						["Website Item Group", "item_group", "=", self.item_group]
-					])
-
-				values = frappe.get_all("Item", fields=[df.fieldname], filters=filters, or_filters=or_filters, distinct="True", pluck=df.fieldname)
-			else:
-				doctype = df.get_link_doctype()
-
-				# apply enable/disable/show_in_website filter
-				meta = frappe.get_meta(doctype)
-
-				if meta.has_field('enabled'):
-					filters['enabled'] = 1
-				if meta.has_field('disabled'):
-					filters['disabled'] = 0
-				if meta.has_field('show_in_website'):
-					filters['show_in_website'] = 1
-
-				values = [d.name for d in frappe.get_all(doctype, filters)]
-
-			# Remove None
-			if None in values:
-				values.remove(None)
-
-			if values:
-				filter_data.append([df, values])
-
-		return filter_data
-
-	def get_attribute_filters(self):
-		attributes = [row.attribute for row in self.doc.filter_attributes]
-
-		if not attributes:
-			return []
-
-		result = frappe.db.sql(
-			"""
-			select
-				distinct attribute, attribute_value
-			from
-				`tabItem Variant Attribute`
-			where
-				attribute in %(attributes)s
-				and attribute_value is not null
-		""",
-			{"attributes": attributes},
-			as_dict=1,
-		)
-
-		attribute_value_map = {}
-		for d in result:
-			attribute_value_map.setdefault(d.attribute, []).append(d.attribute_value)
-
-		out = []
-		for name, values in attribute_value_map.items():
-			out.append(frappe._dict(name=name, item_attribute_values=values))
-		return out
diff --git a/erpnext/shopping_cart/product_info.py b/erpnext/shopping_cart/product_info.py
deleted file mode 100644
index 977f12f..0000000
--- a/erpnext/shopping_cart/product_info.py
+++ /dev/null
@@ -1,72 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-
-import frappe
-
-from erpnext.shopping_cart.cart import _get_cart_quotation, _set_price_list
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	get_shopping_cart_settings,
-	show_quantity_in_website,
-)
-from erpnext.utilities.product import get_non_stock_item_status, get_price, get_qty_in_stock
-
-
-@frappe.whitelist(allow_guest=True)
-def get_product_info_for_website(item_code, skip_quotation_creation=False):
-	"""get product price / stock info for website"""
-
-	cart_settings = get_shopping_cart_settings()
-	if not cart_settings.enabled:
-		return frappe._dict()
-
-	cart_quotation = frappe._dict()
-	if not skip_quotation_creation:
-		cart_quotation = _get_cart_quotation()
-
-	selling_price_list = cart_quotation.get("selling_price_list") if cart_quotation else _set_price_list(cart_settings, None)
-
-	price = get_price(
-		item_code,
-		selling_price_list,
-		cart_settings.default_customer_group,
-		cart_settings.company
-	)
-
-	stock_status = get_qty_in_stock(item_code, "website_warehouse")
-
-	product_info = {
-		"price": price,
-		"stock_qty": stock_status.stock_qty,
-		"in_stock": stock_status.in_stock if stock_status.is_stock_item else get_non_stock_item_status(item_code, "website_warehouse"),
-		"qty": 0,
-		"uom": frappe.db.get_value("Item", item_code, "stock_uom"),
-		"show_stock_qty": show_quantity_in_website(),
-		"sales_uom": frappe.db.get_value("Item", item_code, "sales_uom")
-	}
-
-	if product_info["price"]:
-		if frappe.session.user != "Guest":
-			item = cart_quotation.get({"item_code": item_code}) if cart_quotation else None
-			if item:
-				product_info["qty"] = item[0].qty
-
-	return frappe._dict({
-		"product_info": product_info,
-		"cart_settings": cart_settings
-	})
-
-def set_product_info_for_website(item):
-	"""set product price uom for website"""
-	product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get("product_info")
-
-	if product_info:
-		item.update(product_info)
-		item["stock_uom"] = product_info.get("uom")
-		item["sales_uom"] = product_info.get("sales_uom")
-		if product_info.get("price"):
-			item["price_stock_uom"] = product_info.get("price").get("formatted_price")
-			item["price_sales_uom"] = product_info.get("price").get("formatted_price_sales_uom")
-		else:
-			item["price_stock_uom"] = ""
-			item["price_sales_uom"] = ""
diff --git a/erpnext/shopping_cart/product_query.py b/erpnext/shopping_cart/product_query.py
deleted file mode 100644
index 5cc0505..0000000
--- a/erpnext/shopping_cart/product_query.py
+++ /dev/null
@@ -1,161 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-
-import frappe
-
-from erpnext.shopping_cart.product_info import get_product_info_for_website
-
-
-class ProductQuery:
-	"""Query engine for product listing
-
-	Attributes:
-	    cart_settings (Document): Settings for Cart
-	    fields (list): Fields to fetch in query
-	    filters (TYPE): Description
-	    or_filters (list): Description
-	    page_length (Int): Length of page for the query
-	    settings (Document): Products Settings DocType
-	    filters (list)
-	    or_filters (list)
-	"""
-
-	def __init__(self):
-		self.settings = frappe.get_doc("Products Settings")
-		self.cart_settings = frappe.get_doc("Shopping Cart Settings")
-		self.page_length = self.settings.products_per_page or 20
-		self.fields = ['name', 'item_name', 'item_code', 'website_image', 'variant_of', 'has_variants',
-			'item_group', 'image', 'web_long_description', 'description', 'route', 'weightage']
-		self.filters = []
-		self.or_filters = [['show_in_website', '=', 1]]
-		if not self.settings.get('hide_variants'):
-			self.or_filters.append(['show_variant_in_website', '=', 1])
-
-	def query(self, attributes=None, fields=None, search_term=None, start=0, item_group=None):
-		"""Summary
-
-		Args:
-		    attributes (dict, optional): Item Attribute filters
-		    fields (dict, optional): Field level filters
-		    search_term (str, optional): Search term to lookup
-		    start (int, optional): Page start
-
-		Returns:
-		    list: List of results with set fields
-		"""
-		if fields: self.build_fields_filters(fields)
-		if search_term: self.build_search_filters(search_term)
-
-		result = []
-		website_item_groups = []
-
-		# if from item group page consider website item group table
-		if item_group:
-			website_item_groups = frappe.db.get_all(
-				"Item",
-				fields=self.fields + ["`tabWebsite Item Group`.parent as wig_parent"],
-				filters=[["Website Item Group", "item_group", "=", item_group]]
-			)
-
-		if attributes:
-			all_items = []
-			for attribute, values in attributes.items():
-				if not isinstance(values, list):
-					values = [values]
-
-				items = frappe.get_all(
-					"Item",
-					fields=self.fields,
-					filters=[
-						*self.filters,
-						["Item Variant Attribute", "attribute", "=", attribute],
-						["Item Variant Attribute", "attribute_value", "in", values],
-					],
-					or_filters=self.or_filters,
-					start=start,
-					limit=self.page_length,
-					order_by="weightage desc"
-				)
-
-				items_dict = {item.name: item for item in items}
-
-				all_items.append(set(items_dict.keys()))
-
-			result = [items_dict.get(item) for item in list(set.intersection(*all_items))]
-		else:
-			result = frappe.get_all(
-				"Item",
-				fields=self.fields,
-				filters=self.filters,
-				or_filters=self.or_filters,
-				start=start,
-				limit=self.page_length,
-				order_by="weightage desc"
-			)
-
-		# Combine results having context of website item groups into item results
-		if item_group and website_item_groups:
-			items_list = {row.name for row in result}
-			for row in website_item_groups:
-				if row.wig_parent not in items_list:
-					result.append(row)
-
-		result = sorted(result, key=lambda x: x.get("weightage"), reverse=True)
-
-		for item in result:
-			product_info = get_product_info_for_website(item.item_code, skip_quotation_creation=True).get('product_info')
-			if product_info:
-				item.formatted_price = (product_info.get('price') or {}).get('formatted_price')
-
-		return result
-
-	def build_fields_filters(self, filters):
-		"""Build filters for field values
-
-		Args:
-		    filters (dict): Filters
-		"""
-		for field, values in filters.items():
-			if not values:
-				continue
-
-			# handle multiselect fields in filter addition
-			meta = frappe.get_meta('Item', cached=True)
-			df = meta.get_field(field)
-			if df.fieldtype == 'Table MultiSelect':
-				child_doctype = df.options
-				child_meta = frappe.get_meta(child_doctype, cached=True)
-				fields = child_meta.get("fields")
-				if fields:
-					self.filters.append([child_doctype, fields[0].fieldname, 'IN', values])
-			elif isinstance(values, list):
-				# If value is a list use `IN` query
-				self.filters.append([field, 'IN', values])
-			else:
-				# `=` will be faster than `IN` for most cases
-				self.filters.append([field, '=', values])
-
-	def build_search_filters(self, search_term):
-		"""Query search term in specified fields
-
-		Args:
-		    search_term (str): Search candidate
-		"""
-		# Default fields to search from
-		default_fields = {'name', 'item_name', 'description', 'item_group'}
-
-		# Get meta search fields
-		meta = frappe.get_meta("Item")
-		meta_fields = set(meta.get_search_fields())
-
-		# Join the meta fields and default fields set
-		search_fields = default_fields.union(meta_fields)
-		try:
-			if frappe.db.count('Item', cache=True) > 50000:
-				search_fields.remove('description')
-		except KeyError:
-			pass
-
-		# Build or filters for query
-		search = '%{}%'.format(search_term)
-		self.or_filters += [[field, 'like', search] for field in search_fields]
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 37e9e89..c9d5f61 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -213,7 +213,14 @@
 			label: __('Target Warehouse'),
 			fieldtype: 'Link',
 			options: 'Warehouse',
-			reqd: 1
+			reqd: 1,
+			get_query() {
+				return {
+					filters: {
+						is_group: 0
+					}
+				}
+			}
 		},
 		{
 			fieldname: 'qty',
@@ -252,52 +259,21 @@
 		dialog.get_field('target').refresh();
 	}
 
-	dialog.set_primary_action(__('Submit'), function () {
-		var values = dialog.get_values();
-		if (!values) {
-			return;
-		}
-		if (source && values.qty > actual_qty) {
-			frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
-			return;
-		}
-		if (values.source === values.target) {
-			frappe.msgprint(__('Source and target warehouse must be different'));
-		}
-
-		frappe.call({
-			method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
-			args: values,
-			btn: dialog.get_primary_btn(),
-			freeze: true,
-			freeze_message: __('Creating Stock Entry'),
-			callback: function (r) {
-				frappe.show_alert(__('Stock Entry {0} created',
-					['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
-				dialog.hide();
-				callback(r);
-			},
+	dialog.set_primary_action(__('Create Stock Entry'), function () {
+		frappe.model.with_doctype('Stock Entry', function () {
+			let doc = frappe.model.get_new_doc('Stock Entry');
+			doc.from_warehouse = dialog.get_value('source');
+			doc.to_warehouse = dialog.get_value('target');
+			doc.stock_entry_type = doc.from_warehouse ? "Material Transfer" : "Material Receipt";
+			let row = frappe.model.add_child(doc, 'items');
+			row.item_code = dialog.get_value('item_code');
+			row.s_warehouse = dialog.get_value('source');
+			row.t_warehouse = dialog.get_value('target');
+			row.qty = dialog.get_value('qty');
+			row.conversion_factor = 1;
+			row.transfer_qty = dialog.get_value('qty');
+			row.basic_rate = dialog.get_value('rate');
+			frappe.set_route('Form', doc.doctype, doc.name);
 		});
 	});
-
-	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' +
-			__("Add more items or open full form") + '</a></p>')
-		.appendTo(dialog.body)
-		.find('.link-open')
-		.on('click', function () {
-			frappe.model.with_doctype('Stock Entry', function () {
-				var doc = frappe.model.get_new_doc('Stock Entry');
-				doc.from_warehouse = dialog.get_value('source');
-				doc.to_warehouse = dialog.get_value('target');
-				var row = frappe.model.add_child(doc, 'items');
-				row.item_code = dialog.get_value('item_code');
-				row.f_warehouse = dialog.get_value('target');
-				row.t_warehouse = dialog.get_value('target');
-				row.qty = dialog.get_value('qty');
-				row.conversion_factor = 1;
-				row.transfer_qty = dialog.get_value('qty');
-				row.basic_rate = dialog.get_value('rate');
-				frappe.set_route('Form', doc.doctype, doc.name);
-			});
-		});
 };
diff --git a/erpnext/stock/doctype/batch/batch.json b/erpnext/stock/doctype/batch/batch.json
index fc4cf1d..967c572 100644
--- a/erpnext/stock/doctype/batch/batch.json
+++ b/erpnext/stock/doctype/batch/batch.json
@@ -9,6 +9,8 @@
  "field_order": [
   "sb_disabled",
   "disabled",
+  "column_break_24",
+  "use_batchwise_valuation",
   "sb_batch",
   "batch_id",
   "item",
@@ -186,6 +188,18 @@
    "fieldtype": "Float",
    "label": "Produced Qty",
    "read_only": 1
+  },
+  {
+   "fieldname": "column_break_24",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "use_batchwise_valuation",
+   "fieldtype": "Check",
+   "label": "Use Batch-wise Valuation",
+   "read_only": 1,
+   "set_only_once": 1
   }
  ],
  "icon": "fa fa-archive",
@@ -193,10 +207,11 @@
  "image_field": "image",
  "links": [],
  "max_attachments": 5,
- "modified": "2021-07-08 16:22:01.343105",
+ "modified": "2022-02-21 08:08:23.999236",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Batch",
+ "naming_rule": "By fieldname",
  "owner": "Administrator",
  "permissions": [
   {
@@ -217,6 +232,7 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "batch_id",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 96751d6..c9b4c14 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -110,11 +110,18 @@
 
 	def validate(self):
 		self.item_has_batch_enabled()
+		self.set_batchwise_valuation()
 
 	def item_has_batch_enabled(self):
 		if frappe.db.get_value("Item", self.item, "has_batch_no") == 0:
 			frappe.throw(_("The selected item cannot have Batch"))
 
+	def set_batchwise_valuation(self):
+		from erpnext.stock.stock_ledger import get_valuation_method
+
+		if self.is_new() and get_valuation_method(self.item) != "Moving Average":
+			self.use_batchwise_valuation = 1
+
 	def before_save(self):
 		has_expiry_date, shelf_life_in_days = frappe.db.get_value('Item', self.item, ['has_expiry_date', 'shelf_life_in_days'])
 		if not self.expiry_date and has_expiry_date and shelf_life_in_days:
diff --git a/erpnext/stock/doctype/batch/test_batch.py b/erpnext/stock/doctype/batch/test_batch.py
index 0a663c2..5763753 100644
--- a/erpnext/stock/doctype/batch/test_batch.py
+++ b/erpnext/stock/doctype/batch/test_batch.py
@@ -1,17 +1,25 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
+import json
+
 import frappe
 from frappe.exceptions import ValidationError
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import cint, flt
+from frappe.utils.data import add_to_date, getdate
 
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.stock.doctype.batch.batch import UnableToSelectBatchError, get_batch_no, get_batch_qty
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
+	create_stock_reconciliation,
+)
 from erpnext.stock.get_item_details import get_item_details
-from erpnext.tests.utils import ERPNextTestCase
+from erpnext.stock.stock_ledger import get_valuation_rate
 
 
-class TestBatch(ERPNextTestCase):
+class TestBatch(FrappeTestCase):
 	def test_item_has_batch_enabled(self):
 		self.assertRaises(ValidationError, frappe.get_doc({
 			"doctype": "Batch",
@@ -300,6 +308,105 @@
 		details = get_item_details(args)
 		self.assertEqual(details.get('price_list_rate'), 400)
 
+
+	def test_basic_batch_wise_valuation(self, batch_qty = 100):
+		item_code = "_TestBatchWiseVal"
+		warehouse = "_Test Warehouse - _TC"
+		self.make_batch_item(item_code)
+
+		rates = [42, 420]
+
+		batches = {}
+		for rate in rates:
+			se = make_stock_entry(item_code=item_code, qty=10, rate=rate, target=warehouse)
+			batches[se.items[0].batch_no] = rate
+
+		LOW, HIGH = list(batches.keys())
+
+		# consume things out of order
+		consumption_plan = [
+			(HIGH, 1),
+			(LOW, 2),
+			(HIGH, 2),
+			(HIGH, 4),
+			(LOW, 6),
+		]
+
+		stock_value = sum(rates) * 10
+		qty_after_transaction = 20
+		for batch, qty in consumption_plan:
+			# consume out of order
+			se = make_stock_entry(item_code=item_code, source=warehouse, qty=qty, batch_no=batch)
+
+			sle = frappe.get_last_doc("Stock Ledger Entry", {"is_cancelled": 0, "voucher_no": se.name})
+
+			stock_value_difference = sle.actual_qty * batches[sle.batch_no]
+			self.assertAlmostEqual(sle.stock_value_difference, stock_value_difference)
+
+			stock_value += stock_value_difference
+			self.assertAlmostEqual(sle.stock_value, stock_value)
+
+			qty_after_transaction += sle.actual_qty
+			self.assertAlmostEqual(sle.qty_after_transaction, qty_after_transaction)
+			self.assertAlmostEqual(sle.valuation_rate, stock_value / qty_after_transaction)
+
+			self.assertEqual(json.loads(sle.stock_queue), [])  # queues don't apply on batched items
+
+	def test_moving_batch_valuation_rates(self):
+		item_code = "_TestBatchWiseVal"
+		warehouse = "_Test Warehouse - _TC"
+		self.make_batch_item(item_code)
+
+		def assertValuation(expected):
+			actual = get_valuation_rate(item_code, warehouse, "voucher_type", "voucher_no", batch_no=batch_no)
+			self.assertAlmostEqual(actual, expected)
+
+		se = make_stock_entry(item_code=item_code, qty=100, rate=10, target=warehouse)
+		batch_no = se.items[0].batch_no
+		assertValuation(10)
+
+		# consumption should never affect current valuation rate
+		make_stock_entry(item_code=item_code, qty=20, source=warehouse)
+		assertValuation(10)
+
+		make_stock_entry(item_code=item_code, qty=30, source=warehouse)
+		assertValuation(10)
+
+		# 50 * 10 = 500 current value, add more item with higher valuation
+		make_stock_entry(item_code=item_code, qty=50, rate=20, target=warehouse, batch_no=batch_no)
+		assertValuation(15)
+
+		# consuming again shouldn't do anything
+		make_stock_entry(item_code=item_code, qty=20, source=warehouse)
+		assertValuation(15)
+
+		# reset rate with stock reconiliation
+		create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=10, rate=25, batch_no=batch_no)
+		assertValuation(25)
+
+		make_stock_entry(item_code=item_code, qty=20, rate=20, target=warehouse, batch_no=batch_no)
+		assertValuation((20 * 20 + 10 * 25) / (10 + 20))
+
+
+	def test_update_batch_properties(self):
+		item_code = "_TestBatchWiseVal"
+		self.make_batch_item(item_code)
+
+		se = make_stock_entry(item_code=item_code, qty=100, rate=10, target="_Test Warehouse - _TC")
+		batch_no = se.items[0].batch_no
+		batch = frappe.get_doc("Batch", batch_no)
+
+		expiry_date = add_to_date(batch.manufacturing_date, days=30)
+
+		batch.expiry_date = expiry_date
+		batch.save()
+
+		batch.reload()
+
+		self.assertEqual(getdate(batch.expiry_date), getdate(expiry_date))
+
+
+
 def create_batch(item_code, rate, create_item_price_for_batch):
 	pi = make_purchase_invoice(company="_Test Company",
 		warehouse= "Stores - _TC", cost_center = "Main - _TC", update_stock=1,
@@ -326,14 +433,13 @@
 def make_new_batch(**args):
 	args = frappe._dict(args)
 
-	try:
+	if frappe.db.exists("Batch", args.batch_id):
+		batch = frappe.get_doc("Batch", args.batch_id)
+	else:
 		batch = frappe.get_doc({
 			"doctype": "Batch",
 			"batch_id": args.batch_id,
 			"item": args.item_code,
 		}).insert()
 
-	except frappe.DuplicateEntryError:
-		batch = frappe.get_doc("Batch", args.batch_id)
-
 	return batch
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 8e79f0e..56dc71c 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -33,6 +33,7 @@
    "oldfieldtype": "Link",
    "options": "Warehouse",
    "read_only": 1,
+   "reqd": 1,
    "search_index": 1
   },
   {
@@ -46,6 +47,7 @@
    "oldfieldtype": "Link",
    "options": "Item",
    "read_only": 1,
+   "reqd": 1,
    "search_index": 1
   },
   {
@@ -169,10 +171,11 @@
  "idx": 1,
  "in_create": 1,
  "links": [],
- "modified": "2021-03-30 23:09:39.572776",
+ "modified": "2022-01-30 17:04:54.715288",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Bin",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -200,5 +203,6 @@
  "quick_entry": 1,
  "search_fields": "item_code,warehouse",
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py
index 0ef7ce2..3bc15a8 100644
--- a/erpnext/stock/doctype/bin/bin.py
+++ b/erpnext/stock/doctype/bin/bin.py
@@ -20,43 +20,12 @@
 			+ flt(self.indented_qty) + flt(self.planned_qty) - flt(self.reserved_qty)
 			- flt(self.reserved_qty_for_production) - flt(self.reserved_qty_for_sub_contract))
 
-	def get_first_sle(self):
-		sle = frappe.qb.DocType("Stock Ledger Entry")
-		first_sle = (
-				frappe.qb.from_(sle)
-					.select("*")
-					.where((sle.item_code == self.item_code) & (sle.warehouse == self.warehouse))
-					.orderby(sle.posting_date, sle.posting_time, sle.creation)
-					.limit(1)
-				).run(as_dict=True)
-
-		return first_sle and first_sle[0] or None
-
 	def update_reserved_qty_for_production(self):
 		'''Update qty reserved for production from Production Item tables
 			in open work orders'''
+		from erpnext.manufacturing.doctype.work_order.work_order import get_reserved_qty_for_production
 
-		wo = frappe.qb.DocType("Work Order")
-		wo_item = frappe.qb.DocType("Work Order Item")
-
-		self.reserved_qty_for_production = (
-				frappe.qb
-					.from_(wo)
-					.from_(wo_item)
-					.select(Sum(Case()
-							.when(wo.skip_transfer == 0, wo_item.required_qty - wo_item.transferred_qty)
-							.else_(wo_item.required_qty - wo_item.consumed_qty))
-						)
-					.where(
-						(wo_item.item_code == self.item_code)
-						& (wo_item.parent == wo.name)
-						& (wo.docstatus == 1)
-						& (wo_item.source_warehouse == self.warehouse)
-						& (wo.status.notin(["Stopped", "Completed"]))
-						& ((wo_item.required_qty > wo_item.transferred_qty)
-							| (wo_item.required_qty > wo_item.consumed_qty))
-					)
-		).run()[0][0] or 0.0
+		self.reserved_qty_for_production = get_reserved_qty_for_production(self.item_code, self.warehouse)
 
 		self.set_projected_qty()
 
@@ -123,16 +92,9 @@
 		self.db_set('projected_qty', self.projected_qty)
 
 def on_doctype_update():
-	frappe.db.add_index("Bin", ["item_code", "warehouse"])
+	frappe.db.add_unique("Bin", ["item_code", "warehouse"], constraint_name="unique_item_warehouse")
 
 
-def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_voucher=False):
-	"""WARNING: This function is deprecated. Inline this function instead of using it."""
-	from erpnext.stock.stock_ledger import repost_current_voucher
-
-	repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher)
-	update_qty(bin_name, args)
-
 def get_bin_details(bin_name):
 	return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty',
 	'reserved_qty', 'indented_qty', 'planned_qty', 'reserved_qty_for_production',
diff --git a/erpnext/stock/doctype/bin/test_bin.py b/erpnext/stock/doctype/bin/test_bin.py
index 9c390d9..ec0d8a8 100644
--- a/erpnext/stock/doctype/bin/test_bin.py
+++ b/erpnext/stock/doctype/bin/test_bin.py
@@ -1,9 +1,36 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-import unittest
+import frappe
+from frappe.tests.utils import FrappeTestCase
 
-# test_records = frappe.get_test_records('Bin')
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.utils import _create_bin
 
-class TestBin(unittest.TestCase):
-	pass
+
+class TestBin(FrappeTestCase):
+
+
+	def test_concurrent_inserts(self):
+		""" Ensure no duplicates are possible in case of concurrent inserts"""
+		item_code = "_TestConcurrentBin"
+		make_item(item_code)
+		warehouse = "_Test Warehouse - _TC"
+
+		bin1 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
+		bin1.insert()
+
+		bin2 = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
+		with self.assertRaises(frappe.UniqueValidationError):
+			bin2.insert()
+
+		# util method should handle it
+		bin = _create_bin(item_code, warehouse)
+		self.assertEqual(bin.item_code, item_code)
+
+		frappe.db.rollback()
+
+	def test_index_exists(self):
+		indexes = frappe.db.sql("show index from tabBin where Non_unique = 0", as_dict=1)
+		if not any(index.get("Key_name") == "unique_item_warehouse" for index in indexes):
+			self.fail(f"Expected unique index on item-warehouse")
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 70d48a4..2a4d639 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -14,6 +14,7 @@
 from erpnext.controllers.selling_controller import SellingController
 from erpnext.stock.doctype.batch.batch import set_batch_nos
 from erpnext.stock.doctype.serial_no.serial_no import get_delivery_note_serial_no
+from erpnext.stock.utils import calculate_mapped_packed_items_return
 
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
@@ -128,8 +129,12 @@
 		self.validate_uom_is_integer("uom", "qty")
 		self.validate_with_previous_doc()
 
-		from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
-		make_packing_list(self)
+		# Keeps mapped packed_items in case product bundle is updated.
+		if self.is_return and self.return_against:
+			calculate_mapped_packed_items_return(self)
+		else:
+			from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+			make_packing_list(self)
 
 		if self._action != 'submit' and not self.is_return:
 			set_batch_nos(self, 'warehouse', throw=True)
@@ -334,17 +339,35 @@
 			frappe.throw(_("Could not create Credit Note automatically, please uncheck 'Issue Credit Note' and submit again"))
 
 def update_billed_amount_based_on_so(so_detail, update_modified=True):
+	from frappe.query_builder.functions import Sum
+
 	# Billed against Sales Order directly
-	billed_against_so = frappe.db.sql("""select sum(amount) from `tabSales Invoice Item`
-		where so_detail=%s and (dn_detail is null or dn_detail = '') and docstatus=1""", so_detail)
+	si = frappe.qb.DocType("Sales Invoice").as_("si")
+	si_item = frappe.qb.DocType("Sales Invoice Item").as_("si_item")
+	sum_amount = Sum(si_item.amount).as_("amount")
+
+	billed_against_so = frappe.qb.from_(si).from_(si_item).select(sum_amount).where(
+		(si_item.parent == si.name) &
+		(si_item.so_detail == so_detail) &
+		((si_item.dn_detail.isnull()) | (si_item.dn_detail == '')) &
+		(si_item.docstatus == 1) &
+		(si.update_stock == 0)
+	).run()
 	billed_against_so = billed_against_so and billed_against_so[0][0] or 0
 
 	# Get all Delivery Note Item rows against the Sales Order Item row
-	dn_details = frappe.db.sql("""select dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent
-		from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
-		where dn.name=dn_item.parent and dn_item.so_detail=%s
-			and dn.docstatus=1 and dn.is_return = 0
-		order by dn.posting_date asc, dn.posting_time asc, dn.name asc""", so_detail, as_dict=1)
+
+	dn = frappe.qb.DocType("Delivery Note").as_("dn")
+	dn_item = frappe.qb.DocType("Delivery Note Item").as_("dn_item")
+
+	dn_details = frappe.qb.from_(dn).from_(dn_item).select(dn_item.name, dn_item.amount, dn_item.si_detail, dn_item.parent, dn_item.stock_qty, dn_item.returned_qty).where(
+		(dn.name == dn_item.parent) &
+		(dn_item.so_detail == so_detail) &
+		(dn.docstatus == 1) &
+		(dn.is_return == 0)
+	).orderby(
+		dn.posting_date, dn.posting_time, dn.name
+	).run(as_dict=True)
 
 	updated_dn = []
 	for dnd in dn_details:
@@ -362,7 +385,11 @@
 
 		# Distribute billed amount directly against SO between DNs based on FIFO
 		if billed_against_so and billed_amt_agianst_dn < dnd.amount:
-			pending_to_bill = flt(dnd.amount) - billed_amt_agianst_dn
+			if dnd.returned_qty:
+				pending_to_bill = flt(dnd.amount) * (dnd.stock_qty - dnd.returned_qty) / dnd.stock_qty
+			else:
+				pending_to_bill = flt(dnd.amount)
+			pending_to_bill -= billed_amt_agianst_dn
 			if pending_to_bill <= billed_against_so:
 				billed_amt_agianst_dn += pending_to_bill
 				billed_against_so -= pending_to_bill
@@ -581,7 +608,18 @@
 			"validation": {
 				"docstatus": ["=", 0]
 			}
+		},
+
+		"Delivery Note Item": {
+			"doctype": "Packing Slip Item",
+			"field_map": {
+				"item_code": "item_code",
+				"item_name": "item_name",
+				"description": "description",
+				"qty": "qty",
+			}
 		}
+
 	}, target_doc)
 
 	return doclist
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_list.js b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
index 0402898..9e6f3bc 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_list.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_list.js
@@ -14,7 +14,7 @@
 			return [__("Completed"), "green", "per_billed,=,100"];
 		}
 	},
-	onload: function (doclist) {
+	onload: function (listview) {
 		const action = () => {
 			const selected_docs = doclist.get_checked_items();
 			const docnames = doclist.get_checked_items(true);
@@ -54,6 +54,16 @@
 			};
 		};
 
-		doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false);
+		// doclist.page.add_actions_menu_item(__('Create Delivery Trip'), action, false);
+
+		listview.page.add_action_item(__('Create Delivery Trip'), action);
+
+		listview.page.add_action_item(__("Sales Invoice"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Sales Invoice");
+		});
+
+		listview.page.add_action_item(__("Packaging Slip From Delivery Note"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Delivery Note", "Packing Slip");
+		});
 	}
 };
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 4f89a19..16c8921 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -6,6 +6,7 @@
 import json
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import cstr, flt, nowdate, nowtime
 
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
@@ -35,10 +36,9 @@
 )
 from erpnext.stock.doctype.warehouse.test_warehouse import get_warehouse
 from erpnext.stock.stock_ledger import get_previous_sle
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestDeliveryNote(ERPNextTestCase):
+class TestDeliveryNote(FrappeTestCase):
 	def test_over_billing_against_dn(self):
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
 
@@ -386,8 +386,7 @@
 		self.assertEqual(actual_qty, 25)
 
 		#  return bundled item
-		dn1 = create_delivery_note(item_code='_Test Product Bundle Item', is_return=1,
-			return_against=dn.name, qty=-2, rate=500, company=company, warehouse="Stores - TCP1", expense_account="Cost of Goods Sold - TCP1", cost_center="Main - TCP1")
+		dn1 = create_return_delivery_note(source_name=dn.name, rate=500, qty=-2)
 
 		# qty after return
 		actual_qty = get_qty_after_transaction(warehouse="Stores - TCP1")
@@ -823,6 +822,15 @@
 
 		automatically_fetch_payment_terms(enable=0)
 
+def create_return_delivery_note(**args):
+	args = frappe._dict(args)
+	from erpnext.controllers.sales_and_purchase_return import make_return_doc
+	doc = make_return_doc("Delivery Note", args.source_name, None)
+	doc.items[0].rate = args.rate
+	doc.items[0].qty = args.qty
+	doc.submit()
+	return doc
+
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 51c88be..f1f5d96 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -757,6 +757,7 @@
   },
   {
    "default": "0",
+   "fetch_from": "item_code.grant_commission",
    "fieldname": "grant_commission",
    "fieldtype": "Check",
    "label": "Grant Commission",
@@ -767,12 +768,14 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-10-06 12:12:44.018872",
+ "modified": "2022-02-24 14:42:20.211085",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "DESC"
-}
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
index 321f48b..dcdff4a 100644
--- a/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/test_delivery_trip.py
@@ -4,6 +4,7 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, flt, now_datetime, nowdate
 
 import erpnext
@@ -12,10 +13,10 @@
 	make_expense_claim,
 	notify_customers,
 )
-from erpnext.tests.utils import ERPNextTestCase, create_test_contact_and_address
+from erpnext.tests.utils import create_test_contact_and_address
 
 
-class TestDeliveryTrip(ERPNextTestCase):
+class TestDeliveryTrip(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		driver = create_driver()
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 752a1fe..ffea9c2 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -17,8 +17,6 @@
 			frm.fields_dict["attributes"].grid.set_column_disp("attribute_value", true);
 		}
 
-		// should never check Private
-		frm.fields_dict["website_image"].df.is_private = 0;
 		if (frm.doc.is_fixed_asset) {
 			frm.trigger("set_asset_naming_series");
 		}
@@ -91,6 +89,29 @@
 			erpnext.toggle_naming_series();
 		}
 
+		if (!frm.doc.published_in_website) {
+			frm.add_custom_button(__("Publish in Website"), function() {
+				frappe.call({
+					method: "erpnext.e_commerce.doctype.website_item.website_item.make_website_item",
+					args: {doc: frm.doc},
+					freeze: true,
+					freeze_message: __("Publishing Item ..."),
+					callback: function(result) {
+						frappe.msgprint({
+							message: __("Website Item {0} has been created.",
+								[repl('<a href="/app/website-item/%(item_encoded)s" class="strong">%(item)s</a>', {
+									item_encoded: encodeURIComponent(result.message[0]),
+									item: result.message[1]
+								})]
+							),
+							title: __("Published"),
+							indicator: "green"
+						});
+					}
+				});
+			}, __('Actions'));
+		}
+
 		erpnext.item.edit_prices_button(frm);
 		erpnext.item.toggle_attributes(frm);
 
@@ -182,25 +203,8 @@
 		}
 	},
 
-	copy_from_item_group: function(frm) {
-		return frm.call({
-			doc: frm.doc,
-			method: "copy_specification_from_item_group"
-		});
-	},
-
 	has_variants: function(frm) {
 		erpnext.item.toggle_attributes(frm);
-	},
-
-	show_in_website: function(frm) {
-		if (frm.doc.default_warehouse && !frm.doc.website_warehouse){
-			frm.set_value("website_warehouse", frm.doc.default_warehouse);
-		}
-	},
-
-	set_meta_tags(frm) {
-		frappe.utils.set_meta_tag(frm.doc.route);
 	}
 });
 
@@ -376,8 +380,7 @@
 		// Show Stock Levels only if is_stock_item
 		if (frm.doc.is_stock_item) {
 			frappe.require('item-dashboard.bundle.js', function() {
-				frm.dashboard.parent.find('.stock-levels').remove();
-				const section = frm.dashboard.add_section('', __("Stock Levels"), 'stock-levels');
+				const section = frm.dashboard.add_section('', __("Stock Levels"));
 				erpnext.item.item_dashboard = new erpnext.stock.ItemDashboard({
 					parent: section,
 					item_code: frm.doc.name,
@@ -393,13 +396,15 @@
 	edit_prices_button: function(frm) {
 		frm.add_custom_button(__("Add / Edit Prices"), function() {
 			frappe.set_route("List", "Item Price", {"item_code": frm.doc.name});
-		}, __("View"));
+		}, __("Actions"));
 	},
 
-	weight_to_validate: function(frm){
-		if((frm.doc.nett_weight || frm.doc.gross_weight) && !frm.doc.weight_uom) {
-			frappe.msgprint(__('Weight is mentioned,\nPlease mention "Weight UOM" too'));
-			frappe.validated = 0;
+	weight_to_validate: function(frm) {
+		if (frm.doc.weight_per_unit && !frm.doc.weight_uom) {
+			frappe.msgprint({
+				message: __("Please mention 'Weight UOM' along with Weight."),
+				title: __("Note")
+			});
 		}
 	},
 
@@ -540,7 +545,7 @@
 			let selected_attributes = {};
 			me.multiple_variant_dialog.$wrapper.find('.form-column').each((i, col) => {
 				if(i===0) return;
-				let attribute_name = $(col).find('label').html();
+				let attribute_name = $(col).find('label').html().trim();
 				selected_attributes[attribute_name] = [];
 				let checked_opts = $(col).find('.checkbox input');
 				checked_opts.each((i, opt) => {
@@ -589,7 +594,7 @@
 							const increment = r.message.increment;
 
 							let values = [];
-							for(var i = from; i <= to; i += increment) {
+							for(var i = from; i <= to; i = flt(i + increment, 6)) {
 								values.push(i);
 							}
 							attr_val_fields[d.attribute] = values;
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 29abd45..c797187 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -28,6 +28,7 @@
   "standard_rate",
   "is_fixed_asset",
   "auto_create_assets",
+  "is_grouped_asset",
   "asset_category",
   "asset_naming_series",
   "over_delivery_receipt_allowance",
@@ -47,6 +48,7 @@
   "warranty_period",
   "weight_per_unit",
   "weight_uom",
+  "allow_negative_stock",
   "reorder_section",
   "reorder_levels",
   "unit_of_measure_conversion",
@@ -116,24 +118,8 @@
   "customer_code",
   "default_item_manufacturer",
   "default_manufacturer_part_no",
-  "website_section",
-  "show_in_website",
-  "show_variant_in_website",
-  "route",
-  "weightage",
-  "slideshow",
-  "website_image",
-  "website_image_alt",
-  "thumbnail",
-  "cb72",
-  "website_warehouse",
-  "website_item_groups",
-  "set_meta_tags",
-  "sb72",
-  "copy_from_item_group",
-  "website_specifications",
-  "web_long_description",
-  "website_content",
+  "more_information_section",
+  "published_in_website",
   "total_projected_qty"
  ],
  "fields": [
@@ -361,7 +347,7 @@
    "fieldname": "valuation_method",
    "fieldtype": "Select",
    "label": "Valuation Method",
-   "options": "\nFIFO\nMoving Average"
+   "options": "\nFIFO\nMoving Average\nLIFO"
   },
   {
    "depends_on": "is_stock_item",
@@ -856,125 +842,6 @@
    "print_hide": 1
   },
   {
-   "collapsible": 1,
-   "depends_on": "eval:!doc.is_fixed_asset",
-   "fieldname": "website_section",
-   "fieldtype": "Section Break",
-   "label": "Website",
-   "options": "fa fa-globe"
-  },
-  {
-   "default": "0",
-   "depends_on": "eval:!doc.variant_of",
-   "fieldname": "show_in_website",
-   "fieldtype": "Check",
-   "label": "Show in Website",
-   "search_index": 1
-  },
-  {
-   "default": "0",
-   "depends_on": "variant_of",
-   "fieldname": "show_variant_in_website",
-   "fieldtype": "Check",
-   "label": "Show in Website (Variant)",
-   "search_index": 1
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "route",
-   "fieldtype": "Small Text",
-   "label": "Route",
-   "no_copy": 1
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "description": "Items with higher weightage will be shown higher",
-   "fieldname": "weightage",
-   "fieldtype": "Int",
-   "label": "Weightage"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "description": "Show a slideshow at the top of the page",
-   "fieldname": "slideshow",
-   "fieldtype": "Link",
-   "label": "Slideshow",
-   "options": "Website Slideshow"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "description": "Item Image (if not slideshow)",
-   "fieldname": "website_image",
-   "fieldtype": "Attach",
-   "label": "Website Image"
-  },
-  {
-   "fieldname": "thumbnail",
-   "fieldtype": "Data",
-   "label": "Thumbnail",
-   "read_only": 1
-  },
-  {
-   "fieldname": "cb72",
-   "fieldtype": "Column Break"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "description": "Show \"In Stock\" or \"Not in Stock\" based on stock available in this warehouse.",
-   "fieldname": "website_warehouse",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Website Warehouse",
-   "options": "Warehouse"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "description": "List this Item in multiple groups on the website.",
-   "fieldname": "website_item_groups",
-   "fieldtype": "Table",
-   "label": "Website Item Groups",
-   "options": "Website Item Group"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "set_meta_tags",
-   "fieldtype": "Button",
-   "label": "Set Meta Tags"
-  },
-  {
-   "collapsible": 1,
-   "collapsible_depends_on": "website_specifications",
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "sb72",
-   "fieldtype": "Section Break",
-   "label": "Website Specifications"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "copy_from_item_group",
-   "fieldtype": "Button",
-   "label": "Copy From Item Group"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "website_specifications",
-   "fieldtype": "Table",
-   "label": "Website Specifications",
-   "options": "Item Website Specification"
-  },
-  {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "web_long_description",
-   "fieldtype": "Text Editor",
-   "label": "Website Description"
-  },
-  {
-   "description": "You can use any valid Bootstrap 4 markup in this field. It will be shown on your Item Page.",
-   "fieldname": "website_content",
-   "fieldtype": "HTML Editor",
-   "label": "Website Content"
-  },
-  {
    "fieldname": "total_projected_qty",
    "fieldtype": "Float",
    "hidden": 1,
@@ -1016,25 +883,45 @@
    "read_only": 1
   },
   {
-   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
-   "fieldname": "website_image_alt",
-   "fieldtype": "Data",
-   "label": "Image Description"
+   "collapsible": 1,
+   "fieldname": "more_information_section",
+   "fieldtype": "Section Break",
+   "label": "More Information"
+  },
+  {
+   "default": "0",
+   "depends_on": "published_in_website",
+   "fieldname": "published_in_website",
+   "fieldtype": "Check",
+   "label": "Published in Website",
+   "read_only": 1
   },
   {
    "default": "1",
    "fieldname": "grant_commission",
    "fieldtype": "Check",
    "label": "Grant Commission"
+  },
+  {
+   "default": "0",
+   "depends_on": "auto_create_assets",
+   "fieldname": "is_grouped_asset",
+   "fieldtype": "Check",
+   "label": "Create Grouped Asset"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_negative_stock",
+   "fieldtype": "Check",
+   "label": "Allow Negative Stock"
   }
  ],
- "has_web_view": 1,
  "icon": "fa fa-tag",
  "idx": 2,
  "image_field": "image",
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-12-14 04:13:16.857534",
+ "modified": "2022-02-11 08:07:46.663220",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
@@ -1104,6 +991,7 @@
  "show_preview_popup": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "item_name",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 281e881..494fb3b 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -2,12 +2,12 @@
 # License: GNU General Public License v3. See license.txt
 
 import copy
-import itertools
 import json
 from typing import List
 
 import frappe
 from frappe import _
+from frappe.model.document import Document
 from frappe.utils import (
 	cint,
 	cstr,
@@ -17,13 +17,9 @@
 	getdate,
 	now_datetime,
 	nowtime,
-	random_string,
 	strip,
 )
 from frappe.utils.html_utils import clean_html
-from frappe.website.doctype.website_slideshow.website_slideshow import get_slideshow
-from frappe.website.utils import clear_cache
-from frappe.website.website_generator import WebsiteGenerator
 
 import erpnext
 from erpnext.controllers.item_variant import (
@@ -33,10 +29,7 @@
 	make_variant_item_code,
 	validate_item_variant_attributes,
 )
-from erpnext.setup.doctype.item_group.item_group import (
-	get_parent_item_groups,
-	invalidate_cache_for,
-)
+from erpnext.setup.doctype.item_group.item_group import invalidate_cache_for
 from erpnext.stock.doctype.item_default.item_default import ItemDefault
 
 
@@ -51,18 +44,11 @@
 class InvalidBarcode(frappe.ValidationError):
 	pass
 
+class DataValidationError(frappe.ValidationError):
+	pass
 
-class Item(WebsiteGenerator):
-	website = frappe._dict(
-		page_title_field="item_name",
-		condition_field="show_in_website",
-		template="templates/generators/item/item.html",
-		no_cache=1
-	)
-
+class Item(Document):
 	def onload(self):
-		super(Item, self).onload()
-
 		self.set_onload('stock_exists', self.stock_ledger_created())
 		self.set_asset_naming_series()
 
@@ -103,8 +89,6 @@
 			self.set_opening_stock()
 
 	def validate(self):
-		super(Item, self).validate()
-
 		if not self.item_name:
 			self.item_name = self.item_code
 
@@ -130,8 +114,6 @@
 		self.validate_attributes()
 		self.validate_variant_attributes()
 		self.validate_variant_based_on_change()
-		self.validate_website_image()
-		self.make_thumbnail()
 		self.validate_fixed_asset()
 		self.validate_retain_sample()
 		self.validate_uom_conversion_factor()
@@ -140,21 +122,17 @@
 		self.validate_item_defaults()
 		self.validate_auto_reorder_enabled_in_stock_settings()
 		self.cant_change()
-		self.update_show_in_website()
 		self.validate_item_tax_net_rate_range()
 		set_item_tax_from_hsn_code(self)
 
 		if not self.is_new():
 			self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
-			self.old_website_item_groups = frappe.db.sql_list("""select item_group
-					from `tabWebsite Item Group`
-					where parentfield='website_item_groups' and parenttype='Item' and parent=%s""", self.name)
 
 	def on_update(self):
 		invalidate_cache_for_item(self)
 		self.update_variants()
 		self.update_item_price()
-		self.update_template_item()
+		self.update_website_item()
 
 	def validate_description(self):
 		'''Clean HTML description if set'''
@@ -216,97 +194,6 @@
 
 				stock_entry.add_comment("Comment", _("Opening Stock"))
 
-	def make_route(self):
-		if not self.route:
-			return cstr(frappe.db.get_value('Item Group', self.item_group,
-					'route')) + '/' + self.scrub((self.item_name or self.item_code) + '-' + random_string(5))
-
-	def validate_website_image(self):
-		"""Validate if the website image is a public file"""
-
-		if frappe.flags.in_import:
-			return
-
-		auto_set_website_image = False
-		if not self.website_image and self.image:
-			auto_set_website_image = True
-			self.website_image = self.image
-
-		if not self.website_image:
-			return
-
-		# find if website image url exists as public
-		file_doc = frappe.get_all("File", filters={
-			"file_url": self.website_image
-		}, fields=["name", "is_private"], order_by="is_private asc", limit_page_length=1)
-
-		if file_doc:
-			file_doc = file_doc[0]
-
-		if not file_doc:
-			if not auto_set_website_image:
-				frappe.msgprint(_("Website Image {0} attached to Item {1} cannot be found").format(self.website_image, self.name))
-
-			self.website_image = None
-
-		elif file_doc.is_private:
-			if not auto_set_website_image:
-				frappe.msgprint(_("Website Image should be a public file or website URL"))
-
-			self.website_image = None
-
-	def make_thumbnail(self):
-		"""Make a thumbnail of `website_image`"""
-
-		if frappe.flags.in_import:
-			return
-
-		import requests.exceptions
-
-		if not self.is_new() and self.website_image != frappe.db.get_value(self.doctype, self.name, "website_image"):
-			self.thumbnail = None
-
-		if self.website_image and not self.thumbnail:
-			file_doc = None
-
-			try:
-				file_doc = frappe.get_doc("File", {
-					"file_url": self.website_image,
-					"attached_to_doctype": "Item",
-					"attached_to_name": self.name
-				})
-			except frappe.DoesNotExistError:
-				# cleanup
-				frappe.local.message_log.pop()
-
-			except requests.exceptions.HTTPError:
-				frappe.msgprint(_("Warning: Invalid attachment {0}").format(self.website_image))
-				self.website_image = None
-
-			except requests.exceptions.SSLError:
-				frappe.msgprint(
-					_("Warning: Invalid SSL certificate on attachment {0}").format(self.website_image))
-				self.website_image = None
-
-			# for CSV import
-			if self.website_image and not file_doc:
-				try:
-					file_doc = frappe.get_doc({
-						"doctype": "File",
-						"file_url": self.website_image,
-						"attached_to_doctype": "Item",
-						"attached_to_name": self.name
-					}).save()
-
-				except IOError:
-					self.website_image = None
-
-			if file_doc:
-				if not file_doc.thumbnail_url:
-					file_doc.make_thumbnail()
-
-				self.thumbnail = file_doc.thumbnail_url
-
 	def validate_fixed_asset(self):
 		if self.is_fixed_asset:
 			if self.is_stock_item:
@@ -330,167 +217,6 @@
 			frappe.throw(_("{0} Retain Sample is based on batch, please check Has Batch No to retain sample of item").format(
 				self.item_code))
 
-	def get_context(self, context):
-		context.show_search = True
-		context.search_link = '/product_search'
-
-		context.parents = get_parent_item_groups(self.item_group)
-		context.body_class = "product-page"
-
-		self.set_variant_context(context)
-		self.set_attribute_context(context)
-		self.set_disabled_attributes(context)
-		self.set_metatags(context)
-		self.set_shopping_cart_data(context)
-
-		return context
-
-	def set_variant_context(self, context):
-		if self.has_variants:
-			context.no_cache = True
-
-			# load variants
-			# also used in set_attribute_context
-			context.variants = frappe.get_all("Item",
-				 filters={"variant_of": self.name, "show_variant_in_website": 1},
-				 order_by="name asc")
-
-			variant = frappe.form_dict.variant
-			if not variant and context.variants:
-				# the case when the item is opened for the first time from its list
-				variant = context.variants[0]
-
-			if variant:
-				context.variant = frappe.get_doc("Item", variant)
-
-				for fieldname in ("website_image", "website_image_alt", "web_long_description", "description",
-										"website_specifications"):
-					if context.variant.get(fieldname):
-						value = context.variant.get(fieldname)
-						if isinstance(value, list):
-							value = [d.as_dict() for d in value]
-
-						context[fieldname] = value
-
-		if self.slideshow:
-			if context.variant and context.variant.slideshow:
-				context.update(get_slideshow(context.variant))
-			else:
-				context.update(get_slideshow(self))
-
-	def set_attribute_context(self, context):
-		if not self.has_variants:
-			return
-
-		attribute_values_available = {}
-		context.attribute_values = {}
-		context.selected_attributes = {}
-
-		# load attributes
-		for v in context.variants:
-			v.attributes = frappe.get_all("Item Variant Attribute",
-				fields=["attribute", "attribute_value"],
-				filters={"parent": v.name})
-			# make a map for easier access in templates
-			v.attribute_map = frappe._dict({})
-			for attr in v.attributes:
-				v.attribute_map[attr.attribute] = attr.attribute_value
-
-			for attr in v.attributes:
-				values = attribute_values_available.setdefault(attr.attribute, [])
-				if attr.attribute_value not in values:
-					values.append(attr.attribute_value)
-
-				if v.name == context.variant.name:
-					context.selected_attributes[attr.attribute] = attr.attribute_value
-
-		# filter attributes, order based on attribute table
-		for attr in self.attributes:
-			values = context.attribute_values.setdefault(attr.attribute, [])
-
-			if cint(frappe.db.get_value("Item Attribute", attr.attribute, "numeric_values")):
-				for val in sorted(attribute_values_available.get(attr.attribute, []), key=flt):
-					values.append(val)
-
-			else:
-				# get list of values defined (for sequence)
-				for attr_value in frappe.db.get_all("Item Attribute Value",
-					fields=["attribute_value"],
-					filters={"parent": attr.attribute}, order_by="idx asc"):
-
-					if attr_value.attribute_value in attribute_values_available.get(attr.attribute, []):
-						values.append(attr_value.attribute_value)
-
-		context.variant_info = json.dumps(context.variants)
-
-	def set_disabled_attributes(self, context):
-		"""Disable selection options of attribute combinations that do not result in a variant"""
-		if not self.attributes or not self.has_variants:
-			return
-
-		context.disabled_attributes = {}
-		attributes = [attr.attribute for attr in self.attributes]
-
-		def find_variant(combination):
-			for variant in context.variants:
-				if len(variant.attributes) < len(attributes):
-					continue
-
-				if "combination" not in variant:
-					ref_combination = []
-
-					for attr in variant.attributes:
-						idx = attributes.index(attr.attribute)
-						ref_combination.insert(idx, attr.attribute_value)
-
-					variant["combination"] = ref_combination
-
-				if not (set(combination) - set(variant["combination"])):
-					# check if the combination is a subset of a variant combination
-					# eg. [Blue, 0.5] is a possible combination if exists [Blue, Large, 0.5]
-					return True
-
-		for i, attr in enumerate(self.attributes):
-			if i == 0:
-				continue
-
-			combination_source = []
-
-			# loop through previous attributes
-			for prev_attr in self.attributes[:i]:
-				combination_source.append([context.selected_attributes.get(prev_attr.attribute)])
-
-			combination_source.append(context.attribute_values[attr.attribute])
-
-			for combination in itertools.product(*combination_source):
-				if not find_variant(combination):
-					context.disabled_attributes.setdefault(attr.attribute, []).append(combination[-1])
-
-	def set_metatags(self, context):
-		context.metatags = frappe._dict({})
-
-		safe_description = frappe.utils.to_markdown(self.description)
-
-		context.metatags.url = frappe.utils.get_url() + '/' + context.route
-
-		if context.website_image:
-			if context.website_image.startswith('http'):
-				url = context.website_image
-			else:
-				url = frappe.utils.get_url() + context.website_image
-			context.metatags.image = url
-
-		context.metatags.description = safe_description[:300]
-
-		context.metatags.title = self.item_name or self.item_code
-
-		context.metatags['og:type'] = 'product'
-		context.metatags['og:site_name'] = 'ERPNext'
-
-	def set_shopping_cart_data(self, context):
-		from erpnext.shopping_cart.product_info import get_product_info_for_website
-		context.shopping_cart = get_product_info_for_website(self.name, skip_quotation_creation=True)
-
 	def add_default_uom_in_conversion_factor_table(self):
 		if not self.is_new() and self.has_value_changed("stock_uom"):
 			self.uoms = []
@@ -507,9 +233,29 @@
 				"conversion_factor": 1
 			})
 
-	def update_show_in_website(self):
-		if self.disabled:
-			self.show_in_website = False
+	def update_website_item(self):
+		"""Update Website Item if change in Item impacts it."""
+		web_item = frappe.db.exists("Website Item", {"item_code": self.item_code})
+
+		if web_item:
+			changed = {}
+			editable_fields = ["item_name", "item_group", "stock_uom", "brand", "description",
+				"disabled"]
+			doc_before_save = self.get_doc_before_save()
+
+			for field in editable_fields:
+				if doc_before_save.get(field) != self.get(field):
+					if field == "disabled":
+						changed["published"] = not self.get(field)
+					else:
+						changed[field] = self.get(field)
+
+			if not changed:
+				return
+
+			web_item_doc = frappe.get_doc("Website Item", web_item)
+			web_item_doc.update(changed)
+			web_item_doc.save()
 
 	def validate_item_tax_net_rate_range(self):
 		for tax in self.get('taxes'):
@@ -602,14 +348,6 @@
 							frappe.throw(_("Barcode {0} is not a valid {1} code").format(
 								item_barcode.barcode, item_barcode.barcode_type), InvalidBarcode)
 
-					if item_barcode.barcode != item_barcode.name:
-						# if barcode is getting updated , the row name has to reset.
-						# Delete previous old row doc and re-enter row as if new to reset name in db.
-						item_barcode.set("__islocal", True)
-						item_barcode_entry_name = item_barcode.name
-						item_barcode.name = None
-						frappe.delete_doc("Item Barcode", item_barcode_entry_name)
-
 	def validate_warehouse_for_reorder(self):
 		'''Validate Reorder level table for duplicate and conditional mandatory'''
 		warehouse = []
@@ -649,7 +387,6 @@
 		)
 
 	def on_trash(self):
-		super(Item, self).on_trash()
 		frappe.db.sql("""delete from tabBin where item_code=%s""", self.name)
 		frappe.db.sql("delete from `tabItem Price` where item_code=%s", self.name)
 		for variant_of in frappe.get_all("Item", filters={"variant_of": self.name}):
@@ -660,15 +397,9 @@
 			frappe.db.set_value("Item", old_name, "item_name", new_name)
 
 		if merge:
-			# Validate properties before merging
-			if not frappe.db.exists("Item", new_name):
-				frappe.throw(_("Item {0} does not exist").format(new_name))
-
-			field_list = ["stock_uom", "is_stock_item", "has_serial_no", "has_batch_no"]
-			new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
-			if new_properties != [cstr(self.get(fld)) for fld in field_list]:
-				frappe.throw(_("To merge, following properties must be same for both items")
-									+ ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list]))
+			self.validate_properties_before_merge(new_name)
+			self.validate_duplicate_product_bundles_before_merge(old_name, new_name)
+			self.validate_duplicate_website_item_before_merge(old_name, new_name)
 
 	def after_rename(self, old_name, new_name, merge):
 		if merge:
@@ -676,9 +407,8 @@
 			frappe.msgprint(_("It can take upto few hours for accurate stock values to be visible after merging items."),
 					indicator="orange", title="Note")
 
-		if self.route:
+		if self.published_in_website:
 			invalidate_cache_for_item(self)
-			clear_cache(self.route)
 
 		frappe.db.set_value("Item", new_name, "item_code", new_name)
 
@@ -718,7 +448,56 @@
 		msg += _("Note: To merge the items, create a separate Stock Reconciliation for the old item {0}").format(
 			frappe.bold(old_name))
 
-		frappe.throw(_(msg), title=_("Merge not allowed"))
+		frappe.throw(_(msg), title=_("Cannot Merge"), exc=DataValidationError)
+
+	def validate_properties_before_merge(self, new_name):
+		# Validate properties before merging
+		if not frappe.db.exists("Item", new_name):
+			frappe.throw(_("Item {0} does not exist").format(new_name))
+
+		field_list = ["stock_uom", "is_stock_item", "has_serial_no", "has_batch_no"]
+		new_properties = [cstr(d) for d in frappe.db.get_value("Item", new_name, field_list)]
+
+		if new_properties != [cstr(self.get(field)) for field in field_list]:
+			msg = _("To merge, following properties must be same for both items")
+			msg += ": \n" + ", ".join([self.meta.get_label(fld) for fld in field_list])
+			frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError)
+
+	def validate_duplicate_product_bundles_before_merge(self, old_name, new_name):
+		"Block merge if both old and new items have product bundles."
+		old_bundle = frappe.get_value("Product Bundle",filters={"new_item_code": old_name})
+		new_bundle = frappe.get_value("Product Bundle",filters={"new_item_code": new_name})
+
+		if old_bundle and new_bundle:
+			bundle_link = get_link_to_form("Product Bundle", old_bundle)
+			old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
+
+			msg = _("Please delete Product Bundle {0}, before merging {1} into {2}").format(
+				bundle_link, old_name, new_name
+			)
+			frappe.throw(msg, title=_("Cannot Merge"), exc=DataValidationError)
+
+	def validate_duplicate_website_item_before_merge(self, old_name, new_name):
+		"""
+			Block merge if both old and new items have website items against them.
+			This is to avoid duplicate website items after merging.
+		"""
+		web_items = frappe.get_all(
+			"Website Item",
+			filters={
+				"item_code": ["in", [old_name, new_name]]
+			},
+			fields=["item_code", "name"])
+
+		if len(web_items) <= 1:
+			return
+
+		old_web_item = [d.get("name") for d in web_items if d.get("item_code") == old_name][0]
+		web_item_link = get_link_to_form("Website Item", old_web_item)
+		old_name, new_name = frappe.bold(old_name), frappe.bold(new_name)
+
+		msg = f"Please delete linked Website Item {frappe.bold(web_item_link)} before merging {old_name} into {new_name}"
+		frappe.throw(_(msg), title=_("Cannot Merge"), exc=DataValidationError)
 
 	def set_last_purchase_rate(self, new_name):
 		last_purchase_rate = get_last_purchase_details(new_name).get("base_net_rate", 0)
@@ -740,16 +519,6 @@
 
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
 
-	@frappe.whitelist()
-	def copy_specification_from_item_group(self):
-		self.set("website_specifications", [])
-		if self.item_group:
-			for label, desc in frappe.db.get_values("Item Website Specification",
-										   {"parent": self.item_group}, ["label", "description"]):
-				row = self.append("website_specifications")
-				row.label = label
-				row.description = desc
-
 	def update_bom_item_desc(self):
 		if self.is_new():
 			return
@@ -773,25 +542,6 @@
 				where item_code = %s and docstatus < 2
 			""", (self.description, self.name))
 
-	def update_template_item(self):
-		"""Set Show in Website for Template Item if True for its Variant"""
-		if not self.variant_of:
-			return
-
-		if self.show_in_website:
-			self.show_variant_in_website = 1
-			self.show_in_website = 0
-
-		if self.show_variant_in_website:
-			# show template
-			template_item = frappe.get_doc("Item", self.variant_of)
-
-			if not template_item.show_in_website:
-				template_item.show_in_website = 1
-				template_item.flags.dont_update_variants = True
-				template_item.flags.ignore_permissions = True
-				template_item.save()
-
 	def validate_item_defaults(self):
 		companies = {row.company for row in self.item_defaults}
 
@@ -1042,47 +792,6 @@
 			if not enabled:
 				frappe.msgprint(msg=_("You have to enable auto re-order in Stock Settings to maintain re-order levels."), title=_("Enable Auto Re-Order"), indicator="orange")
 
-	def create_onboarding_docs(self, args):
-		company = frappe.defaults.get_defaults().get('company') or \
-			frappe.db.get_single_value('Global Defaults', 'default_company')
-
-		for i in range(1, args.get('max_count')):
-			item = args.get('item_' + str(i))
-			if item:
-				default_warehouse = ''
-				default_warehouse = frappe.db.get_value('Warehouse', filters={
-					'warehouse_name': _('Finished Goods'),
-					'company': company
-				})
-
-				try:
-					frappe.get_doc({
-						'doctype': self.doctype,
-						'item_code': item,
-						'item_name': item,
-						'description': item,
-						'show_in_website': 1,
-						'is_sales_item': 1,
-						'is_purchase_item': 1,
-						'is_stock_item': 1,
-						'item_group': _('Products'),
-						'stock_uom': _(args.get('item_uom_' + str(i))),
-						'item_defaults': [{
-							'default_warehouse': default_warehouse,
-							'company': company
-						}]
-					}).insert()
-
-				except frappe.NameError:
-					pass
-				else:
-					if args.get('item_price_' + str(i)):
-						item_price = flt(args.get('item_price_' + str(i)))
-
-						price_list_name = frappe.db.get_value('Price List', {'selling': 1})
-						make_item_price(item, price_list_name, item_price)
-						price_list_name = frappe.db.get_value('Price List', {'buying': 1})
-						make_item_price(item, price_list_name, item_price)
 
 def make_item_price(item, price_list_name, item_price):
 	frappe.get_doc({
@@ -1197,14 +906,9 @@
 
 
 def invalidate_cache_for_item(doc):
+	"""Invalidate Item Group cache and rebuild ItemVariantsCacheManager."""
 	invalidate_cache_for(doc, doc.item_group)
 
-	website_item_groups = list(set((doc.get("old_website_item_groups") or [])
-								+ [d.item_group for d in doc.get({"doctype": "Website Item Group"}) if d.item_group]))
-
-	for item_group in website_item_groups:
-		invalidate_cache_for(doc, item_group)
-
 	if doc.get("old_item_group") and doc.get("old_item_group") != doc.item_group:
 		invalidate_cache_for(doc, doc.old_item_group)
 
@@ -1212,12 +916,14 @@
 
 
 def invalidate_item_variants_cache_for_website(doc):
-	from erpnext.portal.product_configurator.item_variants_cache import ItemVariantsCacheManager
+	"""Rebuild ItemVariantsCacheManager via Item or Website Item."""
+	from erpnext.e_commerce.variant_selector.item_variants_cache import ItemVariantsCacheManager
 
 	item_code = None
-	if doc.has_variants and doc.show_in_website:
-		item_code = doc.name
-	elif doc.variant_of and frappe.db.get_value('Item', doc.variant_of, 'show_in_website'):
+	is_web_item = doc.get("published_in_website") or doc.get("published")
+	if doc.has_variants and is_web_item:
+		item_code = doc.item_code
+	elif doc.variant_of and frappe.db.get_value('Item', doc.variant_of, 'published_in_website'):
 		item_code = doc.variant_of
 
 	if item_code:
@@ -1341,10 +1047,6 @@
 		if publish_progress:
 			frappe.publish_progress(count / total * 100, title=_("Updating Variants..."))
 
-def on_doctype_update():
-	# since route is a Text column, it needs a length for indexing
-	frappe.db.add_index("Item", ["route(500)"])
-
 @erpnext.allow_regional
 def set_item_tax_from_hsn_code(item):
 	pass
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 0957ce0..d7671b1 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -6,6 +6,8 @@
 
 import frappe
 from frappe.test_runner import make_test_objects
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, today
 
 from erpnext.controllers.item_variant import (
 	InvalidItemAttributeValueError,
@@ -14,6 +16,7 @@
 	get_variant,
 )
 from erpnext.stock.doctype.item.item import (
+	DataValidationError,
 	InvalidBarcode,
 	StockExistsForTemplate,
 	get_item_attribute,
@@ -23,7 +26,6 @@
 )
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.get_item_details import get_item_details
-from erpnext.tests.utils import ERPNextTestCase, change_settings
 
 test_ignore = ["BOM"]
 test_dependencies = ["Warehouse", "Item Group", "Item Tax Template", "Brand", "Item Attribute"]
@@ -51,7 +53,7 @@
 
 	return item
 
-class TestItem(ERPNextTestCase):
+class TestItem(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		frappe.flags.attribute_values = None
@@ -387,6 +389,26 @@
 		self.assertTrue(frappe.db.get_value("Bin",
 			{"item_code": "Test Item for Merging 2", "warehouse": "_Test Warehouse 1 - _TC"}))
 
+	def test_item_merging_with_product_bundle(self):
+		from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
+
+		create_item("Test Item Bundle Item 1", is_stock_item=False)
+		create_item("Test Item Bundle Item 2", is_stock_item=False)
+		create_item("Test Item inside Bundle")
+		bundle_items = ["Test Item inside Bundle"]
+
+		# make bundles for both items
+		bundle1 = make_product_bundle("Test Item Bundle Item 1", bundle_items, qty=2)
+		make_product_bundle("Test Item Bundle Item 2", bundle_items, qty=2)
+
+		with self.assertRaises(DataValidationError):
+			frappe.rename_doc("Item", "Test Item Bundle Item 1", "Test Item Bundle Item 2", merge=True)
+
+		bundle1.delete()
+		frappe.rename_doc("Item", "Test Item Bundle Item 1", "Test Item Bundle Item 2", merge=True)
+
+		self.assertFalse(frappe.db.exists("Item", "Test Item Bundle Item 1"))
+
 	def test_uom_conversion_factor(self):
 		if frappe.db.exists('Item', 'Test Item UOM'):
 			frappe.delete_doc('Item', 'Test Item UOM')
@@ -536,7 +558,7 @@
 		"check if index is getting created in db"
 
 		indices = frappe.db.sql("show index from tabItem", as_dict=1)
-		expected_columns = {"item_code", "item_name", "item_group", "route"}
+		expected_columns = {"item_code", "item_name", "item_group"}
 		for index in indices:
 			expected_columns.discard(index.get("Column_name"))
 
@@ -608,6 +630,45 @@
 		item.item_group = "All Item Groups"
 		item.save()  # if item code saved without item_code then series worked
 
+	@change_settings("Stock Settings", {"allow_negative_stock": 0})
+	def test_item_wise_negative_stock(self):
+		""" When global settings are disabled check that item that allows
+		negative stock can still consume material in all known stock
+		transactions that consume inventory."""
+		from erpnext.stock.stock_ledger import is_negative_stock_allowed
+
+		item = make_item("_TestNegativeItemSetting", {"allow_negative_stock": 1, "valuation_rate": 100})
+		self.assertTrue(is_negative_stock_allowed(item_code=item.name))
+
+		self.consume_item_code_with_differet_stock_transactions(item_code=item.name)
+
+	@change_settings("Stock Settings", {"allow_negative_stock": 0})
+	def test_backdated_negative_stock(self):
+		""" same as test above but backdated entries """
+		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+		item = make_item("_TestNegativeItemSetting", {"allow_negative_stock": 1, "valuation_rate": 100})
+
+		# create a future entry so all new entries are backdated
+		make_stock_entry(qty=1, item_code=item.name, target="_Test Warehouse - _TC", posting_date = add_days(today(), 5))
+		self.consume_item_code_with_differet_stock_transactions(item_code=item.name)
+
+
+	def consume_item_code_with_differet_stock_transactions(self, item_code, warehouse="_Test Warehouse - _TC"):
+		from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+		typical_args = {"item_code": item_code, "warehouse": warehouse}
+
+		create_delivery_note(**typical_args)
+		create_sales_invoice(update_stock=1, **typical_args)
+		make_stock_entry(item_code=item_code, source=warehouse, qty=1, purpose="Material Issue")
+		make_stock_entry(item_code=item_code, source=warehouse, target="Stores - _TC", qty=1)
+		# standalone return
+		make_purchase_receipt(is_return=True, qty=-1, **typical_args)
+
+
 
 def set_item_variant_settings(fields):
 	doc = frappe.get_doc('Item Variant Settings')
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 6cec852..91c77d5 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -40,9 +40,7 @@
       "conversion_factor": 10.0
     }
   ],
-  "stock_uom": "_Test UOM",
-  "show_in_website": 1,
-  "website_warehouse": "_Test Warehouse - _TC"
+  "stock_uom": "_Test UOM"
  },
  {
   "description": "_Test Item 2",
@@ -56,8 +54,6 @@
   "item_group": "_Test Item Group",
   "item_name": "_Test Item 2",
   "stock_uom": "_Test UOM",
-  "show_in_website": 1,
-  "website_warehouse": "_Test Warehouse - _TC",
   "gst_hsn_code": "999800",
   "opening_stock": 10,
   "valuation_rate": 100,
@@ -311,8 +307,7 @@
        "warehouse_reorder_level": 20,
        "warehouse_reorder_qty": 20
       }
-  ],
-  "show_in_website": 1
+  ]
  },
  {
   "description": "_Test Item 1",
@@ -344,9 +339,7 @@
     "warehouse_reorder_qty": 20
    }
   ],
-  "stock_uom": "_Test UOM",
-  "show_in_website": 1,
-  "website_warehouse": "_Test Warehouse Group-C1 - _TC"
+  "stock_uom": "_Test UOM"
  },
  {
   "description": "_Test Item With Item Tax Template",
diff --git a/erpnext/stock/doctype/item_alternative/test_item_alternative.py b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
index 3976af4..501c1c1 100644
--- a/erpnext/stock/doctype/item_alternative/test_item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/test_item_alternative.py
@@ -4,6 +4,7 @@
 import json
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import flt
 
 from erpnext.buying.doctype.purchase_order.purchase_order import (
@@ -18,10 +19,9 @@
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
 	create_stock_reconciliation,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestItemAlternative(ERPNextTestCase):
+class TestItemAlternative(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		make_items()
diff --git a/erpnext/stock/doctype/item_attribute/test_item_attribute.py b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
index 0b7ca25..055c22e 100644
--- a/erpnext/stock/doctype/item_attribute/test_item_attribute.py
+++ b/erpnext/stock/doctype/item_attribute/test_item_attribute.py
@@ -6,11 +6,12 @@
 
 test_records = frappe.get_test_records('Item Attribute')
 
+from frappe.tests.utils import FrappeTestCase
+
 from erpnext.stock.doctype.item_attribute.item_attribute import ItemAttributeIncrementError
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestItemAttribute(ERPNextTestCase):
+class TestItemAttribute(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		if frappe.db.exists("Item Attribute", "_Test_Length"):
diff --git a/erpnext/stock/doctype/item_price/test_item_price.py b/erpnext/stock/doctype/item_price/test_item_price.py
index f81770e..6ceba3f 100644
--- a/erpnext/stock/doctype/item_price/test_item_price.py
+++ b/erpnext/stock/doctype/item_price/test_item_price.py
@@ -4,13 +4,13 @@
 
 import frappe
 from frappe.test_runner import make_test_records_for_doctype
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.stock.doctype.item_price.item_price import ItemPriceDuplicateItem
 from erpnext.stock.get_item_details import get_price_list_rate_for, process_args
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestItemPrice(ERPNextTestCase):
+class TestItemPrice(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		frappe.db.sql("delete from `tabItem Price`")
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
index 488920a..5e1f7d5 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
@@ -7,9 +7,8 @@
 
 		const existing_fields = frm.doc.fields.map(row => row.field_name);
 		const exclude_fields = [...existing_fields, "naming_series", "item_code", "item_name",
-			"show_in_website", "show_variant_in_website", "standard_rate", "opening_stock", "image",
-			"variant_of", "valuation_rate", "barcodes", "website_image", "thumbnail",
-			"website_specifiations", "web_long_description", "has_variants", "attributes"];
+			"published_in_website", "standard_rate", "opening_stock", "image",
+			"variant_of", "valuation_rate", "barcodes", "has_variants", "attributes"];
 
 		const exclude_field_types = ['HTML', 'Section Break', 'Column Break', 'Button', 'Read Only'];
 
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
index f63498b..be1517e 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
 
@@ -13,10 +13,9 @@
 	def set_default_fields(self):
 		self.fields = []
 		fields = frappe.get_meta('Item').fields
-		exclude_fields = {"naming_series", "item_code", "item_name", "show_in_website",
-			"show_variant_in_website", "standard_rate", "opening_stock", "image", "description",
+		exclude_fields = {"naming_series", "item_code", "item_name", "published_in_website",
+			"standard_rate", "opening_stock", "image", "description",
 			"variant_of", "valuation_rate", "description", "barcodes",
-			"website_image", "thumbnail", "website_specifiations", "web_long_description",
 			"has_variants", "attributes"}
 
 		for d in fields:
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 9204842..dbaefc1 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -4,19 +4,20 @@
 
 
 import frappe
-from frappe.utils import flt
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import add_to_date, flt, now
 
 from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+from erpnext.accounts.utils import update_gl_entries_after
 from erpnext.assets.doctype.asset.test_asset import create_asset_category, create_fixed_asset_item
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import (
 	get_gl_entries,
 	make_purchase_receipt,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestLandedCostVoucher(ERPNextTestCase):
+class TestLandedCostVoucher(FrappeTestCase):
 	def test_landed_cost_voucher(self):
 		frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
 
@@ -28,7 +29,8 @@
 				"voucher_type": pr.doctype,
 				"voucher_no": pr.name,
 				"item_code": "_Test Item",
-				"warehouse": "Stores - TCP1"
+				"warehouse": "Stores - TCP1",
+				"is_cancelled": 0,
 			},
 			fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
 
@@ -41,14 +43,39 @@
 				"voucher_type": pr.doctype,
 				"voucher_no": pr.name,
 				"item_code": "_Test Item",
-				"warehouse": "Stores - TCP1"
+				"warehouse": "Stores - TCP1",
+				"is_cancelled": 0,
 			},
 			fieldname=["qty_after_transaction", "stock_value"], as_dict=1)
 
 		self.assertEqual(last_sle.qty_after_transaction, last_sle_after_landed_cost.qty_after_transaction)
-
 		self.assertEqual(last_sle_after_landed_cost.stock_value - last_sle.stock_value, 25.0)
 
+		# assert after submit
+		self.assertPurchaseReceiptLCVGLEntries(pr)
+
+		# Mess up cancelled SLE modified timestamp to check
+		# if they aren't effective in any business logic.
+		frappe.db.set_value("Stock Ledger Entry",
+			{
+				"is_cancelled": 1,
+				"voucher_type": pr.doctype,
+				"voucher_no": pr.name
+			},
+			"is_cancelled", 1,
+			modified=add_to_date(now(), hours=1, as_datetime=True, as_string=True)
+		)
+
+		items, warehouses = pr.get_items_and_warehouses()
+		update_gl_entries_after(pr.posting_date, pr.posting_time,
+			warehouses, items, company=pr.company)
+
+		# reassert after reposting
+		self.assertPurchaseReceiptLCVGLEntries(pr)
+
+
+	def assertPurchaseReceiptLCVGLEntries(self, pr):
+
 		gl_entries = get_gl_entries("Purchase Receipt", pr.name)
 
 		self.assertTrue(gl_entries)
@@ -74,8 +101,8 @@
 
 		for gle in gl_entries:
 			if not gle.get('is_cancelled'):
-				self.assertEqual(expected_values[gle.account][0], gle.debit)
-				self.assertEqual(expected_values[gle.account][1], gle.credit)
+				self.assertEqual(expected_values[gle.account][0], gle.debit, msg=f"incorrect debit for {gle.account}")
+				self.assertEqual(expected_values[gle.account][1], gle.credit, msg=f"incorrect credit for {gle.account}")
 
 
 	def test_landed_cost_voucher_against_purchase_invoice(self):
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 103e8d6..51209ac 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -56,14 +56,13 @@
 				if actual_so_qty and (flt(so_items[so_no][item]) + already_indented > actual_so_qty):
 					frappe.throw(_("Material Request of maximum {0} can be made for Item {1} against Sales Order {2}").format(actual_so_qty - already_indented, item, so_no))
 
-	# Validate
-	# ---------------------
 	def validate(self):
 		super(MaterialRequest, self).validate()
 
 		self.validate_schedule_date()
 		self.check_for_on_hold_or_closed_status('Sales Order', 'sales_order')
 		self.validate_uom_is_integer("uom", "qty")
+		self.validate_material_request_type()
 
 		if not self.status:
 			self.status = "Draft"
@@ -83,6 +82,12 @@
 		self.reset_default_field_value("set_warehouse", "items", "warehouse")
 		self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
 
+	def validate_material_request_type(self):
+		""" Validate fields in accordance with selected type """
+
+		if self.material_request_type != "Customer Provided":
+			self.customer = None
+
 	def set_title(self):
 		'''Set title as comma separated list of items'''
 		if not self.title:
@@ -533,6 +538,7 @@
 					"stock_uom": d.stock_uom,
 					"expected_delivery_date": d.schedule_date,
 					"sales_order": d.sales_order,
+					"sales_order_item": d.get("sales_order_item"),
 					"bom_no": get_item_details(d.item_code).bom_no,
 					"material_request": mr.name,
 					"material_request_item": d.name,
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 383b0ae..1cda781 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -6,6 +6,7 @@
 
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import flt, today
 
 from erpnext.stock.doctype.item.test_item import create_item
@@ -15,10 +16,9 @@
 	make_supplier_quotation,
 	raise_work_orders,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestMaterialRequest(ERPNextTestCase):
+class TestMaterialRequest(FrappeTestCase):
 	def test_make_purchase_order(self):
 		mr = frappe.copy_doc(test_records[0]).insert()
 
diff --git a/erpnext/stock/doctype/packed_item/packed_item.json b/erpnext/stock/doctype/packed_item/packed_item.json
index 830d546..d2d4789 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.json
+++ b/erpnext/stock/doctype/packed_item/packed_item.json
@@ -218,8 +218,6 @@
    "label": "Conversion Factor"
   },
   {
-   "fetch_from": "item_code.valuation_rate",
-   "fetch_if_empty": 1,
    "fieldname": "rate",
    "fieldtype": "Currency",
    "in_list_view": 1,
@@ -232,7 +230,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-09-01 15:10:29.646399",
+ "modified": "2022-01-28 16:03:30.780111",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Packed Item",
@@ -240,5 +238,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/packed_item/packed_item.py b/erpnext/stock/doctype/packed_item/packed_item.py
index e4091c4..07c2f1f 100644
--- a/erpnext/stock/doctype/packed_item/packed_item.py
+++ b/erpnext/stock/doctype/packed_item/packed_item.py
@@ -8,187 +8,253 @@
 
 import frappe
 from frappe.model.document import Document
-from frappe.utils import cstr, flt
+from frappe.utils import flt
 
-from erpnext.stock.get_item_details import get_item_details
+from erpnext.stock.get_item_details import get_item_details, get_price_list_rate
 
 
 class PackedItem(Document):
 	pass
 
-def get_product_bundle_items(item_code):
-	return frappe.db.sql("""select t1.item_code, t1.qty, t1.uom, t1.description
-		from `tabProduct Bundle Item` t1, `tabProduct Bundle` t2
-		where t2.new_item_code=%s and t1.parent = t2.name order by t1.idx""", item_code, as_dict=1)
-
-def get_packing_item_details(item, company):
-	return frappe.db.sql("""
-		select i.item_name, i.is_stock_item, i.description, i.stock_uom, id.default_warehouse
-		from `tabItem` i LEFT JOIN `tabItem Default` id ON id.parent=i.name and id.company=%s
-		where i.name = %s""",
-		(company, item), as_dict = 1)[0]
-
-def get_bin_qty(item, warehouse):
-	det = frappe.db.sql("""select actual_qty, projected_qty from `tabBin`
-		where item_code = %s and warehouse = %s""", (item, warehouse), as_dict = 1)
-	return det and det[0] or frappe._dict()
-
-def update_packing_list_item(doc, packing_item_code, qty, main_item_row, description):
-	if doc.amended_from:
-		old_packed_items_map = get_old_packed_item_details(doc.packed_items)
-	else:
-		old_packed_items_map = False
-	item = get_packing_item_details(packing_item_code, doc.company)
-
-	# check if exists
-	exists = 0
-	for d in doc.get("packed_items"):
-		if d.parent_item == main_item_row.item_code and d.item_code == packing_item_code:
-			if d.parent_detail_docname != main_item_row.name:
-				d.parent_detail_docname = main_item_row.name
-
-			pi, exists = d, 1
-			break
-
-	if not exists:
-		pi = doc.append('packed_items', {})
-
-	pi.parent_item = main_item_row.item_code
-	pi.item_code = packing_item_code
-	pi.item_name = item.item_name
-	pi.parent_detail_docname = main_item_row.name
-	pi.uom = item.stock_uom
-	pi.qty = flt(qty)
-	pi.conversion_factor = main_item_row.conversion_factor
-	if description and not pi.description:
-		pi.description = description
-	if not pi.warehouse and not doc.amended_from:
-		pi.warehouse = (main_item_row.warehouse if ((doc.get('is_pos') or item.is_stock_item \
-			or not item.default_warehouse) and main_item_row.warehouse) else item.default_warehouse)
-	if not pi.batch_no and not doc.amended_from:
-		pi.batch_no = cstr(main_item_row.get("batch_no"))
-	if not pi.target_warehouse:
-		pi.target_warehouse = main_item_row.get("target_warehouse")
-	bin = get_bin_qty(packing_item_code, pi.warehouse)
-	pi.actual_qty = flt(bin.get("actual_qty"))
-	pi.projected_qty = flt(bin.get("projected_qty"))
-	if old_packed_items_map and old_packed_items_map.get((packing_item_code, main_item_row.item_code)):
-		pi.batch_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].batch_no
-		pi.serial_no = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].serial_no
-		pi.warehouse = old_packed_items_map.get((packing_item_code, main_item_row.item_code))[0].warehouse
 
 def make_packing_list(doc):
-	"""make packing list for Product Bundle item"""
-	if doc.get("_action") and doc._action == "update_after_submit": return
-
-	parent_items = []
-	for d in doc.get("items"):
-		if frappe.db.get_value("Product Bundle", {"new_item_code": d.item_code}):
-			for i in get_product_bundle_items(d.item_code):
-				update_packing_list_item(doc, i.item_code, flt(i.qty)*flt(d.stock_qty), d, i.description)
-
-			if [d.item_code, d.name] not in parent_items:
-				parent_items.append([d.item_code, d.name])
-
-	cleanup_packing_list(doc, parent_items)
-
-	if frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates"):
-		update_product_bundle_price(doc, parent_items)
-
-def cleanup_packing_list(doc, parent_items):
-	"""Remove all those child items which are no longer present in main item table"""
-	delete_list = []
-	for d in doc.get("packed_items"):
-		if [d.parent_item, d.parent_detail_docname] not in parent_items:
-			# mark for deletion from doclist
-			delete_list.append(d)
-
-	if not delete_list:
-		return doc
-
-	packed_items = doc.get("packed_items")
-	doc.set("packed_items", [])
-
-	for d in packed_items:
-		if d not in delete_list:
-			add_item_to_packing_list(doc, d)
-
-def add_item_to_packing_list(doc, packed_item):
-	doc.append("packed_items", {
-		'parent_item': packed_item.parent_item,
-		'item_code': packed_item.item_code,
-		'item_name': packed_item.item_name,
-		'uom': packed_item.uom,
-		'qty': packed_item.qty,
-		'rate': packed_item.rate,
-		'conversion_factor': packed_item.conversion_factor,
-		'description': packed_item.description,
-		'warehouse': packed_item.warehouse,
-		'batch_no': packed_item.batch_no,
-		'actual_batch_qty': packed_item.actual_batch_qty,
-		'serial_no': packed_item.serial_no,
-		'target_warehouse': packed_item.target_warehouse,
-		'actual_qty': packed_item.actual_qty,
-		'projected_qty': packed_item.projected_qty,
-		'incoming_rate': packed_item.incoming_rate,
-		'prevdoc_doctype': packed_item.prevdoc_doctype,
-		'parent_detail_docname': packed_item.parent_detail_docname
-	})
-
-def update_product_bundle_price(doc, parent_items):
-	"""Updates the prices of Product Bundles based on the rates of the Items in the bundle."""
-
-	if not doc.get('items'):
+	"Make/Update packing list for Product Bundle Item."
+	if doc.get("_action") and doc._action == "update_after_submit":
 		return
 
-	parent_items_index = 0
-	bundle_price = 0
+	parent_items_price, reset = {}, False
+	set_price_from_children = frappe.db.get_single_value("Selling Settings", "editable_bundle_item_rates")
 
-	for bundle_item in doc.get("packed_items"):
-		if parent_items[parent_items_index][0] == bundle_item.parent_item:
-			bundle_item_rate = bundle_item.rate if bundle_item.rate else 0
-			bundle_price += bundle_item.qty * bundle_item_rate
-		else:
-			update_parent_item_price(doc, parent_items[parent_items_index][0], bundle_price)
+	stale_packed_items_table = get_indexed_packed_items_table(doc)
 
-			bundle_item_rate = bundle_item.rate if bundle_item.rate else 0
-			bundle_price = bundle_item.qty * bundle_item_rate
-			parent_items_index += 1
+	reset = reset_packing_list(doc)
 
-	# for the last product bundle
-	if doc.get("packed_items"):
-		update_parent_item_price(doc, parent_items[parent_items_index][0], bundle_price)
+	for item_row in doc.get("items"):
+		if frappe.db.exists("Product Bundle", {"new_item_code": item_row.item_code}):
+			for bundle_item in get_product_bundle_items(item_row.item_code):
+				pi_row = add_packed_item_row(
+					doc=doc, packing_item=bundle_item,
+					main_item_row=item_row, packed_items_table=stale_packed_items_table,
+					reset=reset
+				)
+				item_data = get_packed_item_details(bundle_item.item_code, doc.company)
+				update_packed_item_basic_data(item_row, pi_row, bundle_item, item_data)
+				update_packed_item_stock_data(item_row, pi_row, bundle_item, item_data, doc)
+				update_packed_item_price_data(pi_row, item_data, doc)
+				update_packed_item_from_cancelled_doc(item_row, bundle_item, pi_row, doc)
 
-def update_parent_item_price(doc, parent_item_code, bundle_price):
-	parent_item_doc = doc.get('items', {'item_code': parent_item_code})[0]
+				if set_price_from_children: # create/update bundle item wise price dict
+					update_product_bundle_rate(parent_items_price, pi_row)
 
-	current_parent_item_price = parent_item_doc.amount
-	if current_parent_item_price != bundle_price:
-		parent_item_doc.amount = bundle_price
-		update_parent_item_rate(parent_item_doc, bundle_price)
+	if parent_items_price:
+		set_product_bundle_rate_amount(doc, parent_items_price) # set price in bundle item
 
-def update_parent_item_rate(parent_item_doc, bundle_price):
-	parent_item_doc.rate = bundle_price/parent_item_doc.qty
+def get_indexed_packed_items_table(doc):
+	"""
+		Create dict from stale packed items table like:
+		{(Parent Item 1, Bundle Item 1, ae4b5678): {...}, (key): {value}}
 
-@frappe.whitelist()
-def get_items_from_product_bundle(args):
-	args = json.loads(args)
-	items = []
-	bundled_items = get_product_bundle_items(args["item_code"])
-	for item in bundled_items:
-		args.update({
-			"item_code": item.item_code,
-			"qty": flt(args["quantity"]) * flt(item.qty)
-		})
-		items.append(get_item_details(args))
+		Use: to quickly retrieve/check if row existed in table instead of looping n times
+	"""
+	indexed_table = {}
+	for packed_item in doc.get("packed_items"):
+		key = (packed_item.parent_item, packed_item.item_code, packed_item.parent_detail_docname)
+		indexed_table[key] = packed_item
 
-	return items
+	return indexed_table
+
+def reset_packing_list(doc):
+	"Conditionally reset the table and return if it was reset or not."
+	reset_table = False
+	doc_before_save = doc.get_doc_before_save()
+
+	if doc_before_save:
+		# reset table if:
+		# 1. items were deleted
+		# 2. if bundle item replaced by another item (same no. of items but different items)
+		# we maintain list to track recurring item rows as well
+		items_before_save = [item.item_code for item in doc_before_save.get("items")]
+		items_after_save = [item.item_code for item in doc.get("items")]
+		reset_table = items_before_save != items_after_save
+	else:
+		# reset: if via Update Items OR
+		# if new mapped doc with packed items set (SO -> DN)
+		# (cannot determine action)
+		reset_table = True
+
+	if reset_table:
+		doc.set("packed_items", [])
+	return reset_table
+
+def get_product_bundle_items(item_code):
+	product_bundle = frappe.qb.DocType("Product Bundle")
+	product_bundle_item = frappe.qb.DocType("Product Bundle Item")
+
+	query = (
+		frappe.qb.from_(product_bundle_item)
+		.join(product_bundle).on(product_bundle_item.parent == product_bundle.name)
+		.select(
+			product_bundle_item.item_code,
+			product_bundle_item.qty,
+			product_bundle_item.uom,
+			product_bundle_item.description
+		).where(
+			product_bundle.new_item_code == item_code
+		).orderby(
+			product_bundle_item.idx
+		)
+	)
+	return query.run(as_dict=True)
+
+def add_packed_item_row(doc, packing_item, main_item_row, packed_items_table, reset):
+	"""Add and return packed item row.
+		doc: Transaction document
+		packing_item (dict): Packed Item details
+		main_item_row (dict): Items table row corresponding to packed item
+		packed_items_table (dict): Packed Items table before save (indexed)
+		reset (bool): State if table is reset or preserved as is
+	"""
+	exists, pi_row = False, {}
+
+	# check if row already exists in packed items table
+	key = (main_item_row.item_code, packing_item.item_code, main_item_row.name)
+	if packed_items_table.get(key):
+		pi_row, exists = packed_items_table.get(key), True
+
+	if not exists:
+		pi_row = doc.append('packed_items', {})
+	elif reset: # add row if row exists but table is reset
+		pi_row.idx, pi_row.name = None, None
+		pi_row = doc.append('packed_items', pi_row)
+
+	return pi_row
+
+def get_packed_item_details(item_code, company):
+	item = frappe.qb.DocType("Item")
+	item_default = frappe.qb.DocType("Item Default")
+	query = (
+		frappe.qb.from_(item)
+		.left_join(item_default)
+		.on(
+			(item_default.parent == item.name)
+			& (item_default.company == company)
+		).select(
+			item.item_name, item.is_stock_item,
+			item.description, item.stock_uom,
+			item.valuation_rate,
+			item_default.default_warehouse
+		).where(
+			item.name == item_code
+		)
+	)
+	return query.run(as_dict=True)[0]
+
+def update_packed_item_basic_data(main_item_row, pi_row, packing_item, item_data):
+	pi_row.parent_item = main_item_row.item_code
+	pi_row.parent_detail_docname = main_item_row.name
+	pi_row.item_code = packing_item.item_code
+	pi_row.item_name = item_data.item_name
+	pi_row.uom = item_data.stock_uom
+	pi_row.qty = flt(packing_item.qty) * flt(main_item_row.stock_qty)
+	pi_row.conversion_factor = main_item_row.conversion_factor
+
+	if not pi_row.description:
+		pi_row.description = packing_item.get("description")
+
+def update_packed_item_stock_data(main_item_row, pi_row, packing_item, item_data, doc):
+	# TODO batch_no, actual_batch_qty, incoming_rate
+	if not pi_row.warehouse and not doc.amended_from:
+		fetch_warehouse = (doc.get('is_pos') or item_data.is_stock_item or not item_data.default_warehouse)
+		pi_row.warehouse = (main_item_row.warehouse if (fetch_warehouse and main_item_row.warehouse)
+			else item_data.default_warehouse)
+
+	if not pi_row.target_warehouse:
+		pi_row.target_warehouse = main_item_row.get("target_warehouse")
+
+	bin = get_packed_item_bin_qty(packing_item.item_code, pi_row.warehouse)
+	pi_row.actual_qty = flt(bin.get("actual_qty"))
+	pi_row.projected_qty = flt(bin.get("projected_qty"))
+
+def update_packed_item_price_data(pi_row, item_data, doc):
+	"Set price as per price list or from the Item master."
+	if pi_row.rate:
+		return
+
+	item_doc = frappe.get_cached_doc("Item", pi_row.item_code)
+	row_data = pi_row.as_dict().copy()
+	row_data.update({
+		"company": doc.get("company"),
+		"price_list": doc.get("selling_price_list"),
+		"currency": doc.get("currency")
+	})
+	rate = get_price_list_rate(row_data, item_doc).get("price_list_rate")
+
+	pi_row.rate = rate or item_data.get("valuation_rate") or 0.0
+
+def update_packed_item_from_cancelled_doc(main_item_row, packing_item, pi_row, doc):
+	"Update packed item row details from cancelled doc into amended doc."
+	prev_doc_packed_items_map = None
+	if doc.amended_from:
+		prev_doc_packed_items_map = get_cancelled_doc_packed_item_details(doc.packed_items)
+
+	if prev_doc_packed_items_map and prev_doc_packed_items_map.get((packing_item.item_code, main_item_row.item_code)):
+		prev_doc_row = prev_doc_packed_items_map.get((packing_item.item_code, main_item_row.item_code))
+		pi_row.batch_no = prev_doc_row[0].batch_no
+		pi_row.serial_no = prev_doc_row[0].serial_no
+		pi_row.warehouse = prev_doc_row[0].warehouse
+
+def get_packed_item_bin_qty(item, warehouse):
+	bin_data = frappe.db.get_values(
+		"Bin",
+		fieldname=["actual_qty", "projected_qty"],
+		filters={"item_code": item, "warehouse": warehouse},
+		as_dict=True
+	)
+
+	return bin_data[0] if bin_data else {}
+
+def get_cancelled_doc_packed_item_details(old_packed_items):
+	prev_doc_packed_items_map = {}
+	for items in old_packed_items:
+		prev_doc_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict())
+	return prev_doc_packed_items_map
+
+def update_product_bundle_rate(parent_items_price, pi_row):
+	"""
+		Update the price dict of Product Bundles based on the rates of the Items in the bundle.
+
+		Stucture:
+		{(Bundle Item 1, ae56fgji): 150.0, (Bundle Item 2, bc78fkjo): 200.0}
+	"""
+	key = (pi_row.parent_item, pi_row.parent_detail_docname)
+	rate = parent_items_price.get(key)
+	if not rate:
+		parent_items_price[key] = 0.0
+
+	parent_items_price[key] += flt(pi_row.rate)
+
+def set_product_bundle_rate_amount(doc, parent_items_price):
+	"Set cumulative rate and amount in bundle item."
+	for item in doc.get("items"):
+		bundle_rate = parent_items_price.get((item.item_code, item.name))
+		if bundle_rate and bundle_rate != item.rate:
+			item.rate = bundle_rate
+			item.amount = flt(bundle_rate * item.qty)
 
 def on_doctype_update():
 	frappe.db.add_index("Packed Item", ["item_code", "warehouse"])
 
-def get_old_packed_item_details(old_packed_items):
-	old_packed_items_map = {}
-	for items in old_packed_items:
-		old_packed_items_map.setdefault((items.item_code ,items.parent_item), []).append(items.as_dict())
-	return old_packed_items_map
+
+@frappe.whitelist()
+def get_items_from_product_bundle(row):
+	row, items = json.loads(row), []
+
+	bundled_items = get_product_bundle_items(row["item_code"])
+	for item in bundled_items:
+		row.update({
+			"item_code": item.item_code,
+			"qty": flt(row["quantity"]) * flt(item.qty)
+		})
+		items.append(get_item_details(row))
+
+	return items
diff --git a/erpnext/stock/doctype/packed_item/test_packed_item.py b/erpnext/stock/doctype/packed_item/test_packed_item.py
new file mode 100644
index 0000000..94268a8
--- /dev/null
+++ b/erpnext/stock/doctype/packed_item/test_packed_item.py
@@ -0,0 +1,158 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_to_date, nowdate
+
+from erpnext.selling.doctype.product_bundle.test_product_bundle import make_product_bundle
+from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+
+class TestPackedItem(FrappeTestCase):
+	"Test impact on Packed Items table in various scenarios."
+	@classmethod
+	def setUpClass(cls) -> None:
+		super().setUpClass()
+		cls.bundle = "_Test Product Bundle X"
+		cls.bundle_items = ["_Test Bundle Item 1", "_Test Bundle Item 2"]
+		make_item(cls.bundle, {"is_stock_item": 0})
+		for item in cls.bundle_items:
+			make_item(item, {"is_stock_item": 1})
+
+		make_item("_Test Normal Stock Item", {"is_stock_item": 1})
+
+		make_product_bundle(cls.bundle, cls.bundle_items, qty=2)
+
+	def test_adding_bundle_item(self):
+		"Test impact on packed items if bundle item row is added."
+		so = make_sales_order(item_code = self.bundle, qty=1,
+			do_not_submit=True)
+
+		self.assertEqual(so.items[0].qty, 1)
+		self.assertEqual(len(so.packed_items), 2)
+		self.assertEqual(so.packed_items[0].item_code, self.bundle_items[0])
+		self.assertEqual(so.packed_items[0].qty, 2)
+
+	def test_updating_bundle_item(self):
+		"Test impact on packed items if bundle item row is updated."
+		so = make_sales_order(item_code=self.bundle, qty=1, do_not_submit=True)
+
+		so.items[0].qty = 2 # change qty
+		so.save()
+
+		self.assertEqual(so.packed_items[0].qty, 4)
+		self.assertEqual(so.packed_items[1].qty, 4)
+
+		# change item code to non bundle item
+		so.items[0].item_code = "_Test Normal Stock Item"
+		so.save()
+
+		self.assertEqual(len(so.packed_items), 0)
+
+	def test_recurring_bundle_item(self):
+		"Test impact on packed items if same bundle item is added and removed."
+		so_items = []
+		for qty in [2, 4, 6, 8]:
+			so_items.append({
+				"item_code": self.bundle,
+				"qty": qty,
+				"rate": 400,
+				"warehouse": "_Test Warehouse - _TC"
+			})
+
+		# create SO with recurring bundle item
+		so = make_sales_order(item_list=so_items, do_not_submit=True)
+
+		# check alternate rows for qty
+		self.assertEqual(len(so.packed_items), 8)
+		self.assertEqual(so.packed_items[1].item_code, self.bundle_items[1])
+		self.assertEqual(so.packed_items[1].qty, 4)
+		self.assertEqual(so.packed_items[3].qty, 8)
+		self.assertEqual(so.packed_items[5].qty, 12)
+		self.assertEqual(so.packed_items[7].qty, 16)
+
+		# delete intermediate row (2nd)
+		del so.items[1]
+		so.save()
+
+		# check alternate rows for qty
+		self.assertEqual(len(so.packed_items), 6)
+		self.assertEqual(so.packed_items[1].qty, 4)
+		self.assertEqual(so.packed_items[3].qty, 12)
+		self.assertEqual(so.packed_items[5].qty, 16)
+
+		# delete last row
+		del so.items[2]
+		so.save()
+
+		# check alternate rows for qty
+		self.assertEqual(len(so.packed_items), 4)
+		self.assertEqual(so.packed_items[1].qty, 4)
+		self.assertEqual(so.packed_items[3].qty, 12)
+
+	@change_settings("Selling Settings", {"editable_bundle_item_rates": 1})
+	def test_bundle_item_cumulative_price(self):
+		"Test if Bundle Item rate is cumulative from packed items."
+		so = make_sales_order(item_code=self.bundle, qty=2, do_not_submit=True)
+
+		so.packed_items[0].rate = 150
+		so.packed_items[1].rate = 200
+		so.save()
+
+		self.assertEqual(so.items[0].rate, 350)
+		self.assertEqual(so.items[0].amount, 700)
+
+	def test_newly_mapped_doc_packed_items(self):
+		"Test impact on packed items in newly mapped DN from SO."
+		so_items = []
+		for qty in [2, 4]:
+			so_items.append({
+				"item_code": self.bundle,
+				"qty": qty,
+				"rate": 400,
+				"warehouse": "_Test Warehouse - _TC"
+			})
+
+		# create SO with recurring bundle item
+		so = make_sales_order(item_list=so_items)
+
+		dn = make_delivery_note(so.name)
+		dn.items[1].qty = 3 # change second row qty for inserting doc
+		dn.save()
+
+		self.assertEqual(len(dn.packed_items), 4)
+		self.assertEqual(dn.packed_items[2].qty, 6)
+		self.assertEqual(dn.packed_items[3].qty, 6)
+
+	def test_reposting_packed_items(self):
+		warehouse = "Stores - TCP1"
+		company = "_Test Company with perpetual inventory"
+
+		today = nowdate()
+		yesterday = add_to_date(today, days=-1, as_string=True)
+
+		for item in self.bundle_items:
+			make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, rate=100, posting_date=today)
+
+		so = make_sales_order(item_code = self.bundle, qty=1, company=company, warehouse=warehouse)
+
+		dn = make_delivery_note(so.name)
+		dn.save()
+		dn.submit()
+
+		gles = get_gl_entries(dn.doctype, dn.name)
+		credit_before_repost = sum(gle.credit for gle in gles)
+
+		# backdated stock entry
+		for item in self.bundle_items:
+			make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, rate=200, posting_date=yesterday)
+
+		# assert correct reposting
+		gles = get_gl_entries(dn.doctype, dn.name)
+		credit_after_reposting = sum(gle.credit for gle in gles)
+		self.assertNotEqual(credit_before_repost, credit_after_reposting)
+		self.assertAlmostEqual(credit_after_reposting, 2 * credit_before_repost)
diff --git a/erpnext/stock/doctype/packing_slip/test_packing_slip.py b/erpnext/stock/doctype/packing_slip/test_packing_slip.py
index 5eb6b73..bc405b2 100644
--- a/erpnext/stock/doctype/packing_slip/test_packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/test_packing_slip.py
@@ -4,7 +4,7 @@
 import unittest
 
 # test_records = frappe.get_test_records('Packing Slip')
-from erpnext.tests.utils import ERPNextTestCase
+from frappe.tests.utils import FrappeTestCase
 
 
 class TestPackingSlip(unittest.TestCase):
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 41e3150..f3b6b89 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -6,16 +6,17 @@
 
 test_dependencies = ['Item', 'Sales Invoice', 'Stock Entry', 'Batch']
 
+from frappe.tests.utils import FrappeTestCase
+
 from erpnext.stock.doctype.item.test_item import create_item
 from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation import (
 	EmptyStockReconciliationItemsError,
 )
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestPickList(ERPNextTestCase):
+class TestPickList(FrappeTestCase):
 
 	def test_pick_list_picks_warehouse_for_each_item(self):
 		try:
diff --git a/erpnext/stock/doctype/price_list/price_list.py b/erpnext/stock/doctype/price_list/price_list.py
index 74b823a..8a3172e 100644
--- a/erpnext/stock/doctype/price_list/price_list.py
+++ b/erpnext/stock/doctype/price_list/price_list.py
@@ -36,14 +36,14 @@
 			(self.currency, cint(self.buying), cint(self.selling), self.name))
 
 	def check_impact_on_shopping_cart(self):
-		"Check if Price List currency change impacts Shopping Cart."
-		from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
+		"Check if Price List currency change impacts E Commerce Cart."
+		from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 			validate_cart_settings,
 		)
 
 		doc_before_save = self.get_doc_before_save()
 		currency_changed = self.currency != doc_before_save.currency
-		affects_cart = self.name == frappe.get_cached_value("Shopping Cart Settings", None, "price_list")
+		affects_cart = self.name == frappe.get_cached_value("E Commerce Settings", None, "price_list")
 
 		if currency_changed and affects_cart:
 			validate_cart_settings()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index 112dded..b54a90e 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -106,6 +106,8 @@
   "terms",
   "bill_no",
   "bill_date",
+  "accounting_details_section",
+  "provisional_expense_account",
   "more_info",
   "project",
   "status",
@@ -1144,16 +1146,30 @@
    "label": "Represents Company",
    "options": "Company",
    "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "accounting_details_section",
+   "fieldtype": "Section Break",
+   "label": "Accounting Details"
+  },
+  {
+   "fieldname": "provisional_expense_account",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Provisional Expense Account",
+   "options": "Account"
   }
  ],
  "icon": "fa fa-truck",
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-09-28 13:11:10.181328",
+ "modified": "2022-02-01 11:40:52.690984",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -1214,6 +1230,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "timeline_field": "supplier",
  "title_field": "title",
  "track_changes": 1
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index c97b306..33e40c8 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -8,6 +8,7 @@
 from frappe.model.mapper import get_mapped_doc
 from frappe.utils import cint, flt, getdate, nowdate
 
+import erpnext
 from erpnext.accounts.utils import get_account_currency
 from erpnext.assets.doctype.asset.asset import get_asset_account, is_cwip_accounting_enabled
 from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
@@ -112,6 +113,7 @@
 		self.validate_uom_is_integer("uom", ["qty", "received_qty"])
 		self.validate_uom_is_integer("stock_uom", "stock_qty")
 		self.validate_cwip_accounts()
+		self.validate_provisional_expense_account()
 
 		self.check_on_hold_or_closed_status()
 
@@ -133,6 +135,15 @@
 					company = self.company)
 				break
 
+	def validate_provisional_expense_account(self):
+		provisional_accounting_for_non_stock_items = \
+			cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
+
+		if provisional_accounting_for_non_stock_items:
+			default_provisional_account = self.get_company_default("default_provisional_account")
+			if not self.provisional_expense_account:
+				self.provisional_expense_account = default_provisional_account
+
 	def validate_with_previous_doc(self):
 		super(PurchaseReceipt, self).validate_with_previous_doc({
 			"Purchase Order": {
@@ -258,13 +269,15 @@
 			get_purchase_document_details,
 		)
 
-		stock_rbnb = self.get_company_default("stock_received_but_not_billed")
-		landed_cost_entries = get_item_account_wise_additional_cost(self.name)
-		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
-		auto_accounting_for_non_stock_items = cint(frappe.db.get_value('Company', self.company, 'enable_perpetual_inventory_for_non_stock_items'))
+		if erpnext.is_perpetual_inventory_enabled(self.company):
+			stock_rbnb = self.get_company_default("stock_received_but_not_billed")
+			landed_cost_entries = get_item_account_wise_additional_cost(self.name)
+			expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
 
 		warehouse_with_no_account = []
 		stock_items = self.get_stock_items()
+		provisional_accounting_for_non_stock_items = \
+				cint(frappe.db.get_value('Company', self.company, 'enable_provisional_accounting_for_non_stock_items'))
 
 		exchange_rate_map, net_rate_map = get_purchase_document_details(self)
 
@@ -273,10 +286,7 @@
 				if warehouse_account.get(d.warehouse):
 					stock_value_diff = frappe.db.get_value("Stock Ledger Entry",
 						{"voucher_type": "Purchase Receipt", "voucher_no": self.name,
-						"voucher_detail_no": d.name, "warehouse": d.warehouse}, "stock_value_difference")
-
-					if not stock_value_diff:
-						continue
+						"voucher_detail_no": d.name, "warehouse": d.warehouse, "is_cancelled": 0}, "stock_value_difference")
 
 					warehouse_account_name = warehouse_account[d.warehouse]["account"]
 					warehouse_account_currency = warehouse_account[d.warehouse]["account_currency"]
@@ -422,43 +432,58 @@
 				elif d.warehouse not in warehouse_with_no_account or \
 					d.rejected_warehouse not in warehouse_with_no_account:
 						warehouse_with_no_account.append(d.warehouse)
-			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
-				service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
-				credit_currency = get_account_currency(service_received_but_not_billed_account)
-				debit_currency = get_account_currency(d.expense_account)
-				remarks = self.get("remarks") or _("Accounting Entry for Service")
-
-				self.add_gl_entry(
-					gl_entries=gl_entries,
-					account=service_received_but_not_billed_account,
-					cost_center=d.cost_center,
-					debit=0.0,
-					credit=d.amount,
-					remarks=remarks,
-					against_account=d.expense_account,
-					account_currency=credit_currency,
-					project=d.project,
-					voucher_detail_no=d.name, item=d)
-
-				self.add_gl_entry(
-					gl_entries=gl_entries,
-					account=d.expense_account,
-					cost_center=d.cost_center,
-					debit=d.amount,
-					credit=0.0,
-					remarks=remarks,
-					against_account=service_received_but_not_billed_account,
-					account_currency = debit_currency,
-					project=d.project,
-					voucher_detail_no=d.name,
-					item=d)
+			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and provisional_accounting_for_non_stock_items:
+				self.add_provisional_gl_entry(d, gl_entries, self.posting_date)
 
 		if warehouse_with_no_account:
 			frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
 				"\n".join(warehouse_with_no_account))
 
+	def add_provisional_gl_entry(self, item, gl_entries, posting_date, reverse=0):
+		provisional_expense_account = self.get('provisional_expense_account')
+		credit_currency = get_account_currency(provisional_expense_account)
+		debit_currency = get_account_currency(item.expense_account)
+		expense_account = item.expense_account
+		remarks = self.get("remarks") or _("Accounting Entry for Service")
+		multiplication_factor = 1
+
+		if reverse:
+			multiplication_factor = -1
+			expense_account = frappe.db.get_value('Purchase Receipt Item', {'name': item.get('pr_detail')}, ['expense_account'])
+
+		self.add_gl_entry(
+			gl_entries=gl_entries,
+			account=provisional_expense_account,
+			cost_center=item.cost_center,
+			debit=0.0,
+			credit=multiplication_factor * item.amount,
+			remarks=remarks,
+			against_account=expense_account,
+			account_currency=credit_currency,
+			project=item.project,
+			voucher_detail_no=item.name,
+			item=item,
+			posting_date=posting_date)
+
+		self.add_gl_entry(
+			gl_entries=gl_entries,
+			account=expense_account,
+			cost_center=item.cost_center,
+			debit=multiplication_factor * item.amount,
+			credit=0.0,
+			remarks=remarks,
+			against_account=provisional_expense_account,
+			account_currency = debit_currency,
+			project=item.project,
+			voucher_detail_no=item.name,
+			item=item,
+			posting_date=posting_date)
+
 	def make_tax_gl_entries(self, gl_entries):
-		expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+
+		if erpnext.is_perpetual_inventory_enabled(self.company):
+			expenses_included_in_valuation = self.get_company_default("expenses_included_in_valuation")
+
 		negative_expense_to_be_booked = sum([flt(d.item_tax_amount) for d in self.get('items')])
 		# Cost center-wise amount breakup for other charges included for valuation
 		valuation_tax = {}
@@ -515,7 +540,8 @@
 
 	def add_gl_entry(self, gl_entries, account, cost_center, debit, credit, remarks, against_account,
 		debit_in_account_currency=None, credit_in_account_currency=None, account_currency=None,
-		project=None, voucher_detail_no=None, item=None):
+		project=None, voucher_detail_no=None, item=None, posting_date=None):
+
 		gl_entry = {
 			"account": account,
 			"cost_center": cost_center,
@@ -534,6 +560,9 @@
 		if credit_in_account_currency:
 			gl_entry.update({"credit_in_account_currency": credit_in_account_currency})
 
+		if posting_date:
+			gl_entry.update({"posting_date": posting_date})
+
 		gl_entries.append(self.get_gl_dict(gl_entry, item=item))
 
 	def get_asset_gl_entry(self, gl_entries):
@@ -562,6 +591,7 @@
 		# debit cwip account
 		debit_in_account_currency = (base_asset_amount
 			if cwip_account_currency == self.company_currency else asset_amount)
+
 		self.add_gl_entry(
 			gl_entries=gl_entries,
 			account=cwip_account,
@@ -577,6 +607,7 @@
 		# credit arbnb account
 		credit_in_account_currency = (base_asset_amount
 			if asset_rbnb_currency == self.company_currency else asset_amount)
+
 		self.add_gl_entry(
 			gl_entries=gl_entries,
 			account=arbnb_account,
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
index 77711de..4029f0c 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt_list.js
@@ -13,5 +13,13 @@
 		} else if (flt(doc.grand_total) === 0 || flt(doc.per_billed, 2) === 100) {
 			return [__("Completed"), "green", "per_billed,=,100"];
 		}
+	},
+
+	onload: function(listview) {
+
+		listview.page.add_action_item(__("Purchase Invoice"), ()=>{
+			erpnext.bulk_transaction_processing.create(listview, "Purchase Receipt", "Purchase Invoice");
+		});
 	}
+
 };
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 2909a2d..a24acb1 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -4,8 +4,10 @@
 
 import json
 import unittest
+from collections import defaultdict
 
 import frappe
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import add_days, cint, cstr, flt, today
 
 import erpnext
@@ -16,10 +18,9 @@
 from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError, get_serial_nos
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.stock_ledger import SerialNoExistsInFutureTransaction
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestPurchaseReceipt(ERPNextTestCase):
+class TestPurchaseReceipt(FrappeTestCase):
 	def setUp(self):
 		frappe.db.set_value("Buying Settings", None, "allow_multiple_items", 1)
 
@@ -1312,58 +1313,6 @@
 		self.assertEqual(pr.status, "To Bill")
 		self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
 
-	def test_service_item_purchase_with_perpetual_inventory(self):
-		company = '_Test Company with perpetual inventory'
-		service_item = '_Test Non Stock Item'
-
-		before_test_value = frappe.db.get_value(
-			'Company', company, 'enable_perpetual_inventory_for_non_stock_items'
-		)
-		frappe.db.set_value(
-			'Company', company,
-			'enable_perpetual_inventory_for_non_stock_items', 1
-		)
-		srbnb_account = 'Stock Received But Not Billed - TCP1'
-		frappe.db.set_value(
-			'Company', company,
-			'service_received_but_not_billed', srbnb_account
-		)
-
-		pr = make_purchase_receipt(
-			company=company, item=service_item,
-			warehouse='Finished Goods - TCP1', do_not_save=1
-		)
-		item_row_with_diff_rate = frappe.copy_doc(pr.items[0])
-		item_row_with_diff_rate.rate = 100
-		pr.append('items', item_row_with_diff_rate)
-
-		pr.save()
-		pr.submit()
-
-		item_one_gl_entry = frappe.db.get_all("GL Entry", {
-			'voucher_type': pr.doctype,
-			'voucher_no': pr.name,
-			'account': srbnb_account,
-			'voucher_detail_no': pr.items[0].name
-		}, pluck="name")
-
-		item_two_gl_entry = frappe.db.get_all("GL Entry", {
-			'voucher_type': pr.doctype,
-			'voucher_no': pr.name,
-			'account': srbnb_account,
-			'voucher_detail_no': pr.items[1].name
-		}, pluck="name")
-
-		# check if the entries are not merged into one
-		# seperate entries should be made since voucher_detail_no is different
-		self.assertEqual(len(item_one_gl_entry), 1)
-		self.assertEqual(len(item_two_gl_entry), 1)
-
-		frappe.db.set_value(
-			'Company', company,
-			'enable_perpetual_inventory_for_non_stock_items', before_test_value
-		)
-
 	def test_purchase_receipt_with_exchange_rate_difference(self):
 		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import (
 			make_purchase_receipt as create_purchase_receipt,
@@ -1439,6 +1388,36 @@
 
 		automatically_fetch_payment_terms(enable=0)
 
+	@change_settings("Stock Settings", {"allow_negative_stock": 1})
+	def test_neg_to_positive(self):
+		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+		item_code = "_TestNegToPosItem"
+		warehouse = "Stores - TCP1"
+		company = "_Test Company with perpetual inventory"
+		account = "Stock Received But Not Billed - TCP1"
+
+		make_item(item_code)
+		se = make_stock_entry(item_code=item_code, from_warehouse=warehouse, qty=50, do_not_save=True, rate=0)
+		se.items[0].allow_zero_valuation_rate = 1
+		se.save()
+		se.submit()
+
+		pr = make_purchase_receipt(
+			qty=50,
+			rate=1,
+			item_code=item_code,
+			warehouse=warehouse,
+			get_taxes_and_charges=True,
+			company=company,
+		)
+		gles = get_gl_entries(pr.doctype, pr.name)
+
+		for gle in gles:
+			if gle.account == account:
+				self.assertEqual(gle.credit, 50)
+
+
 def get_sl_entries(voucher_type, voucher_no):
 	return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
 		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
@@ -1561,6 +1540,7 @@
 		"conversion_factor": args.conversion_factor or 1.0,
 		"stock_qty": flt(qty) * (flt(args.conversion_factor) or 1.0),
 		"serial_no": args.serial_no,
+		"batch_no": args.batch_no,
 		"stock_uom": args.stock_uom or "_Test UOM",
 		"uom": uom,
 		"cost_center": args.cost_center or frappe.get_cached_value('Company',  pr.company,  'cost_center'),
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 30ea1c3..e5994b2 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -976,7 +976,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-11-15 15:46:10.591600",
+ "modified": "2022-02-01 11:32:27.980524",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
@@ -985,5 +985,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/putaway_rule/putaway_rule.py b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
index 523ba12..4e472a9 100644
--- a/erpnext/stock/doctype/putaway_rule/putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/putaway_rule.py
@@ -9,7 +9,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import cint, floor, flt, nowdate
+from frappe.utils import cint, cstr, floor, flt, nowdate
 
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.utils import get_stock_balance
@@ -142,11 +142,44 @@
 	if items_not_accomodated:
 		show_unassigned_items_message(items_not_accomodated)
 
-	items[:] = updated_table if updated_table else items # modify items table
+	if updated_table and _items_changed(items, updated_table, doctype):
+		items[:] = updated_table
+		frappe.msgprint(_("Applied putaway rules."), alert=True)
 
 	if sync and json.loads(sync): # sync with client side
 		return items
 
+def _items_changed(old, new, doctype: str) -> bool:
+	""" Check if any items changed by application of putaway rules.
+
+		If not, changing item table can have side effects since `name` items also changes.
+	"""
+	if len(old) != len(new):
+		return True
+
+	old = [frappe._dict(item) if isinstance(item, dict) else item for item in old]
+
+	if doctype == "Stock Entry":
+		compare_keys = ("item_code", "t_warehouse", "transfer_qty", "serial_no")
+		sort_key = lambda item: (item.item_code, cstr(item.t_warehouse),  # noqa
+				flt(item.transfer_qty), cstr(item.serial_no))
+	else:
+		# purchase receipt / invoice
+		compare_keys = ("item_code", "warehouse", "stock_qty", "received_qty", "serial_no")
+		sort_key = lambda item: (item.item_code, cstr(item.warehouse),  # noqa
+				flt(item.stock_qty), flt(item.received_qty), cstr(item.serial_no))
+
+	old_sorted = sorted(old, key=sort_key)
+	new_sorted = sorted(new, key=sort_key)
+
+	# Once sorted by all relevant keys both tables should align if they are same.
+	for old_item, new_item in zip(old_sorted, new_sorted):
+		for key in compare_keys:
+			if old_item.get(key) != new_item.get(key):
+				return True
+	return False
+
+
 def get_ordered_putaway_rules(item_code, company, source_warehouse=None):
 	"""Returns an ordered list of putaway rules to apply on an item."""
 	filters = {
diff --git a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
index bd4d811..4e8d71f 100644
--- a/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
+++ b/erpnext/stock/doctype/putaway_rule/test_putaway_rule.py
@@ -2,6 +2,7 @@
 # See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.stock.doctype.batch.test_batch import make_new_batch
 from erpnext.stock.doctype.item.test_item import make_item
@@ -9,10 +10,9 @@
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.get_item_details import get_conversion_factor
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestPutawayRule(ERPNextTestCase):
+class TestPutawayRule(FrappeTestCase):
 	def setUp(self):
 		if not frappe.db.exists("Item", "_Rice"):
 			make_item("_Rice", {
@@ -35,6 +35,18 @@
 			new_uom.uom_name = "Bag"
 			new_uom.save()
 
+	def assertUnchangedItemsOnResave(self, doc):
+		""" Check if same items remain even after reapplication of rules.
+
+			This is required since some business logic like subcontracting
+			depends on `name` of items to be same if item isn't changed.
+		"""
+		doc.reload()
+		old_items = {d.name for d in doc.items}
+		doc.save()
+		new_items = {d.name for d in doc.items}
+		self.assertSetEqual(old_items, new_items)
+
 	def test_putaway_rules_priority(self):
 		"""Test if rule is applied by priority, irrespective of free space."""
 		rule_1 = create_putaway_rule(item_code="_Rice", warehouse=self.warehouse_1, capacity=200,
@@ -50,6 +62,8 @@
 		self.assertEqual(pr.items[1].qty, 100)
 		self.assertEqual(pr.items[1].warehouse, self.warehouse_2)
 
+		self.assertUnchangedItemsOnResave(pr)
+
 		pr.delete()
 		rule_1.delete()
 		rule_2.delete()
@@ -162,6 +176,8 @@
 		# leftover space was for 500 kg (0.5 Bag)
 		# Since Bag is a whole UOM, 1(out of 2) Bag will be unassigned
 
+		self.assertUnchangedItemsOnResave(pr)
+
 		pr.delete()
 		rule_1.delete()
 		rule_2.delete()
@@ -196,6 +212,8 @@
 		self.assertEqual(pr.items[1].warehouse, self.warehouse_1)
 		self.assertEqual(pr.items[1].putaway_rule, rule_1.name)
 
+		self.assertUnchangedItemsOnResave(pr)
+
 		pr.delete()
 		rule_1.delete()
 
@@ -239,6 +257,8 @@
 		self.assertEqual(stock_entry_item.qty, 100) # unassigned 100 out of 200 Kg
 		self.assertEqual(stock_entry_item.putaway_rule, rule_2.name)
 
+		self.assertUnchangedItemsOnResave(stock_entry)
+
 		stock_entry.delete()
 		rule_1.delete()
 		rule_2.delete()
@@ -294,6 +314,8 @@
 		self.assertEqual(stock_entry.items[2].qty, 200)
 		self.assertEqual(stock_entry.items[2].putaway_rule, rule_2.name)
 
+		self.assertUnchangedItemsOnResave(stock_entry)
+
 		stock_entry.delete()
 		rule_1.delete()
 		rule_2.delete()
@@ -344,6 +366,8 @@
 		self.assertEqual(stock_entry.items[1].serial_no, "\n".join(serial_nos[3:]))
 		self.assertEqual(stock_entry.items[1].batch_no, "BOTTL-BATCH-1")
 
+		self.assertUnchangedItemsOnResave(stock_entry)
+
 		stock_entry.delete()
 		pr.cancel()
 		rule_1.delete()
@@ -366,6 +390,8 @@
 		self.assertEqual(stock_entry_item.qty, 100)
 		self.assertEqual(stock_entry_item.putaway_rule, rule_1.name)
 
+		self.assertUnchangedItemsOnResave(stock_entry)
+
 		stock_entry.delete()
 		rule_1.delete()
 		rule_2.delete()
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 308c628..601ca05 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -2,6 +2,7 @@
 # See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import nowdate
 
 from erpnext.controllers.stock_controller import (
@@ -13,12 +14,11 @@
 from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 from erpnext.stock.doctype.item.test_item import create_item
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
-from erpnext.tests.utils import ERPNextTestCase
 
 # test_records = frappe.get_test_records('Quality Inspection')
 
 
-class TestQualityInspection(ERPNextTestCase):
+class TestQualityInspection(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		create_item("_Test Item with QA")
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
index cd7e63b..0ba97d5 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "autoname": "REPOST-ITEM-VAL-.######",
- "creation": "2020-10-22 22:27:07.742161",
+ "creation": "2022-01-11 15:03:38.273179",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
@@ -129,7 +129,7 @@
    "reqd": 1
   },
   {
-   "default": "0",
+   "default": "1",
    "fieldname": "allow_negative_stock",
    "fieldtype": "Check",
    "label": "Allow Negative Stock"
@@ -177,7 +177,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-24 02:18:10.524560",
+ "modified": "2022-01-18 10:57:33.450907",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Repost Item Valuation",
@@ -227,5 +227,6 @@
   }
  ],
  "sort_field": "modified",
- "sort_order": "DESC"
-}
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index fb3b355..977d470 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -13,7 +13,7 @@
 	check_if_stock_and_account_balance_synced,
 	update_gl_entries_after,
 )
-from erpnext.stock.stock_ledger import repost_future_sle
+from erpnext.stock.stock_ledger import get_items_to_be_repost, repost_future_sle
 
 
 class RepostItemValuation(Document):
@@ -27,8 +27,7 @@
 			self.item_code = None
 			self.warehouse = None
 
-		self.allow_negative_stock = self.allow_negative_stock or \
-				cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+		self.allow_negative_stock = 1
 
 	def set_company(self):
 		if self.based_on == "Transaction":
@@ -139,13 +138,20 @@
 
 	if doc.based_on == 'Transaction':
 		ref_doc = frappe.get_doc(doc.voucher_type, doc.voucher_no)
-		items, warehouses = ref_doc.get_items_and_warehouses()
+		doc_items, doc_warehouses = ref_doc.get_items_and_warehouses()
+
+		sles = get_items_to_be_repost(doc.voucher_type, doc.voucher_no)
+		sle_items = [sle.item_code for sle in sles]
+		sle_warehouse = [sle.warehouse for sle in sles]
+
+		items = list(set(doc_items).union(set(sle_items)))
+		warehouses = list(set(doc_warehouses).union(set(sle_warehouse)))
 	else:
 		items = [doc.item_code]
 		warehouses = [doc.warehouse]
 
 	update_gl_entries_after(doc.posting_date, doc.posting_time,
-		warehouses, items, company=doc.company)
+		for_warehouses=warehouses, for_items=items, company=doc.company)
 
 def notify_error_to_stock_managers(doc, traceback):
 	recipients = get_users_with_role("Stock Manager")
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index ee55af3..ee08e38 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -465,6 +465,13 @@
 	return [s.strip() for s in cstr(serial_no).strip().upper().replace(',', '\n').split('\n')
 		if s.strip()]
 
+def clean_serial_no_string(serial_no: str) -> str:
+	if not serial_no:
+		return ""
+
+	serial_no_list = get_serial_nos(serial_no)
+	return "\n".join(serial_no_list)
+
 def update_args_for_serial_no(serial_no_doc, serial_no, args, is_new=False):
 	for field in ["item_code", "work_order", "company", "batch_no", "supplier", "location"]:
 		if args.get(field):
diff --git a/erpnext/stock/doctype/serial_no/test_serial_no.py b/erpnext/stock/doctype/serial_no/test_serial_no.py
index f8cea71..057a7d4 100644
--- a/erpnext/stock/doctype/serial_no/test_serial_no.py
+++ b/erpnext/stock/doctype/serial_no/test_serial_no.py
@@ -18,11 +18,12 @@
 test_dependencies = ["Item"]
 test_records = frappe.get_test_records('Serial No')
 
+from frappe.tests.utils import FrappeTestCase
+
 from erpnext.stock.doctype.serial_no.serial_no import *
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestSerialNo(ERPNextTestCase):
+class TestSerialNo(FrappeTestCase):
 
 	def tearDown(self):
 		frappe.db.rollback()
diff --git a/erpnext/stock/doctype/shipment/test_shipment.py b/erpnext/stock/doctype/shipment/test_shipment.py
index afe8218..317abb6 100644
--- a/erpnext/stock/doctype/shipment/test_shipment.py
+++ b/erpnext/stock/doctype/shipment/test_shipment.py
@@ -4,12 +4,12 @@
 from datetime import date, timedelta
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.stock.doctype.delivery_note.delivery_note import make_shipment
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestShipment(ERPNextTestCase):
+class TestShipment(FrappeTestCase):
 	def test_shipment_from_delivery_note(self):
 		delivery_note = create_test_delivery_note()
 		delivery_note.submit()
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index c4b8131..324ca7a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -425,6 +425,7 @@
 			'posting_time'		: frm.doc.posting_time,
 			'warehouse'			: cstr(item.s_warehouse) || cstr(item.t_warehouse),
 			'serial_no'			: item.serial_no,
+			'batch_no'          : item.batch_no,
 			'company'			: frm.doc.company,
 			'qty'				: item.s_warehouse ? -1*flt(item.transfer_qty) : flt(item.transfer_qty),
 			'voucher_type'		: frm.doc.doctype,
@@ -457,6 +458,7 @@
 						'warehouse': cstr(child.s_warehouse) || cstr(child.t_warehouse),
 						'transfer_qty': child.transfer_qty,
 						'serial_no': child.serial_no,
+						'batch_no': child.batch_no,
 						'qty': child.s_warehouse ? -1* child.transfer_qty : child.transfer_qty,
 						'posting_date': frm.doc.posting_date,
 						'posting_time': frm.doc.posting_time,
@@ -627,6 +629,12 @@
 		frm.events.set_serial_no(frm, cdt, cdn, () => {
 			frm.events.get_warehouse_details(frm, cdt, cdn);
 		});
+
+		// set allow_zero_valuation_rate to 0 if s_warehouse is selected.
+		let item = frappe.get_doc(cdt, cdn);
+		if (item.s_warehouse) {
+			item.allow_zero_valuation_rate = 0;
+		}
 	},
 
 	t_warehouse: function(frm, cdt, cdn) {
@@ -680,6 +688,7 @@
 				'warehouse'			: cstr(d.s_warehouse) || cstr(d.t_warehouse),
 				'transfer_qty'		: d.transfer_qty,
 				'serial_no'		: d.serial_no,
+				'batch_no'      : d.batch_no,
 				'bom_no'		: d.bom_no,
 				'expense_account'	: d.expense_account,
 				'cost_center'		: d.cost_center,
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 2f37778..c38dfaa 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -8,7 +8,6 @@
  "engine": "InnoDB",
  "field_order": [
   "items_section",
-  "title",
   "naming_series",
   "stock_entry_type",
   "outgoing_stock_entry",
@@ -84,14 +83,6 @@
    "oldfieldtype": "Section Break"
   },
   {
-   "fieldname": "title",
-   "fieldtype": "Data",
-   "hidden": 1,
-   "label": "Title",
-   "no_copy": 1,
-   "print_hide": 1
-  },
-  {
    "fieldname": "naming_series",
    "fieldtype": "Select",
    "label": "Series",
@@ -353,9 +344,9 @@
   },
   {
    "fieldname": "scan_barcode",
-   "options": "Barcode",
    "fieldtype": "Data",
-   "label": "Scan Barcode"
+   "label": "Scan Barcode",
+   "options": "Barcode"
   },
   {
    "allow_bulk_edit": 1,
@@ -628,10 +619,11 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-08-20 19:19:31.514846",
+ "modified": "2022-02-07 12:55:14.614077",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -698,6 +690,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
- "title_field": "title",
+ "states": [],
+ "title_field": "stock_entry_type",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index a61b319..99cf4de 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -76,7 +76,6 @@
 
 		self.validate_posting_time()
 		self.validate_purpose()
-		self.set_title()
 		self.validate_item()
 		self.validate_customer_provided_item()
 		self.validate_qty()
@@ -434,9 +433,10 @@
 				)
 
 	def set_actual_qty(self):
-		allow_negative_stock = cint(frappe.db.get_value("Stock Settings", None, "allow_negative_stock"))
+		from erpnext.stock.stock_ledger import is_negative_stock_allowed
 
 		for d in self.get('items'):
+			allow_negative_stock = is_negative_stock_allowed(item_code=d.item_code)
 			previous_sle = get_previous_sle({
 				"item_code": d.item_code,
 				"warehouse": d.s_warehouse or d.t_warehouse,
@@ -510,7 +510,7 @@
 				d.basic_rate = get_valuation_rate(d.item_code, d.t_warehouse,
 					self.doctype, self.name, d.allow_zero_valuation_rate,
 					currency=erpnext.get_company_currency(self.company), company=self.company,
-					raise_error_if_no_rate=raise_error_if_no_rate)
+					raise_error_if_no_rate=raise_error_if_no_rate, batch_no=d.batch_no)
 
 			d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
 			if d.is_process_loss:
@@ -541,6 +541,7 @@
 			"posting_time": self.posting_time,
 			"qty": item.s_warehouse and -1*flt(item.transfer_qty) or flt(item.transfer_qty),
 			"serial_no": item.serial_no,
+			"batch_no": item.batch_no,
 			"voucher_type": self.doctype,
 			"voucher_no": self.name,
 			"company": self.company,
@@ -1116,7 +1117,7 @@
 		self.set_actual_qty()
 		self.update_items_for_process_loss()
 		self.validate_customer_provided_item()
-		self.calculate_rate_and_amount()
+		self.calculate_rate_and_amount(raise_error_if_no_rate=False)
 
 	def set_scrap_items(self):
 		if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]:
@@ -1445,14 +1446,15 @@
 							qty = req_qty_each * flt(self.fg_completed_qty)
 
 			elif backflushed_materials.get(item.item_code):
+				precision = frappe.get_precision("Stock Entry Detail", "qty")
 				for d in backflushed_materials.get(item.item_code):
-					if d.get(item.warehouse):
+					if d.get(item.warehouse) > 0:
 						if (qty > req_qty):
-							qty = (qty/trans_qty) * flt(self.fg_completed_qty)
+							qty = ((flt(qty, precision) - flt(d.get(item.warehouse), precision))
+								/ (flt(trans_qty, precision) - flt(produced_qty, precision))
+							) * flt(self.fg_completed_qty)
 
-						if consumed_qty and frappe.db.get_single_value("Manufacturing Settings",
-							"material_consumption"):
-							qty -= consumed_qty
+							d[item.warehouse] -= qty
 
 			if cint(frappe.get_cached_value('UOM', item.stock_uom, 'must_be_whole_number')):
 				qty = frappe.utils.ceil(qty)
@@ -1672,6 +1674,8 @@
 			for d in self.get("items"):
 				item_code = d.get('original_item') or d.get('item_code')
 				reserve_warehouse = item_wh.get(item_code)
+				if not (reserve_warehouse and item_code):
+					continue
 				stock_bin = get_bin(item_code, reserve_warehouse)
 				stock_bin.update_reserved_qty_for_sub_contracting()
 
@@ -1832,14 +1836,6 @@
 
 		return sorted(list(set(get_serial_nos(self.pro_doc.serial_no)) - set(used_serial_nos)))
 
-	def set_title(self):
-		if frappe.flags.in_import and self.title:
-			# Allow updating title during data import/update
-			return
-
-		self.title = self.purpose
-
-
 @frappe.whitelist()
 def move_sample_to_retention_warehouse(company, items):
 	if isinstance(items, str):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 306f2c3..54c0e43 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe.permissions import add_user_permission, remove_user_permission
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import flt, nowdate, nowtime
 
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
@@ -28,7 +29,6 @@
 	create_stock_reconciliation,
 )
 from erpnext.stock.stock_ledger import NegativeStockError, get_previous_sle
-from erpnext.tests.utils import ERPNextTestCase, change_settings
 
 
 def get_sle(**args):
@@ -42,8 +42,9 @@
 		order by timestamp(posting_date, posting_time) desc, creation desc limit 1"""% condition,
 		values, as_dict=1)
 
-class TestStockEntry(ERPNextTestCase):
+class TestStockEntry(FrappeTestCase):
 	def tearDown(self):
+		frappe.db.rollback()
 		frappe.set_user("Administrator")
 		frappe.db.set_value("Manufacturing Settings", None, "material_consumption", "0")
 
@@ -565,6 +566,7 @@
 		st1.set_stock_entry_type()
 		st1.insert()
 		st1.submit()
+		st1.cancel()
 
 		frappe.set_user("Administrator")
 		remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
@@ -689,6 +691,8 @@
 		bom_no = frappe.db.get_value("BOM", {"item": "_Test Variant Item",
 			"is_default": 1, "docstatus": 1})
 
+		make_item_variant() # make variant of _Test Variant Item if absent
+
 		work_order = frappe.new_doc("Work Order")
 		work_order.update({
 			"company": "_Test Company",
@@ -1023,13 +1027,10 @@
 
 		# Check if FG cost is calculated based on RM total cost
 		# RM total cost = 200, FG rate = 200/4(FG qty) =  50
-		self.assertEqual(se.items[1].basic_rate, 50)
+		self.assertEqual(se.items[1].basic_rate, flt(se.items[0].basic_rate/4))
 		self.assertEqual(se.value_difference, 0.0)
 		self.assertEqual(se.total_incoming_value, se.total_outgoing_value)
 
-		# teardown
-		se.delete()
-
 	@change_settings("Stock Settings", {"allow_negative_stock": 0})
 	def test_future_negative_sle(self):
 		# Initialize item, batch, warehouse, opening qty
@@ -1107,6 +1108,52 @@
 				posting_date='2021-09-02', # backdated consumption of 2nd batch
 				purpose='Material Issue')
 
+	def test_multi_batch_value_diff(self):
+		""" Test value difference on stock entry in case of multi-batch.
+			| Stock entry | batch | qty | rate | value diff on SE             |
+			| ---         | ---   | --- | ---  | ---                          |
+			| receipt     | A     | 1   | 10   | 30                           |
+			| receipt     | B     | 1   | 20   |                              |
+			| issue       | A     | -1  | 10   | -30 (to assert after submit) |
+			| issue       | B     | -1  | 20   |                              |
+		"""
+		from erpnext.stock.doctype.batch.test_batch import TestBatch
+
+		batch_nos = []
+
+		item_code = '_TestMultibatchFifo'
+		TestBatch.make_batch_item(item_code)
+		warehouse = '_Test Warehouse - _TC'
+		receipt = make_stock_entry(
+				item_code=item_code,
+				qty=1,
+				rate=10,
+				to_warehouse=warehouse,
+				purpose='Material Receipt',
+				do_not_save=True
+			)
+		receipt.append("items", frappe.copy_doc(receipt.items[0], ignore_no_copy=False).update({"basic_rate": 20}) )
+		receipt.save()
+		receipt.submit()
+		batch_nos.extend(row.batch_no for row in receipt.items)
+		self.assertEqual(receipt.value_difference, 30)
+
+		issue = make_stock_entry(
+				item_code=item_code,
+				qty=1,
+				from_warehouse=warehouse,
+				purpose='Material Issue',
+				do_not_save=True
+			)
+		issue.append("items", frappe.copy_doc(issue.items[0], ignore_no_copy=False))
+		for row, batch_no in zip(issue.items, batch_nos):
+			row.batch_no = batch_no
+		issue.save()
+		issue.submit()
+
+		issue.reload()  # reload because reposting current voucher updates rate
+		self.assertEqual(issue.value_difference, -30)
+
 def make_serialized_item(**args):
 	args = frappe._dict(args)
 	se = frappe.copy_doc(test_records[0])
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 df65706..83aed90 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -1,7 +1,7 @@
 {
  "actions": [],
  "autoname": "hash",
- "creation": "2013-03-29 18:22:12",
+ "creation": "2022-02-05 00:17:49.860824",
  "doctype": "DocType",
  "document_type": "Other",
  "editable_grid": 1,
@@ -340,13 +340,13 @@
    "label": "More Information"
   },
   {
-   "allow_on_submit": 1,
    "default": "0",
    "fieldname": "allow_zero_valuation_rate",
    "fieldtype": "Check",
    "label": "Allow Zero Valuation Rate",
    "no_copy": 1,
-   "print_hide": 1
+   "print_hide": 1,
+   "read_only_depends_on": "eval:doc.s_warehouse"
   },
   {
    "allow_on_submit": 1,
@@ -556,12 +556,14 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-22 16:47:11.268975",
+ "modified": "2022-02-26 00:51:24.963653",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "ASC"
+ "sort_order": "ASC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index cafbd75..01d25b2 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -1,11 +1,19 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
+import json
+from operator import itemgetter
+from uuid import uuid4
+
 import frappe
 from frappe.core.page.permission_manager.permission_manager import reset
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, today
 
-from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+from erpnext.stock.doctype.delivery_note.test_delivery_note import (
+	create_delivery_note,
+	create_return_delivery_note,
+)
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
 	create_landed_cost_voucher,
@@ -17,10 +25,9 @@
 	create_stock_reconciliation,
 )
 from erpnext.stock.stock_ledger import get_previous_sle
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestStockLedgerEntry(ERPNextTestCase):
+class TestStockLedgerEntry(FrappeTestCase):
 	def setUp(self):
 		items = create_items()
 		reset('Stock Entry')
@@ -232,8 +239,7 @@
 		self.assertEqual(outgoing_rate, 100)
 
 		# Return Entry: Qty = -2, Rate = 150
-		return_dn = create_delivery_note(is_return=1, return_against=dn.name, item_code=bundled_item, qty=-2, rate=150,
-			company=company, warehouse="Stores - _TC", expense_account="Cost of Goods Sold - _TC", cost_center="Main - _TC")
+		return_dn = create_return_delivery_note(source_name=dn.name, rate=150, qty=-2)
 
 		# check incoming rate for Return entry
 		incoming_rate, stock_value_difference = frappe.db.get_value("Stock Ledger Entry",
@@ -347,6 +353,317 @@
 			frappe.set_user("Administrator")
 			user.remove_roles("Stock Manager")
 
+	def test_batchwise_item_valuation_moving_average(self):
+		item, warehouses, batches = setup_item_valuation_test(valuation_method="Moving Average")
+
+		# Incoming Entries for Stock Value check
+		pr_entry_list = [
+			(item, warehouses[0], batches[0], 1, 100),
+			(item, warehouses[0], batches[1], 1,  50),
+			(item, warehouses[0], batches[0], 1, 150),
+			(item, warehouses[0], batches[1], 1, 100),
+		]
+		prs = create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_list)
+		sle_details = fetch_sle_details_for_doc_list(prs, ['stock_value'])
+		sv_list = [d['stock_value'] for d in sle_details]
+		expected_sv = [100, 150, 300, 400]
+		self.assertEqual(expected_sv, sv_list, "Incorrect 'Stock Value' values")
+
+		# Outgoing Entries for Stock Value Difference check
+		dn_entry_list = [
+			(item, warehouses[0], batches[1], 1, 200),
+			(item, warehouses[0], batches[0], 1, 200),
+			(item, warehouses[0], batches[1], 1, 200),
+			(item, warehouses[0], batches[0], 1, 200)
+		]
+		dns = create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list)
+		sle_details = fetch_sle_details_for_doc_list(dns, ['stock_value_difference'])
+		svd_list = [-1 * d['stock_value_difference'] for d in sle_details]
+		expected_incoming_rates = expected_abs_svd = [75, 125, 75, 125]
+
+		self.assertEqual(expected_abs_svd, svd_list, "Incorrect 'Stock Value Difference' values")
+		for dn, incoming_rate in zip(dns, expected_incoming_rates):
+			self.assertEqual(
+				dn.items[0].incoming_rate, incoming_rate,
+				"Incorrect 'Incoming Rate' values fetched for DN items"
+			)
+
+
+	def assertSLEs(self, doc, expected_sles):
+		""" Compare sorted SLEs, useful for vouchers that create multiple SLEs for same line"""
+		sles = frappe.get_all("Stock Ledger Entry", fields=["*"],
+				filters={"voucher_no": doc.name, "voucher_type": doc.doctype, "is_cancelled":0},
+			order_by="timestamp(posting_date, posting_time), creation")
+
+		for exp_sle, act_sle in zip(expected_sles, sles):
+			for k, v in exp_sle.items():
+				act_value = act_sle[k]
+				if k == "stock_queue":
+					act_value = json.loads(act_value)
+					if act_value and act_value[0][0] == 0:
+						# ignore empty fifo bins
+						continue
+
+				self.assertEqual(v, act_value, msg=f"{k} doesn't match \n{exp_sle}\n{act_sle}")
+
+
+	def test_batchwise_item_valuation_stock_reco(self):
+		item, warehouses, batches = setup_item_valuation_test()
+		state = {
+			"stock_value" : 0.0,
+			"qty": 0.0
+		}
+		def update_invariants(exp_sles):
+			for sle in exp_sles:
+				state["stock_value"] += sle["stock_value_difference"]
+				state["qty"] += sle["actual_qty"]
+				sle["stock_value"] = state["stock_value"]
+				sle["qty_after_transaction"] = state["qty"]
+
+		osr1 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=10, rate=100, batch_no=batches[1])
+		expected_sles = [
+			{"actual_qty": 10, "stock_value_difference": 1000},
+		]
+		update_invariants(expected_sles)
+		self.assertSLEs(osr1, expected_sles)
+
+		osr2 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=13, rate=200, batch_no=batches[0])
+		expected_sles = [
+			{"actual_qty": 13, "stock_value_difference": 200*13},
+		]
+		update_invariants(expected_sles)
+		self.assertSLEs(osr2, expected_sles)
+
+		sr1 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=5, rate=50, batch_no=batches[1])
+
+		expected_sles = [
+			{"actual_qty": -10, "stock_value_difference": -10 * 100},
+			{"actual_qty": 5, "stock_value_difference": 250}
+		]
+		update_invariants(expected_sles)
+		self.assertSLEs(sr1, expected_sles)
+
+		sr2 = create_stock_reconciliation(warehouse=warehouses[0], item_code=item, qty=20, rate=75, batch_no=batches[0])
+		expected_sles = [
+			{"actual_qty": -13, "stock_value_difference": -13 * 200},
+			{"actual_qty": 20, "stock_value_difference": 20 * 75}
+		]
+		update_invariants(expected_sles)
+		self.assertSLEs(sr2, expected_sles)
+
+	def test_batch_wise_valuation_across_warehouse(self):
+		item_code, warehouses, batches = setup_item_valuation_test()
+		source = warehouses[0]
+		target = warehouses[1]
+
+		unrelated_batch = make_stock_entry(item_code=item_code, target=source, batch_no=batches[1],
+				qty=5, rate=10)
+		self.assertSLEs(unrelated_batch, [
+			{"actual_qty": 5, "stock_value_difference": 10 * 5},
+		])
+
+		reciept = make_stock_entry(item_code=item_code, target=source, batch_no=batches[0], qty=5, rate=10)
+		self.assertSLEs(reciept, [
+			{"actual_qty": 5, "stock_value_difference": 10 * 5},
+		])
+
+		transfer = make_stock_entry(item_code=item_code, source=source, target=target, batch_no=batches[0], qty=5)
+		self.assertSLEs(transfer, [
+			{"actual_qty": -5, "stock_value_difference": -10 * 5, "warehouse": source},
+			{"actual_qty": 5, "stock_value_difference": 10 * 5, "warehouse": target}
+		])
+
+		backdated_receipt = make_stock_entry(item_code=item_code, target=source, batch_no=batches[0],
+				qty=5, rate=20, posting_date=add_days(today(), -1))
+		self.assertSLEs(backdated_receipt, [
+			{"actual_qty": 5, "stock_value_difference": 20 * 5},
+		])
+
+		# check reposted average rate in *future* transfer
+		self.assertSLEs(transfer, [
+			{"actual_qty": -5, "stock_value_difference": -15 * 5, "warehouse": source, "stock_value": 15 * 5 + 10 * 5},
+			{"actual_qty": 5, "stock_value_difference": 15 * 5, "warehouse": target, "stock_value": 15 * 5}
+		])
+
+		transfer_unrelated = make_stock_entry(item_code=item_code, source=source,
+				target=target, batch_no=batches[1], qty=5)
+		self.assertSLEs(transfer_unrelated, [
+			{"actual_qty": -5, "stock_value_difference": -10 * 5, "warehouse": source, "stock_value": 15 * 5},
+			{"actual_qty": 5, "stock_value_difference": 10 * 5, "warehouse": target, "stock_value": 15 * 5 + 10 * 5}
+		])
+
+	def test_intermediate_average_batch_wise_valuation(self):
+		""" A batch has moving average up until posting time,
+		check if same is respected when backdated entry is inserted in middle"""
+		item_code, warehouses, batches = setup_item_valuation_test()
+		warehouse = warehouses[0]
+
+		batch = batches[0]
+
+		yesterday = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batch,
+				qty=1, rate=10, posting_date=add_days(today(), -1))
+		self.assertSLEs(yesterday, [
+			{"actual_qty": 1, "stock_value_difference": 10},
+		])
+
+		tomorrow = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
+				qty=1, rate=30, posting_date=add_days(today(), 1))
+		self.assertSLEs(tomorrow, [
+			{"actual_qty": 1, "stock_value_difference": 30},
+		])
+
+		create_today = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
+				qty=1, rate=20)
+		self.assertSLEs(create_today, [
+			{"actual_qty": 1, "stock_value_difference": 20},
+		])
+
+		consume_today = make_stock_entry(item_code=item_code, source=warehouse, batch_no=batches[0],
+				qty=1)
+		self.assertSLEs(consume_today, [
+			{"actual_qty": -1, "stock_value_difference": -15},
+		])
+
+		consume_tomorrow = make_stock_entry(item_code=item_code, source=warehouse, batch_no=batches[0],
+				qty=2, posting_date=add_days(today(), 2))
+		self.assertSLEs(consume_tomorrow, [
+			{"stock_value_difference": -(30 + 15), "stock_value": 0, "qty_after_transaction": 0},
+		])
+
+	def test_legacy_item_valuation_stock_entry(self):
+		columns = [
+				'stock_value_difference',
+				'stock_value',
+				'actual_qty',
+				'qty_after_transaction',
+				'stock_queue',
+		]
+		item, warehouses, batches = setup_item_valuation_test(use_batchwise_valuation=0)
+
+		def check_sle_details_against_expected(sle_details, expected_sle_details, detail, columns):
+			for i, (sle_vals, ex_sle_vals) in enumerate(zip(sle_details, expected_sle_details)):
+				for col, sle_val, ex_sle_val in zip(columns, sle_vals, ex_sle_vals):
+					if col == 'stock_queue':
+						sle_val = get_stock_value_from_q(sle_val)
+						ex_sle_val = get_stock_value_from_q(ex_sle_val)
+					self.assertEqual(
+						sle_val, ex_sle_val,
+						f"Incorrect {col} value on transaction #: {i} in {detail}"
+					)
+
+		# List used to defer assertions to prevent commits cause of error skipped rollback
+		details_list = []
+
+
+		# Test Material Receipt Entries
+		se_entry_list_mr = [
+			(item, None, warehouses[0], batches[0], 1,  50, "2021-01-21"),
+			(item, None, warehouses[0], batches[1], 1, 100, "2021-01-23"),
+		]
+		ses = create_stock_entry_entries_for_batchwise_item_valuation_test(
+			se_entry_list_mr, "Material Receipt"
+		)
+		sle_details = fetch_sle_details_for_doc_list(ses, columns=columns, as_dict=0)
+		expected_sle_details = [
+			(50.0, 50.0, 1.0, 1.0, '[[1.0, 50.0]]'),
+			(100.0, 150.0, 1.0, 2.0, '[[1.0, 50.0], [1.0, 100.0]]'),
+		]
+		details_list.append((
+			sle_details, expected_sle_details,
+			"Material Receipt Entries", columns
+		))
+
+
+		# Test Material Issue Entries
+		se_entry_list_mi = [
+			(item, warehouses[0], None, batches[1], 1, None, "2021-01-29"),
+		]
+		ses = create_stock_entry_entries_for_batchwise_item_valuation_test(
+			se_entry_list_mi, "Material Issue"
+		)
+		sle_details = fetch_sle_details_for_doc_list(ses, columns=columns, as_dict=0)
+		expected_sle_details = [
+			(-50.0, 100.0, -1.0, 1.0, '[[1, 100.0]]')
+		]
+		details_list.append((
+			sle_details, expected_sle_details,
+			"Material Issue Entries", columns
+		))
+
+
+		# Run assertions
+		for details in details_list:
+			check_sle_details_against_expected(*details)
+
+	def test_mixed_valuation_batches_fifo(self):
+		item_code, warehouses, batches = setup_item_valuation_test(use_batchwise_valuation=0)
+		warehouse = warehouses[0]
+
+		state = {
+			"qty": 0.0,
+			"stock_value": 0.0
+		}
+		def update_invariants(exp_sles):
+			for sle in exp_sles:
+				state["stock_value"] += sle["stock_value_difference"]
+				state["qty"] += sle["actual_qty"]
+				sle["stock_value"] = state["stock_value"]
+				sle["qty_after_transaction"] = state["qty"]
+			return exp_sles
+
+		old1 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
+				qty=10, rate=10)
+		self.assertSLEs(old1, update_invariants([
+			{"actual_qty": 10, "stock_value_difference": 10*10, "stock_queue": [[10, 10]]},
+		]))
+		old2 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[1],
+				qty=10, rate=20)
+		self.assertSLEs(old2, update_invariants([
+			{"actual_qty": 10, "stock_value_difference": 10*20, "stock_queue": [[10, 10], [10, 20]]},
+		]))
+		old3 = make_stock_entry(item_code=item_code, target=warehouse, batch_no=batches[0],
+				qty=5, rate=15)
+
+		self.assertSLEs(old3, update_invariants([
+			{"actual_qty": 5, "stock_value_difference": 5*15, "stock_queue": [[10, 10], [10, 20], [5, 15]]},
+		]))
+
+		new1 = make_stock_entry(item_code=item_code, target=warehouse, qty=10, rate=40)
+		batches.append(new1.items[0].batch_no)
+		# assert old queue remains
+		self.assertSLEs(new1, update_invariants([
+			{"actual_qty": 10, "stock_value_difference": 10*40, "stock_queue": [[10, 10], [10, 20], [5, 15]]},
+		]))
+
+		new2 = make_stock_entry(item_code=item_code, target=warehouse, qty=10, rate=42)
+		batches.append(new2.items[0].batch_no)
+		self.assertSLEs(new2, update_invariants([
+			{"actual_qty": 10, "stock_value_difference": 10*42, "stock_queue": [[10, 10], [10, 20], [5, 15]]},
+		]))
+
+		# consume old batch as per FIFO
+		consume_old1 = make_stock_entry(item_code=item_code, source=warehouse, qty=15, batch_no=batches[0])
+		self.assertSLEs(consume_old1, update_invariants([
+			{"actual_qty": -15, "stock_value_difference": -10*10 - 5*20, "stock_queue": [[5, 20], [5, 15]]},
+		]))
+
+		# consume new batch as per batch
+		consume_new2 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[-1])
+		self.assertSLEs(consume_new2, update_invariants([
+			{"actual_qty": -10, "stock_value_difference": -10*42, "stock_queue": [[5, 20], [5, 15]]},
+		]))
+
+		# finish all old batches
+		consume_old2 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[1])
+		self.assertSLEs(consume_old2, update_invariants([
+			{"actual_qty": -10, "stock_value_difference": -5*20 - 5*15, "stock_queue": []},
+		]))
+
+		# finish all new batches
+		consume_new1 = make_stock_entry(item_code=item_code, source=warehouse, qty=10, batch_no=batches[-2])
+		self.assertSLEs(consume_new1, update_invariants([
+			{"actual_qty": -10, "stock_value_difference": -10*40, "stock_queue": []},
+		]))
 
 def create_repack_entry(**args):
 	args = frappe._dict(args)
@@ -410,3 +727,118 @@
 		make_item(d, properties=properties)
 
 	return items
+
+def setup_item_valuation_test(valuation_method="FIFO", suffix=None, use_batchwise_valuation=1, batches_list=['X', 'Y']):
+	from erpnext.stock.doctype.batch.batch import make_batch
+	from erpnext.stock.doctype.item.test_item import make_item
+	from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+
+	if not suffix:
+		suffix = get_unique_suffix()
+
+	item = make_item(
+		f"IV - Test Item {valuation_method} {suffix}",
+		dict(valuation_method=valuation_method, has_batch_no=1, create_new_batch=1)
+	)
+	warehouses = [create_warehouse(f"IV - Test Warehouse {i}") for i in ['J', 'K']]
+	batches = [f"IV - Test Batch {i} {valuation_method} {suffix}" for i in batches_list]
+
+	for i, batch_id in enumerate(batches):
+		if not frappe.db.exists("Batch", batch_id):
+			ubw = use_batchwise_valuation
+			if isinstance(use_batchwise_valuation, (list, tuple)):
+				ubw = use_batchwise_valuation[i]
+			batch = frappe.get_doc(frappe._dict(
+					doctype="Batch",
+					batch_id=batch_id,
+					item=item.item_code,
+					use_batchwise_valuation=ubw
+				)
+			).insert()
+			batch.use_batchwise_valuation = ubw
+			batch.db_update()
+
+	return item.item_code, warehouses, batches
+
+def create_purchase_receipt_entries_for_batchwise_item_valuation_test(pr_entry_list):
+	from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+	prs = []
+
+	for item, warehouse, batch_no, qty, rate in pr_entry_list:
+		pr = make_purchase_receipt(item=item, warehouse=warehouse, qty=qty, rate=rate, batch_no=batch_no)
+		prs.append(pr)
+
+	return prs
+
+def create_delivery_note_entries_for_batchwise_item_valuation_test(dn_entry_list):
+	from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+	from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+	dns = []
+	for item, warehouse, batch_no, qty, rate in dn_entry_list:
+		so = make_sales_order(
+			rate=rate,
+			qty=qty,
+			item=item,
+			warehouse=warehouse,
+			against_blanket_order=0
+		)
+
+		dn = make_delivery_note(so.name)
+		dn.items[0].batch_no = batch_no
+		dn.insert()
+		dn.submit()
+		dns.append(dn)
+	return dns
+
+def fetch_sle_details_for_doc_list(doc_list, columns, as_dict=1):
+	return frappe.db.sql(f"""
+		SELECT { ', '.join(columns)}
+		FROM `tabStock Ledger Entry`
+		WHERE
+			voucher_no IN %(voucher_nos)s
+			and docstatus = 1
+		ORDER BY timestamp(posting_date, posting_time) ASC, CREATION ASC
+	""", dict(
+		voucher_nos=[doc.name for doc in doc_list]
+	), as_dict=as_dict)
+
+def get_stock_value_from_q(q):
+	return sum(r*q for r,q in json.loads(q))
+
+def create_stock_entry_entries_for_batchwise_item_valuation_test(se_entry_list, purpose):
+	ses = []
+	for item, source, target, batch, qty, rate, posting_date in se_entry_list:
+		args = dict(
+			item_code=item,
+			qty=qty,
+			company="_Test Company",
+			batch_no=batch,
+			posting_date=posting_date,
+			purpose=purpose
+		)
+
+		if purpose == "Material Receipt":
+			args.update(
+				dict(to_warehouse=target, rate=rate)
+			)
+
+		elif purpose == "Material Issue":
+			args.update(
+				dict(from_warehouse=source)
+			)
+
+		elif purpose == "Material Transfer":
+			args.update(
+				dict(from_warehouse=source, to_warehouse=target)
+			)
+
+		else:
+			raise ValueError(f"Invalid purpose: {purpose}")
+		ses.append(make_stock_entry(**args))
+
+	return ses
+
+def get_unique_suffix():
+	# Used to isolate valuation sensitive
+	# tests to prevent future tests from failing.
+	return str(uuid4())[:8].upper()
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
index 3402972..a882a61 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.json
@@ -18,7 +18,6 @@
   "items",
   "section_break_9",
   "expense_account",
-  "reconciliation_json",
   "column_break_13",
   "difference_amount",
   "amended_from",
@@ -112,15 +111,6 @@
    "options": "Cost Center"
   },
   {
-   "fieldname": "reconciliation_json",
-   "fieldtype": "Long Text",
-   "hidden": 1,
-   "label": "Reconciliation JSON",
-   "no_copy": 1,
-   "print_hide": 1,
-   "read_only": 1
-  },
-  {
    "fieldname": "column_break_13",
    "fieldtype": "Column Break"
   },
@@ -155,7 +145,7 @@
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-11-30 01:33:51.437194",
+ "modified": "2022-02-06 14:28:19.043905",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation",
@@ -178,5 +168,6 @@
  "search_fields": "posting_date",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index c4ddc9e..e6b252e 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -6,7 +6,8 @@
 
 
 import frappe
-from frappe.utils import add_days, flt, nowdate, nowtime, random_string
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, cstr, flt, nowdate, nowtime, random_string
 
 from erpnext.accounts.utils import get_stock_and_account_balance
 from erpnext.stock.doctype.item.test_item import create_item
@@ -19,14 +20,13 @@
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.stock_ledger import get_previous_sle, update_entries_after
 from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
-from erpnext.tests.utils import ERPNextTestCase, change_settings
 
 
-class TestStockReconciliation(ERPNextTestCase):
+class TestStockReconciliation(FrappeTestCase):
 	@classmethod
 	def setUpClass(cls):
-		super().setUpClass()
 		create_batch_or_serial_no_items()
+		super().setUpClass()
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
 
 	def tearDown(self):
@@ -200,7 +200,6 @@
 
 	def test_stock_reco_for_batch_item(self):
 		to_delete_records = []
-		to_delete_serial_nos = []
 
 		# Add new serial nos
 		item_code = "Stock-Reco-batch-Item-1"
@@ -208,20 +207,22 @@
 
 		sr = create_stock_reconciliation(item_code=item_code,
 			warehouse = warehouse, qty=5, rate=200, do_not_submit=1)
-		sr.save(ignore_permissions=True)
+		sr.save()
 		sr.submit()
 
-		self.assertTrue(sr.items[0].batch_no)
+		batch_no = sr.items[0].batch_no
+		self.assertTrue(batch_no)
 		to_delete_records.append(sr.name)
 
 		sr1 = create_stock_reconciliation(item_code=item_code,
-			warehouse = warehouse, qty=6, rate=300, batch_no=sr.items[0].batch_no)
+			warehouse = warehouse, qty=6, rate=300, batch_no=batch_no)
 
 		args = {
 			"item_code": item_code,
 			"warehouse": warehouse,
 			"posting_date": nowdate(),
 			"posting_time": nowtime(),
+			"batch_no": batch_no,
 		}
 
 		valuation_rate = get_incoming_rate(args)
@@ -230,7 +231,7 @@
 
 
 		sr2 = create_stock_reconciliation(item_code=item_code,
-			warehouse = warehouse, qty=0, rate=0, batch_no=sr.items[0].batch_no)
+			warehouse = warehouse, qty=0, rate=0, batch_no=batch_no)
 
 		stock_value = get_stock_value_on(warehouse, nowdate(), item_code)
 		self.assertEqual(stock_value, 0)
@@ -439,8 +440,8 @@
 		self.assertRaises(frappe.ValidationError, sr.submit)
 
 	def test_serial_no_cancellation(self):
-
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
 		item = create_item("Stock-Reco-Serial-Item-9", is_stock_item=1)
 		if not item.has_serial_no:
 			item.has_serial_no = 1
@@ -466,6 +467,31 @@
 		self.assertEqual(len(active_sr_no), 10)
 
 
+	def test_serial_no_creation_and_inactivation(self):
+		item = create_item("_TestItemCreatedWithStockReco", is_stock_item=1)
+		if not item.has_serial_no:
+			item.has_serial_no = 1
+			item.save()
+
+		item_code = item.name
+		warehouse = "_Test Warehouse - _TC"
+
+		sr = create_stock_reconciliation(item_code=item.name, warehouse=warehouse,
+				serial_no="SR-CREATED-SR-NO", qty=1, do_not_submit=True, rate=100)
+		sr.save()
+		self.assertEqual(cstr(sr.items[0].current_serial_no), "")
+		sr.submit()
+
+		active_sr_no = frappe.get_all("Serial No",
+				filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+		self.assertEqual(len(active_sr_no), 1)
+
+		sr.cancel()
+		active_sr_no = frappe.get_all("Serial No",
+				filters={"item_code": item_code, "warehouse": warehouse, "status": "Active"})
+		self.assertEqual(len(active_sr_no), 0)
+
+
 def create_batch_item_with_batch(item_name, batch_id):
 	batch_item_doc = create_item(item_name, is_stock_item=1)
 	if not batch_item_doc.has_batch_no:
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 33d9a6c..ec7fb0f 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -5,35 +5,41 @@
  "doctype": "DocType",
  "engine": "InnoDB",
  "field_order": [
+  "defaults_tab",
   "item_defaults_section",
   "item_naming_by",
   "item_group",
   "stock_uom",
-  "default_warehouse",
   "column_break_4",
-  "valuation_method",
+  "default_warehouse",
   "sample_retention_warehouse",
-  "use_naming_series",
-  "naming_series_prefix",
+  "valuation_method",
+  "price_list_defaults_section",
+  "auto_insert_price_list_rate_if_missing",
+  "column_break_12",
+  "update_existing_price_list_rate",
+  "stock_validations_tab",
   "section_break_9",
   "over_delivery_receipt_allowance",
-  "role_allowed_to_over_deliver_receive",
   "mr_qty_allowance",
-  "column_break_12",
-  "auto_insert_price_list_rate_if_missing",
-  "update_existing_price_list_rate",
+  "column_break_121",
+  "role_allowed_to_over_deliver_receive",
   "allow_negative_stock",
   "show_barcode_field",
   "clean_description_html",
   "quality_inspection_settings_section",
   "action_if_quality_inspection_is_not_submitted",
-  "column_break_21",
+  "column_break_23",
   "action_if_quality_inspection_is_rejected",
+  "serial_and_batch_item_settings_tab",
   "section_break_7",
   "automatically_set_serial_nos_based_on_fifo",
   "set_qty_in_transactions_based_on_serial_no_input",
   "column_break_10",
   "disable_serial_no_and_batch_selector",
+  "use_naming_series",
+  "naming_series_prefix",
+  "stock_planning_tab",
   "auto_material_request",
   "auto_indent",
   "column_break_27",
@@ -42,6 +48,7 @@
   "allow_from_dn",
   "column_break_31",
   "allow_from_pr",
+  "stock_closing_tab",
   "control_historical_stock_transactions_section",
   "stock_frozen_upto",
   "stock_frozen_upto_days",
@@ -92,7 +99,7 @@
    "fieldname": "valuation_method",
    "fieldtype": "Select",
    "label": "Default Valuation Method",
-   "options": "FIFO\nMoving Average"
+   "options": "FIFO\nMoving Average\nLIFO"
   },
   {
    "description": "The percentage you are allowed to receive or deliver more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed to receive 110 units.",
@@ -122,7 +129,7 @@
   {
    "fieldname": "section_break_7",
    "fieldtype": "Section Break",
-   "label": "Serialised and Batch Setting"
+   "label": "Serial & Batch Item Settings"
   },
   {
    "default": "0",
@@ -276,10 +283,6 @@
    "label": "Quality Inspection Settings"
   },
   {
-   "fieldname": "column_break_21",
-   "fieldtype": "Column Break"
-  },
-  {
    "default": "Stop",
    "fieldname": "action_if_quality_inspection_is_rejected",
    "fieldtype": "Select",
@@ -298,6 +301,44 @@
    "fieldname": "update_existing_price_list_rate",
    "fieldtype": "Check",
    "label": "Update Existing Price List Rate"
+  },
+  {
+   "fieldname": "defaults_tab",
+   "fieldtype": "Tab Break",
+   "label": "Defaults"
+  },
+  {
+   "fieldname": "stock_validations_tab",
+   "fieldtype": "Tab Break",
+   "label": "Stock Validations"
+  },
+  {
+   "fieldname": "stock_planning_tab",
+   "fieldtype": "Tab Break",
+   "label": "Stock Planning"
+  },
+  {
+   "fieldname": "stock_closing_tab",
+   "fieldtype": "Tab Break",
+   "label": "Stock Closing"
+  },
+  {
+   "fieldname": "serial_and_batch_item_settings_tab",
+   "fieldtype": "Tab Break",
+   "label": "Serial & Batch Item"
+  },
+  {
+   "fieldname": "column_break_23",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "price_list_defaults_section",
+   "fieldtype": "Section Break",
+   "label": "Price List Defaults"
+  },
+  {
+   "fieldname": "column_break_121",
+   "fieldtype": "Column Break"
   }
  ],
  "icon": "icon-cog",
@@ -305,7 +346,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-11-06 19:40:02.183592",
+ "modified": "2022-02-05 15:33:43.692736",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
@@ -324,5 +365,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_settings/test_stock_settings.py b/erpnext/stock/doctype/stock_settings/test_stock_settings.py
index 072b54b..1349671 100644
--- a/erpnext/stock/doctype/stock_settings/test_stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/test_stock_settings.py
@@ -4,11 +4,10 @@
 import unittest
 
 import frappe
-
-from erpnext.tests.utils import ERPNextTestCase
+from frappe.tests.utils import FrappeTestCase
 
 
-class TestStockSettings(ERPNextTestCase):
+class TestStockSettings(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		frappe.db.set_value("Stock Settings", None, "clean_description_html", 0)
diff --git a/erpnext/stock/doctype/warehouse/test_warehouse.py b/erpnext/stock/doctype/warehouse/test_warehouse.py
index 26db264..cdb7719 100644
--- a/erpnext/stock/doctype/warehouse/test_warehouse.py
+++ b/erpnext/stock/doctype/warehouse/test_warehouse.py
@@ -3,17 +3,17 @@
 
 import frappe
 from frappe.test_runner import make_test_records
+from frappe.tests.utils import FrappeTestCase
 from frappe.utils import cint
 
 import erpnext
 from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
 from erpnext.stock.doctype.item.test_item import create_item
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
-from erpnext.tests.utils import ERPNextTestCase
 
 test_records = frappe.get_test_records('Warehouse')
 
-class TestWarehouse(ERPNextTestCase):
+class TestWarehouse(FrappeTestCase):
 	def setUp(self):
 		super().setUp()
 		if not frappe.get_value('Item', '_Test Item'):
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index 05076b5..c695d54 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -244,7 +244,7 @@
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2021-12-03 04:40:06.414630",
+ "modified": "2022-03-01 02:37:48.034944",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Warehouse",
@@ -301,5 +301,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
- "title_field": "warehouse_name"
+ "states": [],
+ "title_field": "warehouse_name",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 06f8fa7..9bec5f7 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -6,6 +6,7 @@
 
 import frappe
 from frappe import _, throw
+from frappe.model import child_table_fields, default_fields
 from frappe.model.meta import get_field_precision
 from frappe.utils import add_days, add_months, cint, cstr, flt, getdate
 
@@ -119,8 +120,15 @@
 		out.rate = args.rate or out.price_list_rate
 		out.amount = flt(args.qty) * flt(out.rate)
 
+	out = remove_standard_fields(out)
 	return out
 
+def remove_standard_fields(details):
+	for key in child_table_fields + default_fields:
+		details.pop(key, None)
+	return details
+
+
 def update_stock(args, out):
 	if (args.get("doctype") == "Delivery Note" or
 		(args.get("doctype") == "Sales Invoice" and args.get('update_stock'))) \
@@ -343,6 +351,7 @@
 
 	args.conversion_factor = out.conversion_factor
 	out.stock_qty = out.qty * out.conversion_factor
+	args.stock_qty = out.stock_qty
 
 	# calculate last purchase rate
 	if args.get('doctype') in purchase_doctypes:
diff --git a/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json b/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
deleted file mode 100644
index 5ee3167..0000000
--- a/erpnext/stock/onboarding_slide/add_a_few_products_you_buy_or_sell/add_a_few_products_you_buy_or_sell.json
+++ /dev/null
@@ -1,52 +0,0 @@
-{
- "add_more_button": 1,
- "app": "ERPNext",
- "creation": "2019-11-15 14:41:12.007359",
- "docstatus": 0,
- "doctype": "Onboarding Slide",
- "domains": [],
- "help_links": [],
- "idx": 0,
- "image_src": "",
- "is_completed": 0,
- "max_count": 3,
- "modified": "2019-12-09 17:54:09.602885",
- "modified_by": "Administrator",
- "name": "Add A Few Products You Buy Or Sell",
- "owner": "Administrator",
- "ref_doctype": "Item",
- "slide_desc": "",
- "slide_fields": [
-  {
-   "align": "",
-   "fieldname": "item",
-   "fieldtype": "Data",
-   "label": "Item",
-   "placeholder": "Product Name",
-   "reqd": 1
-  },
-  {
-   "align": "",
-   "fieldname": "item_price",
-   "fieldtype": "Currency",
-   "label": "Item Price",
-   "reqd": 1
-  },
-  {
-   "align": "",
-   "fieldtype": "Column Break",
-   "reqd": 0
-  },
-  {
-   "align": "",
-   "fieldname": "uom",
-   "fieldtype": "Link",
-   "label": "UOM",
-   "options": "UOM",
-   "reqd": 1
-  }
- ],
- "slide_order": 30,
- "slide_title": "Add A Few Products You Buy Or Sell",
- "slide_type": "Create"
-}
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index e6dfc97..97a740e 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -12,6 +12,7 @@
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 Filters = frappe._dict
+precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
 
 def execute(filters: Filters = None) -> Tuple:
 	to_date = filters["to_date"]
@@ -48,10 +49,13 @@
 		if filters.get("show_warehouse_wise_stock"):
 			row.append(details.warehouse)
 
-		row.extend([item_dict.get("total_qty"), average_age,
+		row.extend([
+			flt(item_dict.get("total_qty"), precision),
+			average_age,
 			range1, range2, range3, above_range3,
 			earliest_age, latest_age,
-			details.stock_uom])
+			details.stock_uom
+		])
 
 		data.append(row)
 
@@ -79,13 +83,13 @@
 		qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
 
 		if age <= filters.range1:
-			range1 += qty
+			range1 = flt(range1 + qty, precision)
 		elif age <= filters.range2:
-			range2 += qty
+			range2 = flt(range2 + qty, precision)
 		elif age <= filters.range3:
-			range3 += qty
+			range3 = flt(range3 + qty, precision)
 		else:
-			above_range3 += qty
+			above_range3 = flt(above_range3 + qty, precision)
 
 	return range1, range2, range3, above_range3
 
@@ -252,6 +256,7 @@
 			key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
 
 			if d.voucher_type == "Stock Reconciliation":
+				# get difference in qty shift as actual qty
 				prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
 				d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
 
@@ -264,12 +269,16 @@
 
 			self.__update_balances(d, key)
 
+		if not self.filters.get("show_warehouse_wise_stock"):
+			# (Item 1, WH 1), (Item 1, WH 2) => (Item 1)
+			self.item_details = self.__aggregate_details_by_item(self.item_details)
+
 		return self.item_details
 
 	def __init_key_stores(self, row: Dict) -> Tuple:
 		"Initialise keys and FIFO Queue."
 
-		key = (row.name, row.warehouse) if self.filters.get('show_warehouse_wise_stock') else row.name
+		key = (row.name, row.warehouse)
 		self.item_details.setdefault(key, {"details": row, "fifo_queue": []})
 		fifo_queue = self.item_details[key]["fifo_queue"]
 
@@ -281,14 +290,16 @@
 	def __compute_incoming_stock(self, row: Dict, fifo_queue: List, transfer_key: Tuple, serial_nos: List):
 		"Update FIFO Queue on inward stock."
 
-		if self.transferred_item_details.get(transfer_key):
+		transfer_data = self.transferred_item_details.get(transfer_key)
+		if transfer_data:
 			# inward/outward from same voucher, item & warehouse
-			slot = self.transferred_item_details[transfer_key].pop(0)
-			fifo_queue.append(slot)
+			# eg: Repack with same item, Stock reco for batch item
+			# consume transfer data and add stock to fifo queue
+			self.__adjust_incoming_transfer_qty(transfer_data, fifo_queue, row)
 		else:
 			if not serial_nos:
-				if fifo_queue and flt(fifo_queue[0][0]) < 0:
-					# neutralize negative stock by adding positive stock
+				if fifo_queue and flt(fifo_queue[0][0]) <= 0:
+					# neutralize 0/negative stock by adding positive stock
 					fifo_queue[0][0] += flt(row.actual_qty)
 					fifo_queue[0][1] = row.posting_date
 				else:
@@ -319,7 +330,7 @@
 			elif not fifo_queue:
 				# negative stock, no balance but qty yet to consume
 				fifo_queue.append([-(qty_to_pop), row.posting_date])
-				self.transferred_item_details[transfer_key].append([row.actual_qty, row.posting_date])
+				self.transferred_item_details[transfer_key].append([qty_to_pop, row.posting_date])
 				qty_to_pop = 0
 			else:
 				# qty to pop < slot qty, ample balance
@@ -328,6 +339,33 @@
 				self.transferred_item_details[transfer_key].append([qty_to_pop, slot[1]])
 				qty_to_pop = 0
 
+	def __adjust_incoming_transfer_qty(self, transfer_data: Dict, fifo_queue: List, row: Dict):
+		"Add previously removed stock back to FIFO Queue."
+		transfer_qty_to_pop = flt(row.actual_qty)
+
+		def add_to_fifo_queue(slot):
+			if fifo_queue and flt(fifo_queue[0][0]) <= 0:
+				# neutralize 0/negative stock by adding positive stock
+				fifo_queue[0][0] += flt(slot[0])
+				fifo_queue[0][1] = slot[1]
+			else:
+				fifo_queue.append(slot)
+
+		while transfer_qty_to_pop:
+			if transfer_data and 0 < transfer_data[0][0] <= transfer_qty_to_pop:
+				# bucket qty is not enough, consume whole
+				transfer_qty_to_pop -= transfer_data[0][0]
+				add_to_fifo_queue(transfer_data.pop(0))
+			elif not transfer_data:
+				# transfer bucket is empty, extra incoming qty
+				add_to_fifo_queue([transfer_qty_to_pop, row.posting_date])
+				transfer_qty_to_pop = 0
+			else:
+				# ample bucket qty to consume
+				transfer_data[0][0] -= transfer_qty_to_pop
+				add_to_fifo_queue([transfer_qty_to_pop, transfer_data[0][1]])
+				transfer_qty_to_pop = 0
+
 	def __update_balances(self, row: Dict, key: Union[Tuple, str]):
 		self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction
 
@@ -338,6 +376,27 @@
 
 		self.item_details[key]["has_serial_no"] = row.has_serial_no
 
+	def __aggregate_details_by_item(self, wh_wise_data: Dict) -> Dict:
+		"Aggregate Item-Wh wise data into single Item entry."
+		item_aggregated_data = {}
+		for key,row in wh_wise_data.items():
+			item = key[0]
+			if not item_aggregated_data.get(item):
+				item_aggregated_data.setdefault(item, {
+					"details": frappe._dict(),
+					"fifo_queue": [],
+					"qty_after_transaction": 0.0,
+					"total_qty": 0.0
+				})
+			item_row = item_aggregated_data.get(item)
+			item_row["details"].update(row["details"])
+			item_row["fifo_queue"].extend(row["fifo_queue"])
+			item_row["qty_after_transaction"] += flt(row["qty_after_transaction"])
+			item_row["total_qty"] += flt(row["total_qty"])
+			item_row["has_serial_no"] = row["has_serial_no"]
+
+		return item_aggregated_data
+
 	def __get_stock_ledger_entries(self) -> List[Dict]:
 		sle = frappe.qb.DocType("Stock Ledger Entry")
 		item = self.__get_item_query() # used as derived table in sle query
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md b/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
index 5ffe97f..3d759dd 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
+++ b/erpnext/stock/report/stock_ageing/stock_ageing_fifo_logic.md
@@ -15,6 +15,7 @@
 50 qty is (today-the 1st) days old
 20 qty is (today-the 2nd) days old
 
+> Note: We generate FIFO slots warehouse wise as stock reconciliations from different warehouses can cause incorrect values.
 ### Calculation of FIFO Slots
 
 #### Case 1: Outward from sufficient balance qty
@@ -70,4 +71,39 @@
 2nd  | -60 | [[-10, 1-12-2021]]
 3rd  | +5  | [[-5, 3-12-2021]]
 4th  | +10 | [[5, 4-12-2021]]
-4th  | +20 | [[5, 4-12-2021], [20, 4-12-2021]]
\ No newline at end of file
+4th  | +20 | [[5, 4-12-2021], [20, 4-12-2021]]
+
+### Concept of Transfer Qty Bucket
+In the case of **Repack**, Quantity that comes in, isn't really incoming. It is just new stock repurposed from old stock, due to incoming-outgoing of the same warehouse.
+
+Here, stock is consumed from the FIFO Queue. It is then re-added back to the queue.
+While adding stock back to the queue we need to know how much to add.
+For this we need to keep track of how much was previously consumed.
+Hence we use **Transfer Qty Bucket**.
+
+While re-adding stock, we try to add buckets that were consumed earlier (date intact), to maintain correctness.
+
+#### Case 1: Same Item-Warehouse in Repack
+Eg:
+-------------------------------------------------------------------------------------
+Date | Qty   | Voucher |             FIFO Queue           	   | Transfer Qty Buckets
+-------------------------------------------------------------------------------------
+1st  | +500  |  PR     | [[500, 1-12-2021]]   				   |
+2nd  | -50   |  Repack | [[450, 1-12-2021]]   				   | [[50, 1-12-2021]]
+2nd  | +50   |  Repack | [[450, 1-12-2021], [50, 1-12-2021]]   | []
+
+- The balance at the end is restored back to 500
+- However, the initial 500 qty bucket is now split into 450 and 50, with the same date
+- The net effect is the same as that before the Repack
+
+#### Case 2: Same Item-Warehouse in Repack with Split Consumption rows
+Eg:
+-------------------------------------------------------------------------------------
+Date | Qty   | Voucher |             FIFO Queue           	   | Transfer Qty Buckets
+-------------------------------------------------------------------------------------
+1st  | +500  |  PR     | [[500, 1-12-2021]]   				   |
+2nd  | -50   |  Repack | [[450, 1-12-2021]]   				   | [[50, 1-12-2021]]
+2nd  | -50   |  Repack | [[400, 1-12-2021]]   				   | [[50, 1-12-2021],
+-	 |		 |		   |									   |[50, 1-12-2021]]
+2nd  | +100  |  Repack | [[400, 1-12-2021], [50, 1-12-2021],   | []
+-	 |		 |		   | [50, 1-12-2021]]					   |
diff --git a/erpnext/stock/report/stock_ageing/test_stock_ageing.py b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
index 949bb7c..2630805 100644
--- a/erpnext/stock/report/stock_ageing/test_stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/test_stock_ageing.py
@@ -2,24 +2,26 @@
 # See license.txt
 
 import frappe
+from frappe.tests.utils import FrappeTestCase
 
-from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots
-from erpnext.tests.utils import ERPNextTestCase
+from erpnext.stock.report.stock_ageing.stock_ageing import FIFOSlots, format_report_data
 
 
-class TestStockAgeing(ERPNextTestCase):
+class TestStockAgeing(FrappeTestCase):
 	def setUp(self) -> None:
 		self.filters = frappe._dict(
 			company="_Test Company",
-			to_date="2021-12-10"
+			to_date="2021-12-10",
+			range1=30, range2=60, range3=90
 		)
 
 	def test_normal_inward_outward_queue(self):
-		"Reference: Case 1 in stock_ageing_fifo_logic.md"
+		"Reference: Case 1 in stock_ageing_fifo_logic.md (same wh)"
 		sle = [
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=30, qty_after_transaction=30,
+				warehouse="WH 1",
 				posting_date="2021-12-01", voucher_type="Stock Entry",
 				voucher_no="001",
 				has_serial_no=False, serial_no=None
@@ -27,6 +29,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=20, qty_after_transaction=50,
+				warehouse="WH 1",
 				posting_date="2021-12-02", voucher_type="Stock Entry",
 				voucher_no="002",
 				has_serial_no=False, serial_no=None
@@ -34,6 +37,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=(-10), qty_after_transaction=40,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="003",
 				has_serial_no=False, serial_no=None
@@ -50,11 +54,12 @@
 		self.assertEqual(queue[0][0], 20.0)
 
 	def test_insufficient_balance(self):
-		"Reference: Case 3 in stock_ageing_fifo_logic.md"
+		"Reference: Case 3 in stock_ageing_fifo_logic.md (same wh)"
 		sle = [
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=(-30), qty_after_transaction=(-30),
+				warehouse="WH 1",
 				posting_date="2021-12-01", voucher_type="Stock Entry",
 				voucher_no="001",
 				has_serial_no=False, serial_no=None
@@ -62,6 +67,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=20, qty_after_transaction=(-10),
+				warehouse="WH 1",
 				posting_date="2021-12-02", voucher_type="Stock Entry",
 				voucher_no="002",
 				has_serial_no=False, serial_no=None
@@ -69,6 +75,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=20, qty_after_transaction=10,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="003",
 				has_serial_no=False, serial_no=None
@@ -76,6 +83,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=10, qty_after_transaction=20,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="004",
 				has_serial_no=False, serial_no=None
@@ -91,11 +99,16 @@
 		self.assertEqual(queue[0][0], 10.0)
 		self.assertEqual(queue[1][0], 10.0)
 
-	def test_stock_reconciliation(self):
+	def test_basic_stock_reconciliation(self):
+		"""
+		Ledger (same wh): [+30, reco reset >> 50, -10]
+		Bal: 40
+		"""
 		sle = [
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=30, qty_after_transaction=30,
+				warehouse="WH 1",
 				posting_date="2021-12-01", voucher_type="Stock Entry",
 				voucher_no="001",
 				has_serial_no=False, serial_no=None
@@ -103,6 +116,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=0, qty_after_transaction=50,
+				warehouse="WH 1",
 				posting_date="2021-12-02", voucher_type="Stock Reconciliation",
 				voucher_no="002",
 				has_serial_no=False, serial_no=None
@@ -110,6 +124,7 @@
 			frappe._dict(
 				name="Flask Item",
 				actual_qty=(-10), qty_after_transaction=40,
+				warehouse="WH 1",
 				posting_date="2021-12-03", voucher_type="Stock Entry",
 				voucher_no="003",
 				has_serial_no=False, serial_no=None
@@ -122,5 +137,477 @@
 		queue = result["fifo_queue"]
 
 		self.assertEqual(result["qty_after_transaction"], result["total_qty"])
+		self.assertEqual(result["total_qty"], 40.0)
 		self.assertEqual(queue[0][0], 20.0)
 		self.assertEqual(queue[1][0], 20.0)
+
+	def test_sequential_stock_reco_same_warehouse(self):
+		"""
+		Test back to back stock recos (same warehouse).
+		Ledger: [reco opening >> +1000, reco reset >> 400, -10]
+		Bal: 390
+		"""
+		sle = [
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=1000,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=400,
+				warehouse="WH 1",
+				posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+				voucher_no="003",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-10), qty_after_transaction=390,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="003",
+				has_serial_no=False, serial_no=None
+			)
+		]
+		slots = FIFOSlots(self.filters, sle).generate()
+
+		result = slots["Flask Item"]
+		queue = result["fifo_queue"]
+
+		self.assertEqual(result["qty_after_transaction"], result["total_qty"])
+		self.assertEqual(result["total_qty"], 390.0)
+		self.assertEqual(queue[0][0], 390.0)
+
+	def test_sequential_stock_reco_different_warehouse(self):
+		"""
+		Ledger:
+		WH	| Voucher | Qty
+		-------------------
+		WH1 | Reco	  | 1000
+		WH2 | Reco	  | 400
+		WH1 | SE	  | -10
+
+		Bal: WH1 bal + WH2 bal = 990 + 400 = 1390
+		"""
+		sle = [
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=1000,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Reconciliation",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=0, qty_after_transaction=400,
+				warehouse="WH 2",
+				posting_date="2021-12-02", voucher_type="Stock Reconciliation",
+				voucher_no="003",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-10), qty_after_transaction=990,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="004",
+				has_serial_no=False, serial_no=None
+			)
+		]
+
+		item_wise_slots, item_wh_wise_slots = generate_item_and_item_wh_wise_slots(
+			filters=self.filters,sle=sle
+		)
+
+		# test without 'show_warehouse_wise_stock'
+		item_result = item_wise_slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["qty_after_transaction"], item_result["total_qty"])
+		self.assertEqual(item_result["total_qty"], 1390.0)
+		self.assertEqual(queue[0][0], 990.0)
+		self.assertEqual(queue[1][0], 400.0)
+
+		# test with 'show_warehouse_wise_stock' checked
+		item_wh_balances = [item_wh_wise_slots.get(i).get("qty_after_transaction") for i in item_wh_wise_slots]
+		self.assertEqual(sum(item_wh_balances), item_result["qty_after_transaction"])
+
+	def test_repack_entry_same_item_split_rows(self):
+		"""
+		Split consumption rows and have single repacked item row (same warehouse).
+		Ledger:
+		Item	| Qty | Voucher
+		------------------------
+		Item 1  | 500 | 001
+		Item 1  | -50 | 002 (repack)
+		Item 1  | -50 | 002 (repack)
+		Item 1  | 100 | 002 (repack)
+
+		Case most likely for batch items. Test time bucket computation.
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=500, qty_after_transaction=500,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=450,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=400,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=100, qty_after_transaction=500,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		slots = FIFOSlots(self.filters, sle).generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], 500.0)
+		self.assertEqual(queue[0][0], 400.0)
+		self.assertEqual(queue[1][0], 50.0)
+		self.assertEqual(queue[2][0], 50.0)
+		# check if time buckets add up to balance qty
+		self.assertEqual(sum([i[0] for i in queue]), 500.0)
+
+	def test_repack_entry_same_item_overconsume(self):
+		"""
+		Over consume item and have less repacked item qty (same warehouse).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | 500  | 001
+		Item 1  | -100 | 002 (repack)
+		Item 1  | 50   | 002 (repack)
+
+		Case most likely for batch items. Test time bucket computation.
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=500, qty_after_transaction=500,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-100), qty_after_transaction=400,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=450,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		slots = FIFOSlots(self.filters, sle).generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], 450.0)
+		self.assertEqual(queue[0][0], 400.0)
+		self.assertEqual(queue[1][0], 50.0)
+		# check if time buckets add up to balance qty
+		self.assertEqual(sum([i[0] for i in queue]), 450.0)
+
+	def test_repack_entry_same_item_overconsume_with_split_rows(self):
+		"""
+		Over consume item and have less repacked item qty (same warehouse).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | 20   | 001
+		Item 1  | -50  | 002 (repack)
+		Item 1  | -50  | 002 (repack)
+		Item 1  | 50   | 002 (repack)
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=20, qty_after_transaction=20,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-30),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-80),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=(-30),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], -30.0)
+		self.assertEqual(queue[0][0], -30.0)
+
+		# check transfer bucket
+		transfer_bucket = fifo_slots.transferred_item_details[('002', 'Flask Item', 'WH 1')]
+		self.assertEqual(transfer_bucket[0][0], 50)
+
+	def test_repack_entry_same_item_overproduce(self):
+		"""
+		Under consume item and have more repacked item qty (same warehouse).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | 500  | 001
+		Item 1  | -50  | 002 (repack)
+		Item 1  | 100  | 002 (repack)
+
+		Case most likely for batch items. Test time bucket computation.
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=500, qty_after_transaction=500,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=450,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=100, qty_after_transaction=550,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		slots = FIFOSlots(self.filters, sle).generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], 550.0)
+		self.assertEqual(queue[0][0], 450.0)
+		self.assertEqual(queue[1][0], 50.0)
+		self.assertEqual(queue[2][0], 50.0)
+		# check if time buckets add up to balance qty
+		self.assertEqual(sum([i[0] for i in queue]), 550.0)
+
+	def test_repack_entry_same_item_overproduce_with_split_rows(self):
+		"""
+		Over consume item and have less repacked item qty (same warehouse).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | 20   | 001
+		Item 1  | -50  | 002 (repack)
+		Item 1  | 50  | 002 (repack)
+		Item 1  | 50   | 002 (repack)
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=20, qty_after_transaction=20,
+				warehouse="WH 1",
+				posting_date="2021-12-03", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-30),
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=20,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict(
+				name="Flask Item",
+				actual_qty=50, qty_after_transaction=70,
+				warehouse="WH 1",
+				posting_date="2021-12-04", voucher_type="Stock Entry",
+				voucher_no="002",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+		queue = item_result["fifo_queue"]
+
+		self.assertEqual(item_result["total_qty"], 70.0)
+		self.assertEqual(queue[0][0], 20.0)
+		self.assertEqual(queue[1][0], 50.0)
+
+		# check transfer bucket
+		transfer_bucket = fifo_slots.transferred_item_details[('002', 'Flask Item', 'WH 1')]
+		self.assertFalse(transfer_bucket)
+
+	def test_negative_stock_same_voucher(self):
+		"""
+		Test negative stock scenario in transfer bucket via repack entry (same wh).
+		Ledger:
+		Item	| Qty  | Voucher
+		------------------------
+		Item 1  | -50  | 001
+		Item 1  | -50  | 001
+		Item 1  | 30   | 001
+		Item 1  | 80   | 001
+		"""
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-50),
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=(-50), qty_after_transaction=(-100),
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=30, qty_after_transaction=(-70),
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+		]
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+
+		# check transfer bucket
+		transfer_bucket = fifo_slots.transferred_item_details[('001', 'Flask Item', 'WH 1')]
+		self.assertEqual(transfer_bucket[0][0], 20)
+		self.assertEqual(transfer_bucket[1][0], 50)
+		self.assertEqual(item_result["fifo_queue"][0][0], -70.0)
+
+		sle.append(frappe._dict(
+			name="Flask Item",
+			actual_qty=80, qty_after_transaction=10,
+			warehouse="WH 1",
+			posting_date="2021-12-01", voucher_type="Stock Entry",
+			voucher_no="001",
+			has_serial_no=False, serial_no=None
+		))
+
+		fifo_slots = FIFOSlots(self.filters, sle)
+		slots = fifo_slots.generate()
+		item_result = slots["Flask Item"]
+
+		transfer_bucket = fifo_slots.transferred_item_details[('001', 'Flask Item', 'WH 1')]
+		self.assertFalse(transfer_bucket)
+		self.assertEqual(item_result["fifo_queue"][0][0], 10.0)
+
+	def test_precision(self):
+		"Test if final balance qty is rounded off correctly."
+		sle = [
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=0.3, qty_after_transaction=0.3,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+			frappe._dict( # stock up item
+				name="Flask Item",
+				actual_qty=0.6, qty_after_transaction=0.9,
+				warehouse="WH 1",
+				posting_date="2021-12-01", voucher_type="Stock Entry",
+				voucher_no="001",
+				has_serial_no=False, serial_no=None
+			),
+		]
+
+		slots = FIFOSlots(self.filters, sle).generate()
+		report_data = format_report_data(self.filters, slots, self.filters["to_date"])
+		row = report_data[0] # first row in report
+		bal_qty = row[5]
+		range_qty_sum = sum([i for i in row[7:11]]) # get sum of range balance
+
+		# check if value of Available Qty column matches with range bucket post format
+		self.assertEqual(bal_qty, 0.9)
+		self.assertEqual(bal_qty, range_qty_sum)
+
+def generate_item_and_item_wh_wise_slots(filters, sle):
+	"Return results with and without 'show_warehouse_wise_stock'"
+	item_wise_slots = FIFOSlots(filters, sle).generate()
+
+	filters.show_warehouse_wise_stock = True
+	item_wh_wise_slots = FIFOSlots(filters, sle).generate()
+	filters.show_warehouse_wise_stock = False
+
+	return item_wise_slots, item_wh_wise_slots
diff --git a/erpnext/stock/report/stock_analytics/test_stock_analytics.py b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
index 32df585..f6c98f9 100644
--- a/erpnext/stock/report/stock_analytics/test_stock_analytics.py
+++ b/erpnext/stock/report/stock_analytics/test_stock_analytics.py
@@ -1,14 +1,13 @@
 import datetime
-import unittest
 
 from frappe import _dict
+from frappe.tests.utils import FrappeTestCase
 
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.stock.report.stock_analytics.stock_analytics import get_period_date_ranges
-from erpnext.tests.utils import ERPNextTestCase
 
 
-class TestStockAnalyticsReport(ERPNextTestCase):
+class TestStockAnalyticsReport(FrappeTestCase):
 	def test_get_period_date_ranges(self):
 
 		filters = _dict(range="Monthly", from_date="2020-12-28", to_date="2021-02-06")
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index fe2417b..ef7c2cc 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -86,10 +86,10 @@
 	],
 	"formatter": function (value, row, column, data, default_formatter) {
 		value = default_formatter(value, row, column, data);
-		if (column.fieldname == "out_qty" && data.out_qty < 0) {
+		if (column.fieldname == "out_qty" && data && data.out_qty < 0) {
 			value = "<span style='color:red'>" + value + "</span>";
 		}
-		else if (column.fieldname == "in_qty" && data.in_qty > 0) {
+		else if (column.fieldname == "in_qty" && data && data.in_qty > 0) {
 			value = "<span style='color:green'>" + value + "</span>";
 		}
 
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index c60a6ca..81fa045 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -104,6 +104,7 @@
 		{"label": _("Incoming Rate"), "fieldname": "incoming_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
 		{"label": _("Valuation Rate"), "fieldname": "valuation_rate", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency", "convertible": "rate"},
 		{"label": _("Balance Value"), "fieldname": "stock_value", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
+		{"label": _("Value Change"), "fieldname": "stock_value_difference", "fieldtype": "Currency", "width": 110, "options": "Company:company:default_currency"},
 		{"label": _("Voucher Type"), "fieldname": "voucher_type", "width": 110},
 		{"label": _("Voucher #"), "fieldname": "voucher_no", "fieldtype": "Dynamic Link", "options": "voucher_type", "width": 100},
 		{"label": _("Batch"), "fieldname": "batch_no", "fieldtype": "Link", "options": "Batch", "width": 100},
diff --git a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
index 48753b0..7826d34 100644
--- a/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
+++ b/erpnext/stock/report/stock_ledger_invariant_check/stock_ledger_invariant_check.py
@@ -60,6 +60,9 @@
 			fifo_qty += qty
 			fifo_value += qty * rate
 
+		if sle.actual_qty < 0:
+			sle.consumption_rate = sle.stock_value_difference  / sle.actual_qty
+
 		balance_qty += sle.actual_qty
 		balance_stock_value += sle.stock_value_difference
 		if sle.voucher_type == "Stock Reconciliation" and not sle.batch_no:
@@ -90,6 +93,9 @@
 			sle.fifo_stock_diff = sle.fifo_stock_value - sles[idx - 1].fifo_stock_value
 			sle.fifo_difference_diff = sle.fifo_stock_diff - sle.stock_value_difference
 
+		if sle.batch_no:
+			sle.use_batchwise_valuation = frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True)
+
 	return sles
 
 
@@ -135,6 +141,11 @@
 			"options": "Batch",
 		},
 		{
+			"fieldname": "use_batchwise_valuation",
+			"fieldtype": "Check",
+			"label": "Batchwise Valuation",
+		},
+		{
 			"fieldname": "actual_qty",
 			"fieldtype": "Float",
 			"label": "Qty Change",
@@ -145,9 +156,9 @@
 			"label": "Incoming Rate",
 		},
 		{
-			"fieldname": "outgoing_rate",
+			"fieldname": "consumption_rate",
 			"fieldtype": "Float",
-			"label": "Outgoing Rate",
+			"label": "Consumption Rate",
 		},
 		{
 			"fieldname": "qty_after_transaction",
@@ -167,7 +178,7 @@
 		{
 			"fieldname": "stock_queue",
 			"fieldtype": "Data",
-			"label": "FIFO Queue",
+			"label": "FIFO/LIFO Queue",
 		},
 
 		{
diff --git a/erpnext/stock/report/test_reports.py b/erpnext/stock/report/test_reports.py
index 1dcf863..76c2079 100644
--- a/erpnext/stock/report/test_reports.py
+++ b/erpnext/stock/report/test_reports.py
@@ -1,6 +1,8 @@
 import unittest
 from typing import List, Tuple
 
+import frappe
+
 from erpnext.tests.utils import ReportFilters, ReportName, execute_script_report
 
 DEFAULT_FILTERS = {
@@ -10,8 +12,12 @@
 }
 
 
+batch = frappe.db.get_value("Batch", fieldname=["name"], as_dict=True, order_by="creation desc")
+
 REPORT_FILTER_TEST_CASES: List[Tuple[ReportName, ReportFilters]] = [
 	("Stock Ledger", {"_optional": True}),
+	("Stock Ledger", {"batch_no": batch}),
+	("Stock Ledger", {"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"}),
 	("Stock Balance", {"_optional": True}),
 	("Stock Projected Qty", {"_optional": True}),
 	("Batch-Wise Balance History", {}),
@@ -40,6 +46,13 @@
 	("Item Variant Details", {"item": "_Test Variant Item",}),
 	("Total Stock Summary", {"group_by": "warehouse",}),
 	("Batch Item Expiry Status", {}),
+	("Incorrect Stock Value Report", {"company": "_Test Company with perpetual inventory"}),
+	("Incorrect Serial No Valuation", {}),
+	("Incorrect Balance Qty After Transaction", {}),
+	("Supplier-Wise Sales Analytics", {}),
+	("Item Prices", {"items": "Enabled Items only"}),
+	("Delayed Item Report", {"based_on": "Sales Invoice"}),
+	("Delayed Item Report", {"based_on": "Delivery Note"}),
 	("Stock Ageing", {"range1": 30, "range2": 60, "range3": 90, "_optional": True}),
 	("Stock Ledger Invariant Check",
 		{
@@ -60,10 +73,11 @@
 	def test_execute_all_stock_reports(self):
 		"""Test that all script report in stock modules are executable with supported filters"""
 		for report, filter in REPORT_FILTER_TEST_CASES:
-			execute_script_report(
-				report_name=report,
-				module="Stock",
-				filters=filter,
-				default_filters=DEFAULT_FILTERS,
-				optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
-			)
+			with self.subTest(report=report):
+				execute_script_report(
+					report_name=report,
+					module="Stock",
+					filters=filter,
+					default_filters=DEFAULT_FILTERS,
+					optional_filters=OPTIONAL_FILTERS if filter.get("_optional") else None,
+				)
diff --git a/erpnext/stock/spec/README.md b/erpnext/stock/spec/README.md
new file mode 100644
index 0000000..f5a3501
--- /dev/null
+++ b/erpnext/stock/spec/README.md
@@ -0,0 +1,103 @@
+# Implementation notes for Stock Ledger
+
+
+## Important files
+
+- `stock/stock_ledger.py`
+- `controllers/stock_controller.py`
+- `stock/valuation.py`
+
+## What is in an Stock Ledger Entry (SLE)?
+
+Stock Ledger Entry is a single row in the Stock Ledger. It signifies some
+modification of stock for a particular Item in the specified warehouse.
+
+- `item_code`: item for which ledger entry is made
+- `warehouse`: warehouse where inventory is affected
+- `actual_qty`: change in qty
+- `qty_after_transaction`: quantity available after the transaction is processed
+- `incoming_rate`: rate at which inventory was received.
+- `is_cancelled`: if 1 then stock ledger entry is cancelled and should not be used
+for any business logic except for the code that handles cancellation.
+- `posting_date` & `posting_time`: Specify the temporal ordering of stock ledger
+  entries. Ties are broken by `creation` timestamp.
+- `voucher_type`: Many transaction can create SLE, e.g. Stock Entry, Purchase
+  Invoice
+- `voucher_no`: `name` of the transaction that created SLE
+- `voucher_detail_no`: `name` of the child table row from parent transaction
+  that created the SLE.
+- `dependant_sle_voucher_detail_no`: cross-warehouse transfers need this
+  reference in order to update dependent warehouse rates in case of change in
+  rate.
+- `recalculate_rate`: if this is checked in/out rates are recomputed on
+  transactions.
+- `valuation_rate`: current average valuation rate.
+- `stock_value`: current total stock value
+- `stock_value_difference`: stock value difference made between last and current
+  entry. This value is booked in accounting ledger.
+- `stock_queue`: if FIFO/LIFO is used this represents queue/stack maintained for
+  computing incoming rate for inventory getting consumed.
+- `batch_no`: batch no for which stock entry is made; each stock entry can only
+  affect one batch number.
+- `serial_no`: newline separated list of serial numbers that were added (if
+  actual_qty > 0) or else removed. Currently multiple serial nos can have single
+  SLE but this will likely change in future.
+
+
+## Implementation of Stock Ledger
+
+Stock Ledger Entry affects stock of combinations of (item_code, warehouse) and
+optionally batch no if specified. For simplicity, lets avoid batch no. for now.
+
+
+Stock Ledger Entry table stores stock ledger for all combinations of item_code
+and warehouse. So whenever any operations are to be performed on said
+item-warehouse combination stock ledger is filtered and sorted by posting
+datetime. A typical query that will give you individual ledger looks like this:
+
+```sql
+select *
+from `tabStock Ledger Entry` as sle
+where
+    is_cancelled = 0  --- cancelled entries don't affect ledger
+    and item_code = 'item_code' and warehouse = 'warehouse_name'
+order by timestamp(posting_date, posting_time), creation
+```
+
+New entry is just an update to the last entry which is found by looking at last
+row in the filter ledger.
+
+
+### Serial nos
+
+Serial numbers do not follow any valuation method configuration and they are
+consumed at rate they were produced unless they are grouped in which case they
+are consumed at weighted average rate.
+
+
+### Batch Nos
+
+Batches are currently NOT consumed as per batch wise valuation rate, instead
+global FIFO queue for the item is used for valuation rate.
+
+
+## Creation process of SLEs
+
+- SLE creation is usually triggered by Stock Transactions using a method
+  conventionally named `update_stock_ledger()` This might not be defined for
+  stock transaction and could be specified somewhere in inheritance hierarchy of
+  controllers.
+- This method produces SLE objects which are processed by `make_sl_entries` in
+  `stock_ledger.py` which commits the SLE to database.
+- `update_entries_after` class is used to process ONLY the inserted SLE's queue
+  and valuation.
+- The change in qty is propagated to future entries immediately. Valuation and
+  queue for future entries is processed in background using repost item
+  valuation.
+
+
+## Accounting impact
+
+- Accounting impact for stock transaction is handled by `get_gl_entries()`
+  method on controllers. Each transaction has different business logic for
+  booking the accounting impact.
diff --git a/erpnext/stock/spec/reposting.md b/erpnext/stock/spec/reposting.md
new file mode 100644
index 0000000..b0d59fe
--- /dev/null
+++ b/erpnext/stock/spec/reposting.md
@@ -0,0 +1,38 @@
+# Stock Reposting
+
+Stock "reposting" is process of re-processing Stock Ledger Entry and GL Entries
+in event of backdated stock transaction.
+
+*Backdated stock transaction*: Any stock transaction for which some
+item-warehouse combination has a future transactions.
+
+## Why is this required?
+Stock Ledger is stateful, it maintains queue, qty at any
+point in time. So if you do a backdated transaction all future values change,
+queues need to be re-evaluated etc. Watch Nabin and Rohit's conference
+presentation for explanation: https://www.youtube.com/watch?v=mw3WAnekGIM
+
+## How is this implemented?
+Whenever backdated transaction is detected, instead of
+fully processing it while submitting, the processing is queued using "Repost
+Item Valuation" doctype. Every hour a scheduled job runs and processes this
+queue (for up to maximum of 25 minutes)
+
+
+## Queue implementation
+- "Repost item valuation" (RIV) is automatically submitted from backdated transactions. (check stock_controller.py)
+- Draft and cancelled RIV are ignored.
+- Keep filter of "submitted" documents when doing anything with RIVs.
+- The default status is "Queued".
+- When background job runs, it picks the oldest pending reposts and changes the status to "In Progress" and when it finishes it
+changes to "Completed"
+- There are two more status: "Failed" when reposting failed and "Skipped" when reposting is deemed not necessary so it's skipped.
+- technical detail: Entry point for whole process is "repost_entries" function in repost_item_valuation.py
+
+
+## How to identify broken stock data:
+There are 4 major reports for checking broken stock data:
+- Incorrect balance qty after the transaction - to check if the running total of qty isn't correct.
+- Incorrect stock value report - to check incorrect value books in accounts for stock transactions
+- Incorrect serial no valuation -specific to serial nos
+- Stock ledger invariant check - combined report for checking qty, running total, queue, balance value etc
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 6663458..62017e4 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -3,10 +3,9 @@
 
 
 import frappe
-from frappe.utils import cstr, flt, nowdate, nowtime
+from frappe.utils import cstr, flt, now, nowdate, nowtime
 
 from erpnext.controllers.stock_controller import create_repost_item_valuation_entry
-from erpnext.stock.utils import update_bin
 
 
 def repost(only_actual=False, allow_negative_stock=False, allow_zero_rate=False, only_bin=False):
@@ -175,6 +174,7 @@
 			bin.set(field, flt(value))
 			mismatch = True
 
+	bin.modified = now()
 	if mismatch:
 		bin.set_projected_qty()
 		bin.db_update()
@@ -227,8 +227,6 @@
 			"sle_id": sle_doc.name
 		})
 
-		update_bin(args)
-
 		create_repost_item_valuation_entry({
 			"item_code": d[0],
 			"warehouse": d[1],
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 0a7ab40..1b90086 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -3,11 +3,14 @@
 
 import copy
 import json
+from typing import Optional
 
 import frappe
 from frappe import _
 from frappe.model.meta import get_field_precision
+from frappe.query_builder.functions import Sum
 from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdate
+from pypika import CustomFunction
 
 import erpnext
 from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
@@ -16,14 +19,13 @@
 	get_or_make_bin,
 	get_valuation_method,
 )
-from erpnext.stock.valuation import FIFOValuation
+from erpnext.stock.valuation import FIFOValuation, LIFOValuation, round_off_if_near_zero
 
 
 class NegativeStockError(frappe.ValidationError): pass
 class SerialNoExistsInFutureTransaction(frappe.ValidationError):
 	pass
 
-_exceptions = frappe.local('stockledger_exceptions')
 
 def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_voucher=False):
 	from erpnext.controllers.stock_controller import future_sle_exists
@@ -268,11 +270,10 @@
 		self.verbose = verbose
 		self.allow_zero_rate = allow_zero_rate
 		self.via_landed_cost_voucher = via_landed_cost_voucher
-		self.allow_negative_stock = allow_negative_stock \
-			or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
+		self.item_code = args.get("item_code")
+		self.allow_negative_stock = allow_negative_stock or is_negative_stock_allowed(item_code=self.item_code)
 
 		self.args = frappe._dict(args)
-		self.item_code = args.get("item_code")
 		if self.args.sle_id:
 			self.args['name'] = self.args.sle_id
 
@@ -447,6 +448,8 @@
 				self.wh_data.qty_after_transaction = sle.qty_after_transaction
 
 			self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
+		elif sle.batch_no and frappe.db.get_value("Batch", sle.batch_no, "use_batchwise_valuation", cache=True):
+			self.update_batched_values(sle)
 		else:
 			if sle.voucher_type=="Stock Reconciliation" and not sle.batch_no:
 				# assert
@@ -461,11 +464,12 @@
 					self.wh_data.qty_after_transaction += flt(sle.actual_qty)
 					self.wh_data.stock_value = flt(self.wh_data.qty_after_transaction) * flt(self.wh_data.valuation_rate)
 				else:
-					self.update_fifo_values(sle)
-					self.wh_data.qty_after_transaction += flt(sle.actual_qty)
+					self.update_queue_values(sle)
 
 		# rounding as per precision
 		self.wh_data.stock_value = flt(self.wh_data.stock_value, self.precision)
+		if not self.wh_data.qty_after_transaction:
+			self.wh_data.stock_value = 0.0
 		stock_value_difference = self.wh_data.stock_value - self.wh_data.prev_stock_value
 		self.wh_data.prev_stock_value = self.wh_data.stock_value
 
@@ -481,6 +485,7 @@
 		if not self.args.get("sle_id"):
 			self.update_outgoing_rate_on_transaction(sle)
 
+
 	def validate_negative_stock(self, sle):
 		"""
 			validate negative stock for entries current datetime onwards
@@ -629,9 +634,7 @@
 		if not self.wh_data.valuation_rate and sle.voucher_detail_no:
 			allow_zero_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
 			if not allow_zero_rate:
-				self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
-					sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
-					currency=erpnext.get_company_currency(sle.company), company=sle.company)
+				self.wh_data.valuation_rate = self.get_fallback_rate(sle)
 
 	def get_incoming_value_for_serial_nos(self, sle, serial_nos):
 		# get rate from serial nos within same company
@@ -697,42 +700,70 @@
 			if not self.wh_data.valuation_rate and sle.voucher_detail_no:
 				allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
 				if not allow_zero_valuation_rate:
-					self.wh_data.valuation_rate = get_valuation_rate(sle.item_code, sle.warehouse,
-						sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
-						currency=erpnext.get_company_currency(sle.company), company=sle.company)
+					self.wh_data.valuation_rate = self.get_fallback_rate(sle)
 
-	def update_fifo_values(self, sle):
+	def update_queue_values(self, sle):
 		incoming_rate = flt(sle.incoming_rate)
 		actual_qty = flt(sle.actual_qty)
 		outgoing_rate = flt(sle.outgoing_rate)
 
-		fifo_queue = FIFOValuation(self.wh_data.stock_queue)
+		self.wh_data.qty_after_transaction = round_off_if_near_zero(self.wh_data.qty_after_transaction + actual_qty)
+
+		if self.valuation_method == "LIFO":
+			stock_queue = LIFOValuation(self.wh_data.stock_queue)
+		else:
+			stock_queue = FIFOValuation(self.wh_data.stock_queue)
+
+		_prev_qty, prev_stock_value = stock_queue.get_total_stock_and_value()
+
 		if actual_qty > 0:
-			fifo_queue.add_stock(qty=actual_qty, rate=incoming_rate)
+			stock_queue.add_stock(qty=actual_qty, rate=incoming_rate)
 		else:
 			def rate_generator() -> float:
 				allow_zero_valuation_rate = self.check_if_allow_zero_valuation_rate(sle.voucher_type, sle.voucher_detail_no)
 				if not allow_zero_valuation_rate:
-					return get_valuation_rate(sle.item_code, sle.warehouse,
-						sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
-						currency=erpnext.get_company_currency(sle.company), company=sle.company)
+					return self.get_fallback_rate(sle)
 				else:
 					return 0.0
 
-			fifo_queue.remove_stock(qty=abs(actual_qty), outgoing_rate=outgoing_rate, rate_generator=rate_generator)
+			stock_queue.remove_stock(qty=abs(actual_qty), outgoing_rate=outgoing_rate, rate_generator=rate_generator)
 
-		stock_qty, stock_value = fifo_queue.get_total_stock_and_value()
+		_qty, stock_value = stock_queue.get_total_stock_and_value()
 
-		self.wh_data.stock_queue = fifo_queue.get_state()
-		self.wh_data.stock_value = stock_value
-		if stock_qty:
-			self.wh_data.valuation_rate = stock_value / stock_qty
+		stock_value_difference = stock_value - prev_stock_value
 
+		self.wh_data.stock_queue = stock_queue.state
+		self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference)
 
 		if not self.wh_data.stock_queue:
 			self.wh_data.stock_queue.append([0, sle.incoming_rate or sle.outgoing_rate or self.wh_data.valuation_rate])
 
+		if self.wh_data.qty_after_transaction:
+			self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
 
+	def update_batched_values(self, sle):
+		incoming_rate = flt(sle.incoming_rate)
+		actual_qty = flt(sle.actual_qty)
+
+		self.wh_data.qty_after_transaction = round_off_if_near_zero(self.wh_data.qty_after_transaction + actual_qty)
+
+		if actual_qty > 0:
+			stock_value_difference = incoming_rate * actual_qty
+		else:
+			outgoing_rate = get_batch_incoming_rate(item_code=sle.item_code,
+					warehouse=sle.warehouse, batch_no=sle.batch_no, posting_date=sle.posting_date,
+					posting_time=sle.posting_time, creation=sle.creation)
+			if outgoing_rate is None:
+				# This can *only* happen if qty available for the batch is zero.
+				# in such case fall back various other rates.
+				# future entries will correct the overall accounting as each
+				# batch individually uses moving average rates.
+				outgoing_rate = self.get_fallback_rate(sle)
+			stock_value_difference = outgoing_rate * actual_qty
+
+		self.wh_data.stock_value = round_off_if_near_zero(self.wh_data.stock_value + stock_value_difference)
+		if self.wh_data.qty_after_transaction:
+			self.wh_data.valuation_rate = self.wh_data.stock_value / self.wh_data.qty_after_transaction
 
 	def check_if_allow_zero_valuation_rate(self, voucher_type, voucher_detail_no):
 		ref_item_dt = ""
@@ -747,6 +778,13 @@
 		else:
 			return 0
 
+	def get_fallback_rate(self, sle) -> float:
+		"""When exact incoming rate isn't available use any of other "average" rates as fallback.
+			This should only get used for negative stock."""
+		return get_valuation_rate(sle.item_code, sle.warehouse,
+			sle.voucher_type, sle.voucher_no, self.allow_zero_rate,
+			currency=erpnext.get_company_currency(sle.company), company=sle.company, batch_no=sle.batch_no)
+
 	def get_sle_before_datetime(self, args):
 		"""get previous stock ledger entry before current time-bucket"""
 		sle = get_stock_ledger_entries(args, "<", "desc", "limit 1", for_update=False)
@@ -893,22 +931,72 @@
 		['item_code', 'warehouse', 'posting_date', 'posting_time', 'timestamp(posting_date, posting_time) as timestamp'],
 		as_dict=1)
 
+def get_batch_incoming_rate(item_code, warehouse, batch_no, posting_date, posting_time, creation=None):
+
+	Timestamp = CustomFunction('timestamp', ['date', 'time'])
+
+	sle = frappe.qb.DocType("Stock Ledger Entry")
+
+	timestamp_condition = (Timestamp(sle.posting_date, sle.posting_time) < Timestamp(posting_date, posting_time))
+	if creation:
+		timestamp_condition |= (
+				(Timestamp(sle.posting_date, sle.posting_time) == Timestamp(posting_date, posting_time))
+				& (sle.creation < creation)
+			)
+
+	batch_details = (
+		frappe.qb
+			.from_(sle)
+			.select(
+				Sum(sle.stock_value_difference).as_("batch_value"),
+				Sum(sle.actual_qty).as_("batch_qty")
+			)
+			.where(
+				(sle.item_code == item_code)
+				& (sle.warehouse == warehouse)
+				& (sle.batch_no == batch_no)
+				& (sle.is_cancelled == 0)
+			)
+			.where(timestamp_condition)
+	).run(as_dict=True)
+
+	if batch_details and batch_details[0].batch_qty:
+		return batch_details[0].batch_value / batch_details[0].batch_qty
+
+
 def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
-	allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True):
+	allow_zero_rate=False, currency=None, company=None, raise_error_if_no_rate=True, batch_no=None):
 
 	if not company:
 		company =  frappe.get_cached_value("Warehouse", warehouse, "company")
 
+	last_valuation_rate = None
+
+	# Get moving average rate of a specific batch number
+	if warehouse and batch_no and frappe.db.get_value("Batch", batch_no, "use_batchwise_valuation"):
+		last_valuation_rate = frappe.db.sql("""
+			select sum(stock_value_difference) / sum(actual_qty)
+			from `tabStock Ledger Entry`
+			where
+				item_code = %s
+				AND warehouse = %s
+				AND batch_no = %s
+				AND is_cancelled = 0
+				AND NOT (voucher_no = %s AND voucher_type = %s)
+			""",
+			(item_code, warehouse, batch_no, voucher_no, voucher_type))
+
 	# Get valuation rate from last sle for the same item and warehouse
-	last_valuation_rate = frappe.db.sql("""select valuation_rate
-		from `tabStock Ledger Entry` force index (item_warehouse)
-		where
-			item_code = %s
-			AND warehouse = %s
-			AND valuation_rate >= 0
-			AND is_cancelled = 0
-			AND NOT (voucher_no = %s AND voucher_type = %s)
-		order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
+	if not last_valuation_rate or last_valuation_rate[0][0] is None:
+		last_valuation_rate = frappe.db.sql("""select valuation_rate
+			from `tabStock Ledger Entry` force index (item_warehouse)
+			where
+				item_code = %s
+				AND warehouse = %s
+				AND valuation_rate >= 0
+				AND is_cancelled = 0
+				AND NOT (voucher_no = %s AND voucher_type = %s)
+			order by posting_date desc, posting_time desc, name desc limit 1""", (item_code, warehouse, voucher_no, voucher_type))
 
 	if not last_valuation_rate:
 		# Get valuation rate from last sle for the item against any warehouse
@@ -1045,10 +1133,7 @@
 		)"""
 
 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 allow_negative_stock:
+	if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code):
 		return
 	if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
 		return
@@ -1117,3 +1202,11 @@
 			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
 		limit 1
 	""", args, as_dict=1)
+
+
+def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
+	if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
+		return True
+	if item_code and cint(frappe.db.get_value("Item", item_code, "allow_negative_stock", cache=True)):
+		return True
+	return False
diff --git a/erpnext/stock/tests/test_valuation.py b/erpnext/stock/tests/test_valuation.py
index 85788ba..b64ff8e 100644
--- a/erpnext/stock/tests/test_valuation.py
+++ b/erpnext/stock/tests/test_valuation.py
@@ -1,16 +1,21 @@
+import json
 import unittest
 
+import frappe
+from frappe.tests.utils import FrappeTestCase
 from hypothesis import given
 from hypothesis import strategies as st
 
-from erpnext.stock.valuation import FIFOValuation, _round_off_if_near_zero
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+from erpnext.stock.valuation import FIFOValuation, LIFOValuation, round_off_if_near_zero
 
 qty_gen = st.floats(min_value=-1e6, max_value=1e6)
 value_gen = st.floats(min_value=1, max_value=1e6)
 stock_queue_generator = st.lists(st.tuples(qty_gen, value_gen), min_size=10)
 
 
-class TestFifoValuation(unittest.TestCase):
+class TestFIFOValuation(unittest.TestCase):
 
 	def setUp(self):
 		self.queue = FIFOValuation([])
@@ -108,11 +113,11 @@
 		self.assertTotalQty(0)
 
 	def test_rounding_off_near_zero(self):
-		self.assertEqual(_round_off_if_near_zero(0), 0)
-		self.assertEqual(_round_off_if_near_zero(1), 1)
-		self.assertEqual(_round_off_if_near_zero(-1), -1)
-		self.assertEqual(_round_off_if_near_zero(-1e-8), 0)
-		self.assertEqual(_round_off_if_near_zero(1e-8), 0)
+		self.assertEqual(round_off_if_near_zero(0), 0)
+		self.assertEqual(round_off_if_near_zero(1), 1)
+		self.assertEqual(round_off_if_near_zero(-1), -1)
+		self.assertEqual(round_off_if_near_zero(-1e-8), 0)
+		self.assertEqual(round_off_if_near_zero(1e-8), 0)
 
 	def test_totals(self):
 		self.queue.add_stock(1, 10)
@@ -164,3 +169,184 @@
 				total_value -= sum(q * r for q, r in consumed)
 			self.assertTotalQty(total_qty)
 			self.assertTotalValue(total_value)
+
+
+class TestLIFOValuation(unittest.TestCase):
+
+	def setUp(self):
+		self.stack = LIFOValuation([])
+
+	def tearDown(self):
+		qty, value = self.stack.get_total_stock_and_value()
+		self.assertTotalQty(qty)
+		self.assertTotalValue(value)
+
+	def assertTotalQty(self, qty):
+		self.assertAlmostEqual(sum(q for q, _ in self.stack), qty, msg=f"stack: {self.stack}", places=4)
+
+	def assertTotalValue(self, value):
+		self.assertAlmostEqual(sum(q * r for q, r in self.stack), value, msg=f"stack: {self.stack}", places=2)
+
+	def test_simple_addition(self):
+		self.stack.add_stock(1, 10)
+		self.assertTotalQty(1)
+
+	def test_merge_new_stock(self):
+		self.stack.add_stock(1, 10)
+		self.stack.add_stock(1, 10)
+		self.assertEqual(self.stack, [[2, 10]])
+
+	def test_simple_removal(self):
+		self.stack.add_stock(1, 10)
+		self.stack.remove_stock(1)
+		self.assertTotalQty(0)
+
+	def test_adding_negative_stock_keeps_rate(self):
+		self.stack = LIFOValuation([[-5.0, 100]])
+		self.stack.add_stock(1, 10)
+		self.assertEqual(self.stack, [[-4, 100]])
+
+	def test_adding_negative_stock_updates_rate(self):
+		self.stack = LIFOValuation([[-5.0, 100]])
+		self.stack.add_stock(6, 10)
+		self.assertEqual(self.stack, [[1, 10]])
+
+	def test_rounding_off(self):
+		self.stack.add_stock(1.0, 1.0)
+		self.stack.remove_stock(1.0 - 1e-9)
+		self.assertTotalQty(0)
+
+	def test_lifo_consumption(self):
+		self.stack.add_stock(10, 10)
+		self.stack.add_stock(10, 20)
+		consumed = self.stack.remove_stock(15)
+		self.assertEqual(consumed, [[10, 20], [5, 10]])
+		self.assertTotalQty(5)
+
+	def test_lifo_consumption_going_negative(self):
+		self.stack.add_stock(10, 10)
+		self.stack.add_stock(10, 20)
+		consumed = self.stack.remove_stock(25)
+		self.assertEqual(consumed, [[10, 20], [10, 10], [5, 10]])
+		self.assertTotalQty(-5)
+
+	def test_lifo_consumption_multiple(self):
+		self.stack.add_stock(1, 1)
+		self.stack.add_stock(2, 2)
+		consumed = self.stack.remove_stock(1)
+		self.assertEqual(consumed, [[1, 2]])
+
+		self.stack.add_stock(3, 3)
+		consumed = self.stack.remove_stock(4)
+		self.assertEqual(consumed, [[3, 3], [1, 2]])
+
+		self.stack.add_stock(4, 4)
+		consumed = self.stack.remove_stock(5)
+		self.assertEqual(consumed, [[4, 4], [1, 1]])
+
+		self.stack.add_stock(5, 5)
+		consumed = self.stack.remove_stock(5)
+		self.assertEqual(consumed, [[5, 5]])
+
+
+	@given(stock_queue_generator)
+	def test_lifo_qty_hypothesis(self, stock_stack):
+		self.stack = LIFOValuation([])
+		total_qty = 0
+
+		for qty, rate in stock_stack:
+			if qty == 0:
+				continue
+			if qty > 0:
+				self.stack.add_stock(qty, rate)
+				total_qty += qty
+			else:
+				qty = abs(qty)
+				consumed = self.stack.remove_stock(qty)
+				self.assertAlmostEqual(qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}")
+				total_qty -= qty
+			self.assertTotalQty(total_qty)
+
+	@given(stock_queue_generator)
+	def test_lifo_qty_value_nonneg_hypothesis(self, stock_stack):
+		self.stack = LIFOValuation([])
+		total_qty = 0.0
+		total_value = 0.0
+
+		for qty, rate in stock_stack:
+			# don't allow negative stock
+			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+				continue
+			if qty > 0:
+				self.stack.add_stock(qty, rate)
+				total_qty += qty
+				total_value += qty * rate
+			else:
+				qty = abs(qty)
+				consumed = self.stack.remove_stock(qty)
+				self.assertAlmostEqual(qty, sum(q for q, _ in consumed), msg=f"incorrect consumption {consumed}")
+				total_qty -= qty
+				total_value -= sum(q * r for q, r in consumed)
+			self.assertTotalQty(total_qty)
+			self.assertTotalValue(total_value)
+
+class TestLIFOValuationSLE(FrappeTestCase):
+	ITEM_CODE = "_Test LIFO item"
+	WAREHOUSE = "_Test Warehouse - _TC"
+
+	@classmethod
+	def setUpClass(cls) -> None:
+		super().setUpClass()
+		make_item(cls.ITEM_CODE, {"valuation_method": "LIFO"})
+
+	def _make_stock_entry(self, qty, rate=None):
+		kwargs = {
+			"item_code": self.ITEM_CODE,
+			"from_warehouse" if qty < 0 else "to_warehouse": self.WAREHOUSE,
+			"rate": rate,
+			"qty": abs(qty),
+		}
+		return make_stock_entry(**kwargs)
+
+	def assertStockQueue(self, se, expected_queue):
+		sle_name = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": se.name, "is_cancelled": 0, "voucher_type": "Stock Entry"})
+		sle = frappe.get_doc("Stock Ledger Entry", sle_name)
+
+		stock_queue = json.loads(sle.stock_queue)
+
+		total_qty, total_value = LIFOValuation(stock_queue).get_total_stock_and_value()
+		self.assertEqual(sle.qty_after_transaction, total_qty)
+		self.assertEqual(sle.stock_value, total_value)
+
+		if total_qty > 0:
+			self.assertEqual(stock_queue, expected_queue)
+
+
+	def test_lifo_values(self):
+
+		in1 = self._make_stock_entry(1, 1)
+		self.assertStockQueue(in1, [[1, 1]])
+
+		in2 = self._make_stock_entry(2, 2)
+		self.assertStockQueue(in2, [[1, 1], [2, 2]])
+
+		out1 = self._make_stock_entry(-1)
+		self.assertStockQueue(out1, [[1, 1], [1, 2]])
+
+		in3 = self._make_stock_entry(3, 3)
+		self.assertStockQueue(in3, [[1, 1], [1, 2], [3, 3]])
+
+		out2 = self._make_stock_entry(-4)
+		self.assertStockQueue(out2, [[1, 1]])
+
+		in4 = self._make_stock_entry(4, 4)
+		self.assertStockQueue(in4, [[1, 1], [4,4]])
+
+		out3 = self._make_stock_entry(-5)
+		self.assertStockQueue(out3, [])
+
+		in5 = self._make_stock_entry(5, 5)
+		self.assertStockQueue(in5, [[5, 5]])
+
+		out5 = self._make_stock_entry(-5)
+		self.assertStockQueue(out5, [])
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 3c70b41..f85a04f 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -9,6 +9,7 @@
 from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
 
 import erpnext
+from erpnext.stock.valuation import FIFOValuation, LIFOValuation
 
 
 class InvalidWarehouseCompany(frappe.ValidationError): pass
@@ -103,7 +104,7 @@
 			serial_nos = get_serial_nos_data_after_transactions(args)
 
 			return ((last_entry.qty_after_transaction, last_entry.valuation_rate, serial_nos)
-				if last_entry else (0.0, 0.0, 0.0))
+				if last_entry else (0.0, 0.0, None))
 		else:
 			return (last_entry.qty_after_transaction, last_entry.valuation_rate) if last_entry else (0.0, 0.0)
 	else:
@@ -176,13 +177,7 @@
 def get_bin(item_code, warehouse):
 	bin = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse})
 	if not bin:
-		bin_obj = frappe.get_doc({
-			"doctype": "Bin",
-			"item_code": item_code,
-			"warehouse": warehouse,
-		})
-		bin_obj.flags.ignore_permissions = 1
-		bin_obj.insert()
+		bin_obj = _create_bin(item_code, warehouse)
 	else:
 		bin_obj = frappe.get_doc('Bin', bin, for_update=True)
 	bin_obj.flags.ignore_permissions = True
@@ -192,53 +187,65 @@
 	bin_record = frappe.db.get_value('Bin', {'item_code': item_code, 'warehouse': warehouse})
 
 	if not bin_record:
-		bin_obj = frappe.get_doc({
-			"doctype": "Bin",
-			"item_code": item_code,
-			"warehouse": warehouse,
-		})
-		bin_obj.flags.ignore_permissions = 1
-		bin_obj.insert()
+		bin_obj = _create_bin(item_code, warehouse)
 		bin_record = bin_obj.name
-
 	return bin_record
 
-def update_bin(args, allow_negative_stock=False, via_landed_cost_voucher=False):
-	"""WARNING: This function is deprecated. Inline this function instead of using it."""
-	from erpnext.stock.doctype.bin.bin import update_stock
-	is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item')
-	if is_stock_item:
-		bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse"))
-		update_stock(bin_name, args, allow_negative_stock, via_landed_cost_voucher)
-	else:
-		frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))
+def _create_bin(item_code, warehouse):
+	"""Create a bin and take care of concurrent inserts."""
+
+	bin_creation_savepoint = "create_bin"
+	try:
+		frappe.db.savepoint(bin_creation_savepoint)
+		bin_obj = frappe.get_doc(doctype="Bin", item_code=item_code, warehouse=warehouse)
+		bin_obj.flags.ignore_permissions = 1
+		bin_obj.insert()
+	except frappe.UniqueValidationError:
+		frappe.db.rollback(save_point=bin_creation_savepoint)  # preserve transaction in postgres
+		bin_obj = frappe.get_last_doc("Bin", {"item_code": item_code, "warehouse": warehouse})
+
+	return bin_obj
 
 @frappe.whitelist()
 def get_incoming_rate(args, raise_error_if_no_rate=True):
 	"""Get Incoming Rate based on valuation method"""
-	from erpnext.stock.stock_ledger import get_previous_sle, get_valuation_rate
+	from erpnext.stock.stock_ledger import (
+		get_batch_incoming_rate,
+		get_previous_sle,
+		get_valuation_rate,
+	)
 	if isinstance(args, str):
 		args = json.loads(args)
 
-	in_rate = 0
+	voucher_no = args.get('voucher_no') or args.get('name')
+
+	in_rate = None
 	if (args.get("serial_no") or "").strip():
 		in_rate = get_avg_purchase_rate(args.get("serial_no"))
+	elif args.get("batch_no") and \
+			frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True):
+		in_rate = get_batch_incoming_rate(
+			item_code=args.get('item_code'),
+			warehouse=args.get('warehouse'),
+			batch_no=args.get("batch_no"),
+			posting_date=args.get("posting_date"),
+			posting_time=args.get("posting_time"),
+		)
 	else:
 		valuation_method = get_valuation_method(args.get("item_code"))
 		previous_sle = get_previous_sle(args)
-		if valuation_method == 'FIFO':
+		if valuation_method in ('FIFO', 'LIFO'):
 			if previous_sle:
 				previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]') or '[]')
-				in_rate = get_fifo_rate(previous_stock_queue, args.get("qty") or 0) if previous_stock_queue else 0
+				in_rate = _get_fifo_lifo_rate(previous_stock_queue, args.get("qty") or 0, valuation_method) if previous_stock_queue else 0
 		elif valuation_method == 'Moving Average':
 			in_rate = previous_sle.get('valuation_rate') or 0
 
-	if not in_rate:
-		voucher_no = args.get('voucher_no') or args.get('name')
+	if in_rate is None:
 		in_rate = get_valuation_rate(args.get('item_code'), args.get('warehouse'),
 			args.get('voucher_type'), voucher_no, args.get('allow_zero_valuation'),
 			currency=erpnext.get_company_currency(args.get('company')), company=args.get('company'),
-			raise_error_if_no_rate=raise_error_if_no_rate)
+			raise_error_if_no_rate=raise_error_if_no_rate, batch_no=args.get("batch_no"))
 
 	return flt(in_rate)
 
@@ -254,34 +261,30 @@
 	"""get valuation method from item or default"""
 	val_method = frappe.db.get_value('Item', item_code, 'valuation_method', cache=True)
 	if not val_method:
-		val_method = frappe.db.get_value("Stock Settings", None, "valuation_method") or "FIFO"
+		val_method = frappe.db.get_value("Stock Settings", None, "valuation_method", cache=True) or "FIFO"
 	return val_method
 
 def get_fifo_rate(previous_stock_queue, qty):
 	"""get FIFO (average) Rate from Queue"""
-	if flt(qty) >= 0:
-		total = sum(f[0] for f in previous_stock_queue)
-		return sum(flt(f[0]) * flt(f[1]) for f in previous_stock_queue) / flt(total) if total else 0.0
-	else:
-		available_qty_for_outgoing, outgoing_cost = 0, 0
-		qty_to_pop = abs(flt(qty))
-		while qty_to_pop and previous_stock_queue:
-			batch = previous_stock_queue[0]
-			if 0 < batch[0] <= qty_to_pop:
-				# if batch qty > 0
-				# not enough or exactly same qty in current batch, clear batch
-				available_qty_for_outgoing += flt(batch[0])
-				outgoing_cost += flt(batch[0]) * flt(batch[1])
-				qty_to_pop -= batch[0]
-				previous_stock_queue.pop(0)
-			else:
-				# all from current batch
-				available_qty_for_outgoing += flt(qty_to_pop)
-				outgoing_cost += flt(qty_to_pop) * flt(batch[1])
-				batch[0] -= qty_to_pop
-				qty_to_pop = 0
+	return _get_fifo_lifo_rate(previous_stock_queue, qty, "FIFO")
 
-		return outgoing_cost / available_qty_for_outgoing
+def get_lifo_rate(previous_stock_queue, qty):
+	"""get LIFO (average) Rate from Queue"""
+	return _get_fifo_lifo_rate(previous_stock_queue, qty, "LIFO")
+
+
+def _get_fifo_lifo_rate(previous_stock_queue, qty, method):
+	ValuationKlass = LIFOValuation if method == "LIFO" else FIFOValuation
+
+	stock_queue = ValuationKlass(previous_stock_queue)
+	if flt(qty) >= 0:
+		total_qty, total_value = stock_queue.get_total_stock_and_value()
+		return total_value / total_qty if total_qty else 0.0
+	else:
+		popped_bins = stock_queue.remove_stock(abs(flt(qty)))
+
+		total_qty, total_value = ValuationKlass(popped_bins).get_total_stock_and_value()
+		return total_value / total_qty if total_qty else 0.0
 
 def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
 	"""split serial nos, validate and return list of valid serial nos"""
@@ -419,6 +422,19 @@
 	if reposting_in_progress:
 		frappe.msgprint(_("Item valuation reposting in progress. Report might show incorrect item valuation."), alert=1)
 
+
+def calculate_mapped_packed_items_return(return_doc):
+	parent_items = set([item.parent_item for item in return_doc.packed_items])
+	against_doc = frappe.get_doc(return_doc.doctype, return_doc.return_against)
+
+	for original_bundle, returned_bundle in zip(against_doc.items, return_doc.items):
+		if original_bundle.item_code in parent_items:
+			for returned_packed_item, original_packed_item in zip(return_doc.packed_items, against_doc.packed_items):
+				if returned_packed_item.parent_item == original_bundle.item_code:
+					returned_packed_item.parent_detail_docname = returned_bundle.name
+					returned_packed_item.qty = (original_packed_item.qty / original_bundle.qty) * returned_bundle.qty
+
+
 def check_pending_reposting(posting_date: str, throw_error: bool = True) -> bool:
 	"""Check if there are pending reposting job till the specified posting date."""
 
diff --git a/erpnext/stock/valuation.py b/erpnext/stock/valuation.py
index 45c5083..e2bd1ad 100644
--- a/erpnext/stock/valuation.py
+++ b/erpnext/stock/valuation.py
@@ -1,15 +1,54 @@
+from abc import ABC, abstractmethod, abstractproperty
 from typing import Callable, List, NewType, Optional, Tuple
 
 from frappe.utils import flt
 
-FifoBin = NewType("FifoBin", List[float])
+StockBin = NewType("StockBin", List[float])  # [[qty, rate], ...]
 
 # Indexes of values inside FIFO bin 2-tuple
 QTY = 0
 RATE = 1
 
 
-class FIFOValuation:
+class BinWiseValuation(ABC):
+
+	@abstractmethod
+	def add_stock(self, qty: float, rate: float) -> None:
+		pass
+
+	@abstractmethod
+	def remove_stock(
+		self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
+	) -> List[StockBin]:
+		pass
+
+	@abstractproperty
+	def state(self) -> List[StockBin]:
+		pass
+
+	def get_total_stock_and_value(self) -> Tuple[float, float]:
+		total_qty = 0.0
+		total_value = 0.0
+
+		for qty, rate in self.state:
+			total_qty += flt(qty)
+			total_value += flt(qty) * flt(rate)
+
+		return round_off_if_near_zero(total_qty), round_off_if_near_zero(total_value)
+
+	def __repr__(self):
+		return str(self.state)
+
+	def __iter__(self):
+		return iter(self.state)
+
+	def __eq__(self, other):
+		if isinstance(other, list):
+			return self.state == other
+		return type(self) == type(other) and self.state == other.state
+
+
+class FIFOValuation(BinWiseValuation):
 	"""Valuation method where a queue of all the incoming stock is maintained.
 
 	New stock is added at end of the queue.
@@ -24,34 +63,14 @@
 	# ref: https://docs.python.org/3/reference/datamodel.html#slots
 	__slots__ = ["queue",]
 
-	def __init__(self, state: Optional[List[FifoBin]]):
-		self.queue: List[FifoBin] = state if state is not None else []
+	def __init__(self, state: Optional[List[StockBin]]):
+		self.queue: List[StockBin] = state if state is not None else []
 
-	def __repr__(self):
-		return str(self.queue)
-
-	def __iter__(self):
-		return iter(self.queue)
-
-	def __eq__(self, other):
-		if isinstance(other, list):
-			return self.queue == other
-		return self.queue == other.queue
-
-	def get_state(self) -> List[FifoBin]:
+	@property
+	def state(self) -> List[StockBin]:
 		"""Get current state of queue."""
 		return self.queue
 
-	def get_total_stock_and_value(self) -> Tuple[float, float]:
-		total_qty = 0.0
-		total_value = 0.0
-
-		for qty, rate in self.queue:
-			total_qty += flt(qty)
-			total_value += flt(qty) * flt(rate)
-
-		return _round_off_if_near_zero(total_qty), _round_off_if_near_zero(total_value)
-
 	def add_stock(self, qty: float, rate: float) -> None:
 		"""Update fifo queue with new stock.
 
@@ -78,7 +97,7 @@
 
 	def remove_stock(
 		self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
-	) -> List[FifoBin]:
+	) -> List[StockBin]:
 		"""Remove stock from the queue and return popped bins.
 
 		args:
@@ -117,7 +136,7 @@
 			fifo_bin = self.queue[index]
 			if qty >= fifo_bin[QTY]:
 				# consume current bin
-				qty = _round_off_if_near_zero(qty - fifo_bin[QTY])
+				qty = round_off_if_near_zero(qty - fifo_bin[QTY])
 				to_consume = self.queue.pop(index)
 				consumed_bins.append(list(to_consume))
 
@@ -129,14 +148,109 @@
 					break
 			else:
 				# qty found in current bin consume it and exit
-				fifo_bin[QTY] = _round_off_if_near_zero(fifo_bin[QTY] - qty)
+				fifo_bin[QTY] = round_off_if_near_zero(fifo_bin[QTY] - qty)
 				consumed_bins.append([qty, fifo_bin[RATE]])
 				qty = 0
 
 		return consumed_bins
 
 
-def _round_off_if_near_zero(number: float, precision: int = 7) -> float:
+class LIFOValuation(BinWiseValuation):
+	"""Valuation method where a *stack* of all the incoming stock is maintained.
+
+	New stock is added at top of the stack.
+	Qty consumption happens on Last In First Out basis.
+
+	Stack is implemented using "bins" of [qty, rate].
+
+	ref: https://en.wikipedia.org/wiki/FIFO_and_LIFO_accounting
+	Implementation detail: appends and pops both at end of list.
+	"""
+
+	# specifying the attributes to save resources
+	# ref: https://docs.python.org/3/reference/datamodel.html#slots
+	__slots__ = ["stack",]
+
+	def __init__(self, state: Optional[List[StockBin]]):
+		self.stack: List[StockBin] = state if state is not None else []
+
+	@property
+	def state(self) -> List[StockBin]:
+		"""Get current state of stack."""
+		return self.stack
+
+	def add_stock(self, qty: float, rate: float) -> None:
+		"""Update lifo stack with new stock.
+
+			args:
+				qty: new quantity to add
+				rate: incoming rate of new quantity.
+
+			Behaviour of this is same as FIFO valuation.
+		"""
+		if not len(self.stack):
+			self.stack.append([0, 0])
+
+		# last row has the same rate, merge new bin.
+		if self.stack[-1][RATE] == rate:
+			self.stack[-1][QTY] += qty
+		else:
+			# Item has a positive balance qty, add new entry
+			if self.stack[-1][QTY] > 0:
+				self.stack.append([qty, rate])
+			else:  # negative balance qty
+				qty = self.stack[-1][QTY] + qty
+				if qty > 0:  # new balance qty is positive
+					self.stack[-1] = [qty, rate]
+				else:  # new balance qty is still negative, maintain same rate
+					self.stack[-1][QTY] = qty
+
+
+	def remove_stock(
+		self, qty: float, outgoing_rate: float = 0.0, rate_generator: Callable[[], float] = None
+	) -> List[StockBin]:
+		"""Remove stock from the stack and return popped bins.
+
+		args:
+			qty: quantity to remove
+			rate: outgoing rate - ignored. Kept for backwards compatibility.
+			rate_generator: function to be called if stack is not found and rate is required.
+		"""
+		if not rate_generator:
+			rate_generator = lambda : 0.0  # noqa
+
+		consumed_bins = []
+		while qty:
+			if not len(self.stack):
+				# rely on rate generator.
+				self.stack.append([0, rate_generator()])
+
+			# start at the end.
+			index = -1
+
+			stock_bin = self.stack[index]
+			if qty >= stock_bin[QTY]:
+				# consume current bin
+				qty = round_off_if_near_zero(qty - stock_bin[QTY])
+				to_consume = self.stack.pop(index)
+				consumed_bins.append(list(to_consume))
+
+				if not self.stack and qty:
+					# stock finished, qty still remains to be withdrawn
+					# negative stock, keep in as a negative bin
+					self.stack.append([-qty, outgoing_rate or stock_bin[RATE]])
+					consumed_bins.append([qty, outgoing_rate or stock_bin[RATE]])
+					break
+			else:
+				# qty found in current bin consume it and exit
+				stock_bin[QTY] = round_off_if_near_zero(stock_bin[QTY] - qty)
+				consumed_bins.append([qty, stock_bin[RATE]])
+				qty = 0
+
+		return consumed_bins
+
+
+def round_off_if_near_zero(number: float, precision: int = 7) -> float:
 	"""Rounds off the number to zero only if number is close to zero for decimal
 	specified in precision. Precision defaults to 7.
 	"""
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index de8f506..526b6aa 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -701,7 +701,7 @@
 	update_response_and_resolution_metrics(parent, for_resolution)
 	update_agreement_status(parent, for_resolution)
 
-	parent.save()
+	parent.save(ignore_permissions=True)
 
 
 def reset_expected_response_and_resolution(doc):
diff --git a/erpnext/templates/generators/item/item.html b/erpnext/templates/generators/item/item.html
index 17f6880..4070d40 100644
--- a/erpnext/templates/generators/item/item.html
+++ b/erpnext/templates/generators/item/item.html
@@ -1,4 +1,5 @@
 {% extends "templates/web.html" %}
+{% from "erpnext/templates/includes/macros.html" import recommended_item_row %}
 
 {% block title %} {{ title }} {% endblock %}
 
@@ -9,25 +10,70 @@
 {% endblock %}
 
 {% block page_content %}
-<div class="product-container">
+<div class="product-container item-main">
 	{% from "erpnext/templates/includes/macros.html" import product_image %}
 	<div class="item-content">
 		<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
+			<!-- Image, Description, Add to Cart -->
 			<div class="row mb-5">
 				{% include "templates/generators/item/item_image.html" %}
 				{% include "templates/generators/item/item_details.html" %}
 			</div>
-
-			{% include "templates/generators/item/item_specifications.html" %}
-
-			{{ doc.website_content or '' }}
 		</div>
 	</div>
 </div>
+
+<!-- Additional Info/Reviews, Recommendations -->
+<div class="d-flex">
+	{% set show_recommended_items = recommended_items and shopping_cart.cart_settings.enable_recommendations %}
+	{% set info_col = 'col-9' if show_recommended_items else 'col-12' %}
+
+	{% set padding_top = 'pt-0' if (show_tabs and tabs) else '' %}
+
+	<div class="product-container mt-4 {{ padding_top }} {{ info_col }}">
+		<div class="item-content {{ 'mt-minus-2' if (show_tabs and tabs) else '' }}">
+			<div class="product-page-content" itemscope itemtype="http://schema.org/Product">
+				<!-- Product Specifications Table Section -->
+				{% if show_tabs and tabs %}
+					<div class="category-tabs">
+						<!-- tabs -->
+							{{ web_block("Section with Tabs", values=tabs, add_container=0,
+								add_top_padding=0, add_bottom_padding=0)
+							}}
+					</div>
+				{% elif website_specifications %}
+					{% include "templates/generators/item/item_specifications.html"%}
+				{% endif %}
+
+				<!-- Advanced Custom Website Content -->
+				{{ doc.website_content or '' }}
+
+				<!-- Reviews and Comments -->
+				{% if shopping_cart.cart_settings.enable_reviews and not doc.has_variants %}
+					{% include "templates/generators/item/item_reviews.html"%}
+				{% endif %}
+			</div>
+		</div>
+	</div>
+
+	<!-- Recommended Items -->
+	{% if show_recommended_items %}
+		<div class="mt-4 col-3 recommended-item-section">
+			<span class="recommendation-header">Recommended</span>
+			<div class="product-container mt-2 recommendation-container">
+				{% for item in recommended_items %}
+					{{ recommended_item_row(item) }}
+				{% endfor %}
+			</div>
+		</div>
+	{% endif %}
+
+</div>
 {% endblock %}
 
 {% block base_scripts %}
 <!-- js should be loaded in body! -->
+<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
 {{ include_script("frappe-web.bundle.js") }}
 {{ include_script("controls.bundle.js") }}
 {{ include_script("dialog.bundle.js") }}
diff --git a/erpnext/templates/generators/item/item_add_to_cart.html b/erpnext/templates/generators/item/item_add_to_cart.html
index 167c848..8000a24 100644
--- a/erpnext/templates/generators/item/item_add_to_cart.html
+++ b/erpnext/templates/generators/item/item_add_to_cart.html
@@ -5,54 +5,115 @@
 
 <div class="item-cart row mt-2" data-variant-item-code="{{ item_code }}">
 	<div class="col-md-12">
+		<!-- Price and Availability -->
 		{% if cart_settings.show_price and product_info.price %}
-		<div class="product-price">
-			{{ product_info.price.formatted_price_sales_uom }}
-			<small class="formatted-price">({{ product_info.price.formatted_price }} / {{ product_info.uom }})</small>
-		</div>
+			{% set price_info = product_info.price %}
+
+			<div class="product-price">
+				<!-- Final Price -->
+				{{ price_info.formatted_price_sales_uom }}
+
+				<!-- Striked Price and Discount  -->
+				{% if price_info.formatted_mrp %}
+					<small class="formatted-price">
+						<s>MRP {{ price_info.formatted_mrp }}</s>
+					</small>
+					<small class="ml-1 formatted-price in-green">
+						-{{ price_info.get("formatted_discount_percent") or price_info.get("formatted_discount_rate")}}
+					</small>
+				{% endif %}
+
+				<!-- Price per UOM -->
+				<small class="formatted-price ml-2">
+					({{ price_info.formatted_price }} / {{ product_info.uom }})
+				</small>
+			</div>
 		{% else %}
 			{{ _("UOM") }} : {{ product_info.uom }}
 		{% endif %}
 
 		{% if cart_settings.show_stock_availability %}
-		<div>
-			{% if product_info.in_stock == 0 %}
-			<span class="text-danger no-stock">
-				{{ _('Not in stock') }}
-			</span>
+		<div class="mt-2">
+			{% if product_info.get("on_backorder") %}
+				<span class="no-stock out-of-stock" style="color: var(--primary-color);">
+					{{ _('Available on backorder') }}
+				</span>
+			{% elif product_info.in_stock == 0 %}
+				<span class="no-stock out-of-stock">
+					{{ _('Out of stock') }}
+				</span>
 			{% elif product_info.in_stock == 1 %}
-			<span class="text-success has-stock">
-				{{ _('In stock') }}
-				{% if product_info.show_stock_qty and product_info.stock_qty %}
-					({{ product_info.stock_qty[0][0] }})
-				{% endif %}
-			</span>
+				<span class="in-green has-stock">
+					{{ _('In stock') }}
+					{% if product_info.show_stock_qty and product_info.stock_qty %}
+						({{ product_info.stock_qty[0][0] }})
+					{% endif %}
+				</span>
 			{% endif %}
 		</div>
 		{% endif %}
-		<div class="mt-5 mb-5">
-			{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
-				<a href="/cart"
-					class="btn btn-light btn-view-in-cart {% if not product_info.qty %}hidden{% endif %}"
-					role="button"
-				>
-					{{ _("View in Cart") }}
-				</a>
-				<button
-					data-item-code="{{item_code}}"
-					class="btn btn-primary btn-add-to-cart {% if product_info.qty %}hidden{% endif %} w-100"
-				>
-					<span class="mr-2">
-						<svg class="icon icon-md">
-							<use href="#icon-assets"></use>
+
+		<!-- Offers -->
+		{% if doc.offers %}
+			<br>
+			<div class="offers-heading mb-4">
+				<span class="mr-1 tag-icon">
+					<svg class="icon icon-lg"><use href="#icon-tag"></use></svg>
+				</span>
+				<b>Available Offers</b>
+			</div>
+			<div class="offer-container">
+				{% for offer in doc.offers %}
+				<div class="mt-2 d-flex">
+					<div class="mr-2" >
+						<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--yellow-500)" fill="none" xmlns="http://www.w3.org/2000/svg">
+							<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+							<path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+							<path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
+							<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
 						</svg>
-					</span>
-					{{ _("Add to Cart") }}
-				</button>
-			{% endif %}
-			{% if cart_settings.show_contact_us_button %}
-				{% include "templates/generators/item/item_inquiry.html" %}
-			{% endif %}
+					</div>
+					<p class="mr-1 mb-1">
+						{{ _(offer.offer_title) }}:
+						{{ _(offer.offer_subtitle) if offer.offer_subtitle else '' }}
+						<a class="offer-details" href="#"
+							data-offer-title="{{ offer.offer_title }}" data-offer-id="{{ offer.name }}"
+							role="button">
+							{{ _("More") }}
+						</a>
+					</p>
+				</div>
+				{% endfor %}
+			</div>
+		{% endif %}
+
+		<!-- Add to Cart / View in Cart, Contact Us -->
+		<div class="mt-6 mb-5">
+			<div class="mb-4 d-flex">
+				<!-- Add to Cart -->
+				{% if product_info.price and (cart_settings.allow_items_not_in_stock or product_info.in_stock) %}
+					<a href="/cart" class="btn btn-light btn-view-in-cart hidden mr-2 font-md"
+						role="button">
+						{{  _("View in Cart") if cart_settings.enable_checkout else _("View in Quote") }}
+					</a>
+					<button
+						data-item-code="{{item_code}}"
+						class="btn btn-primary btn-add-to-cart mr-2 w-30-40"
+					>
+						<span class="mr-2">
+							<svg class="icon icon-md">
+								<use href="#icon-assets"></use>
+							</svg>
+						</span>
+						{{ _("Add to Cart") if cart_settings.enable_checkout else  _("Add to Quote") }}
+					</button>
+				{% endif %}
+
+				<!-- Contact Us -->
+				{% if cart_settings.show_contact_us_button %}
+					{% include "templates/generators/item/item_inquiry.html" %}
+				{% endif %}
+			</div>
 		</div>
 	</div>
 </div>
@@ -60,10 +121,11 @@
 <script>
 	frappe.ready(() => {
 		$('.page_content').on('click', '.btn-add-to-cart', (e) => {
+			// Bind action on add to cart button
 			const $btn = $(e.currentTarget);
 			$btn.prop('disabled', true);
 			const item_code = $btn.data('item-code');
-			erpnext.shopping_cart.update_cart({
+			erpnext.e_commerce.shopping_cart.update_cart({
 				item_code,
 				qty: 1,
 				callback(r) {
@@ -74,7 +136,42 @@
 				}
 			});
 		});
+
+		$('.page_content').on('click', '.offer-details', (e) => {
+			// Bind action on More link in Offers
+			const $btn = $(e.currentTarget);
+			$btn.prop('disabled', true);
+
+			var d = new frappe.ui.Dialog({
+				title: __($btn.data('offer-title')),
+				fields: [
+					{
+						fieldname: 'offer_details',
+						fieldtype: 'HTML'
+					},
+					{
+						fieldname: 'section_break',
+						fieldtype: 'Section Break'
+					}
+				]
+			});
+
+			frappe.call({
+				method: 'erpnext.e_commerce.doctype.website_offer.website_offer.get_offer_details',
+				args: {
+					offer_id: $btn.data('offer-id')
+				},
+				callback: (value) => {
+					d.set_value("offer_details", value.message);
+					d.show();
+					$btn.prop('disabled', false);
+				}
+			})
+
+		});
 	});
+
+
 </script>
 
 {% endif %}
diff --git a/erpnext/templates/generators/item/item_configure.html b/erpnext/templates/generators/item/item_configure.html
index b61ac73..e97a275 100644
--- a/erpnext/templates/generators/item/item_configure.html
+++ b/erpnext/templates/generators/item/item_configure.html
@@ -3,11 +3,11 @@
 
 <div class="mt-5 mb-6">
 	{% if cart_settings.enable_variants | int %}
-	<button class="btn btn-primary-light btn-configure"
-		data-item-code="{{ doc.name }}"
-		data-item-name="{{ doc.item_name }}"
+	<button class="btn btn-primary-light btn-configure font-md mr-2"
+		data-item-code="{{ doc.item_code }}"
+		data-item-name="{{ doc.web_item_name }}"
 	>
-		{{ _('Configure') }}
+		{{ _('Select Variant') }}
 	</button>
 	{% endif %}
 	{% if cart_settings.show_contact_us_button %}
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
index 8eadb84..231ae05 100644
--- a/erpnext/templates/generators/item/item_configure.js
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -29,7 +29,7 @@
 		});
 
 		this.dialog = new frappe.ui.Dialog({
-			title: __('Configure {0}', [this.item_name]),
+			title: __('Select Variant for {0}', [this.item_name]),
 			fields,
 			on_hide: () => {
 				set_continue_configuration();
@@ -201,7 +201,7 @@
 				<span class="mr-2">
 					${frappe.utils.icon('assets', 'md')}
 				</span>
-				${__("Add to Cart")}s
+				${__("Add to Cart")}
 			</button>
 		` : '';
 
@@ -214,7 +214,7 @@
 			? `<div class="alert alert-success d-flex justify-content-between align-items-center" role="alert">
 				<div><div>
 					${one_item}
-					${product_info && product_info.price
+					${product_info && product_info.price && !$.isEmptyObject(product_info.price)
 						? '(' + product_info.price.formatted_price_sales_uom + ')'
 						: ''
 					}
@@ -247,7 +247,7 @@
 		const additional_notes = Object.keys(this.range_values || {}).map(attribute => {
 			return `${attribute}: ${this.range_values[attribute]}`;
 		}).join('\n');
-		erpnext.shopping_cart.update_cart({
+		erpnext.e_commerce.shopping_cart.update_cart({
 			item_code,
 			additional_notes,
 			qty: 1
@@ -280,14 +280,14 @@
 	}
 
 	get_next_attribute_and_values(selected_attributes) {
-		return this.call('erpnext.portal.product_configurator.utils.get_next_attribute_and_values', {
+		return this.call('erpnext.e_commerce.variant_selector.utils.get_next_attribute_and_values', {
 			item_code: this.item_code,
 			selected_attributes
 		});
 	}
 
 	get_attributes_and_values() {
-		return this.call('erpnext.portal.product_configurator.utils.get_attributes_and_values', {
+		return this.call('erpnext.e_commerce.variant_selector.utils.get_attributes_and_values', {
 			item_code: this.item_code
 		});
 	}
@@ -311,9 +311,9 @@
 	const { itemCode } = $btn_configure.data();
 
 	if (localStorage.getItem(`configure:${itemCode}`)) {
-		$btn_configure.text(__('Continue Configuration'));
+		$btn_configure.text(__('Continue Selection'));
 	} else {
-		$btn_configure.text(__('Configure'));
+		$btn_configure.text(__('Select Variant'));
 	}
 }
 
diff --git a/erpnext/templates/generators/item/item_details.html b/erpnext/templates/generators/item/item_details.html
index 3b77585..028936b 100644
--- a/erpnext/templates/generators/item/item_details.html
+++ b/erpnext/templates/generators/item/item_details.html
@@ -1,27 +1,63 @@
-<div class="col-md-7 product-details">
-<!-- title -->
-<h1 class="product-title" itemprop="name">
-	{{ item_name }}
-</h1>
-<p class="product-code">
-	<span>{{ _("Item Code") }}:</span>
-	<span itemprop="productID">{{ doc.name }}</span>
-</p>
-{% if has_variants %}
-	<!-- configure template -->
-	{% include "templates/generators/item/item_configure.html" %}
-{% else %}
-	<!-- add variant to cart -->
-	{% include "templates/generators/item/item_add_to_cart.html" %}
-{% endif %}
-<!-- description -->
-<div class="product-description" itemprop="description">
-{% if frappe.utils.strip_html(doc.web_long_description or '') %}
-	{{ doc.web_long_description | safe }}
-{% elif frappe.utils.strip_html(doc.description or '')  %}
-	{{ doc.description | safe }}
-{% else %}
-	{{ _("No description given") }}
-{% endif  %}
+{% set width_class = "expand" if not slides else "" %}
+{% set cart_settings = shopping_cart.cart_settings %}
+{% set product_info = shopping_cart.product_info %}
+{% set price_info = product_info.get('price') or {} %}
+
+<div class="col-md-7 product-details {{ width_class }}">
+	<div class="d-flex">
+		<!-- title -->
+		<div class="product-title col-11" itemprop="name">
+			{{ doc.web_item_name }}
+		</div>
+
+		<!-- Wishlist -->
+		{% if cart_settings.enable_wishlist %}
+			<div class="like-action-item-fp like-action {{ 'like-action-wished' if wished else ''}} ml-2"
+				data-item-code="{{ doc.item_code }}">
+				<svg class="icon sm">
+					<use class="{{ 'wished' if wished else 'not-wished' }} wish-icon" href="#icon-heart"></use>
+				</svg>
+			</div>
+		{% endif %}
+	</div>
+
+	<p class="product-code">
+		<span class="product-item-group">
+			{{ _(doc.item_group) }}
+		</span>
+		<span class="product-item-code">
+			{{ _("Item Code") }}:
+		</span>
+		<span itemprop="productID">{{ doc.item_code }}</span>
+	</p>
+	{% if has_variants %}
+		<!-- configure template -->
+		{% include "templates/generators/item/item_configure.html" %}
+	{% else %}
+		<!-- add variant to cart -->
+		{% include "templates/generators/item/item_add_to_cart.html" %}
+	{% endif %}
+	<!-- description -->
+	<div class="product-description" itemprop="description">
+	{% if frappe.utils.strip_html(doc.web_long_description or '') %}
+		{{ doc.web_long_description | safe }}
+	{% elif frappe.utils.strip_html(doc.description or '')  %}
+		{{ doc.description | safe }}
+	{% else %}
+		{{ "" }}
+	{% endif  %}
+	</div>
 </div>
-</div>
+
+{% block base_scripts %}
+<!-- js should be loaded in body! -->
+<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
+{% endblock %}
+
+<script>
+	$('.page_content').on('click', '.like-action-item-fp', (e) => {
+			// Bind action on wishlist button
+			const $btn = $(e.currentTarget);
+			erpnext.e_commerce.wishlist.wishlist_action($btn);
+		});
+</script>
\ No newline at end of file
diff --git a/erpnext/templates/generators/item/item_image.html b/erpnext/templates/generators/item/item_image.html
index 39a30d0..930bb7a 100644
--- a/erpnext/templates/generators/item/item_image.html
+++ b/erpnext/templates/generators/item/item_image.html
@@ -1,29 +1,30 @@
-<div class="col-md-5 h-100 d-flex">
+{% set column_size = 5 if slides else 4 %}
+<div class="col-md-{{ column_size }} h-100 d-flex mb-4">
 	{% if slides %}
-	<div class="item-slideshow d-flex flex-column mr-3">
-		{% for item in slides %}
-		<img class="item-slideshow-image mb-2 {% if loop.first %}active{% endif %}"
-				src="{{ item.image }}" alt="{{ item.heading }}">
-		{% endfor %}
-	</div>
-	{{ product_image(slides[0].image, 'product-image') }}
-	<!-- Simple image slideshow -->
-	<script>
-		frappe.ready(() => {
-			$('.page_content').on('click', '.item-slideshow-image', (e) => {
-				const $img = $(e.currentTarget);
-				const link = $img.prop('src');
-				const $product_image = $('.product-image');
-				$product_image.find('a').prop('href', link);
-				$product_image.find('img').prop('src', link);
+		<div class="item-slideshow d-flex flex-column mr-3">
+			{% for item in slides %}
+			<img class="item-slideshow-image mb-2 {% if loop.first %}active{% endif %}"
+					src="{{ item.image }}" alt="{{ item.heading }}">
+			{% endfor %}
+		</div>
+		{{ product_image(slides[0].image, 'product-image') }}
+		<!-- Simple image slideshow -->
+		<script>
+			frappe.ready(() => {
+				$('.page_content').on('click', '.item-slideshow-image', (e) => {
+					const $img = $(e.currentTarget);
+					const link = $img.prop('src');
+					const $product_image = $('.product-image');
+					$product_image.find('a').prop('href', link);
+					$product_image.find('img').prop('src', link);
 
-				$('.item-slideshow-image').removeClass('active');
-				$img.addClass('active');
-			});
-		})
-	</script>
+					$('.item-slideshow-image').removeClass('active');
+					$img.addClass('active');
+				});
+			})
+		</script>
 	{% else %}
-	{{ product_image(website_image or image or 'no-image.jpg', alt=website_image_alt or item_name) }}
+		{{ product_image(doc.website_image or doc.image, alt=doc.website_image_alt or doc.item_name) }}
 	{% endif %}
 
 	<!-- Simple image preview -->
diff --git a/erpnext/templates/generators/item/item_inquiry.html b/erpnext/templates/generators/item/item_inquiry.html
index 83653b6..af636f1 100644
--- a/erpnext/templates/generators/item/item_inquiry.html
+++ b/erpnext/templates/generators/item/item_inquiry.html
@@ -1,9 +1,9 @@
 {% if shopping_cart and shopping_cart.cart_settings.enabled %}
 {% set cart_settings = shopping_cart.cart_settings %}
-    {% if cart_settings.show_contact_us_button | int %}
-        <button class="btn btn-inquiry btn-primary-light" data-item-code="{{ doc.name }}">
-            {{ _('Contact Us') }}
-        </button>
+	{% if cart_settings.show_contact_us_button | int %}
+		<button class="btn btn-inquiry font-md w-30-40" data-item-code="{{ doc.name }}">
+			{{ _('Contact Us') }}
+		</button>
 	{% endif %}
 <script>
 {% include "templates/generators/item/item_inquiry.js" %}
diff --git a/erpnext/templates/generators/item/item_inquiry.js b/erpnext/templates/generators/item/item_inquiry.js
index 4724b68..0aee996 100644
--- a/erpnext/templates/generators/item/item_inquiry.js
+++ b/erpnext/templates/generators/item/item_inquiry.js
@@ -52,7 +52,7 @@
 
 		d.hide();
 
-		frappe.call('erpnext.shopping_cart.cart.create_lead_for_item_inquiry', {
+		frappe.call('erpnext.e_commerce.shopping_cart.cart.create_lead_for_item_inquiry', {
 			lead: doc,
 			subject: values.subject,
 			message: values.message
diff --git a/erpnext/templates/generators/item/item_reviews.html b/erpnext/templates/generators/item/item_reviews.html
new file mode 100644
index 0000000..c62c6f7
--- /dev/null
+++ b/erpnext/templates/generators/item/item_reviews.html
@@ -0,0 +1,88 @@
+{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %}
+
+<div class="mt-4 ratings-reviews-section">
+		<!-- Title and Action -->
+		<div class="w-100 mt-4 mb-2 d-flex">
+			<div class="reviews-header col-9">
+				{{ _("Customer Reviews") }}
+			</div>
+
+			<div class="write-a-review-btn col-3">
+				<!-- Write a Review for legitimate users -->
+				{% if frappe.session.user != "Guest" and user_is_customer %}
+					<button class="btn btn-write-review"
+						data-web-item="{{ doc.name }}">
+						{{ _("Write a Review") }}
+					</button>
+				{% endif %}
+			</div>
+		</div>
+
+		<!-- Summary -->
+		{{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
+
+
+	<!-- Reviews and Comments -->
+	<div class="mt-8">
+		{% if reviews %}
+			{{ user_review(reviews) }}
+
+			{% if total_reviews > 4 %}
+				<div class="mt-6 mb-6"style="color: var(--primary);">
+					<a href="/customer_reviews?web_item={{ doc.name }}">{{ _("View all reviews") }}</a>
+				</div>
+			{% endif %}
+
+		{% else %}
+			<h6 class="text-muted mt-6">
+				{{ _("No Reviews") }}
+			</h6>
+		{% endif %}
+	</div>
+</div>
+
+<script>
+	frappe.ready(() => {
+		$('.page_content').on('click', '.btn-write-review', (e) => {
+			// Bind action on write a review button
+			const $btn = $(e.currentTarget);
+
+			let d = new frappe.ui.Dialog({
+				title: __("Write a Review"),
+				fields: [
+					{fieldname: "title", fieldtype: "Data", label: "Headline", reqd: 1},
+					{fieldname: "rating", fieldtype: "Rating", label: "Overall Rating", reqd: 1},
+					{fieldtype: "Section Break"},
+					{fieldname: "comment", fieldtype: "Small Text", label: "Your Review"}
+				],
+				primary_action: function() {
+					var data = d.get_values();
+					frappe.call({
+						method: "erpnext.e_commerce.doctype.item_review.item_review.add_item_review",
+						args: {
+							web_item: "{{ doc.name }}",
+							title: data.title,
+							rating: data.rating,
+							comment: data.comment
+						},
+						freeze: true,
+						freeze_message: __("Submitting Review ..."),
+						callback: function(r) {
+							if(!r.exc) {
+								frappe.msgprint({
+									message: __("Thank you for the review"),
+									title: __("Review Submitted"),
+									indicator: "green"
+								});
+								d.hide();
+								location.reload();
+							}
+						}
+					});
+				},
+				primary_action_label: __('Submit')
+			});
+			d.show();
+		});
+	});
+</script>
diff --git a/erpnext/templates/generators/item/item_specifications.html b/erpnext/templates/generators/item/item_specifications.html
index d4dfa8e..0814d81 100644
--- a/erpnext/templates/generators/item/item_specifications.html
+++ b/erpnext/templates/generators/item/item_specifications.html
@@ -1,14 +1,20 @@
-{% if doc.website_specifications -%}
-<div class="row item-website-specification mt-5">
-	<div class="col-md-12">
-		<table class="table table-bordered">
-		{% for d in doc.website_specifications -%}
+<!-- Is reused to render within tabs as well as independently -->
+{% if website_specifications %}
+<div class="{{ 'mt-2' if not show_tabs else 'mt-5'}} item-website-specification">
+	<div class="col-md-11">
+		{% if not show_tabs %}
+			<div class="product-title mb-5 mt-4">
+				Product Details
+			</div>
+		{% endif %}
+		<table class="table">
+		{% for d in website_specifications -%}
 			<tr>
-				<td class="text-muted" style="width: 30%;">{{ d.label }}</td>
-				<td>{{ d.description }}</td>
+				<td class="spec-label">{{ d.label }}</td>
+				<td class="spec-content">{{ d.description }}</td>
 			</tr>
 		{%- endfor %}
 		</table>
 	</div>
 </div>
-{%- endif %}
+{% endif %}
diff --git a/erpnext/templates/generators/item_group.html b/erpnext/templates/generators/item_group.html
index b5f18ba..e099cdd 100644
--- a/erpnext/templates/generators/item_group.html
+++ b/erpnext/templates/generators/item_group.html
@@ -1,17 +1,25 @@
+{% from "erpnext/templates/includes/macros.html" import field_filter_section, attribute_filter_section, discount_range_filters %}
 {% extends "templates/web.html" %}
 
 {% block header %}
-<!-- <h2>{{ title }}</h2> -->
+<div class="mb-6">{{ _(item_group_name) }}</div>
 {% endblock header %}
 
 {% block script %}
 <script type="text/javascript" src="/all-products/index.js"></script>
 {% endblock %}
 
+{% block breadcrumbs %}
+<div class="item-breadcrumbs small text-muted">
+	{% include "templates/includes/breadcrumbs.html" %}
+</div>
+{% endblock %}
+
 {% block page_content %}
-<div class="item-group-content" itemscope itemtype="http://schema.org/Product" data-item-group="{{ name }}">
+<div class="item-group-content" itemscope itemtype="http://schema.org/Product"
+	data-item-group="{{ name }}">
 	<div class="item-group-slideshow">
-		{% if slideshow %}<!-- slideshow -->
+		{% if slideshow %} <!-- slideshow -->
 			{{ web_block(
 				"Hero Slider",
 				values=slideshow,
@@ -20,91 +28,28 @@
 				add_bottom_padding=0,
 			) }}
 		{% endif %}
-		<h2 class="mt-3">{{ title }}</h2>
-		{% if description %}<!-- description -->
+
+		{% if description %} <!-- description -->
 		<div class="item-group-description text-muted mb-5" itemprop="description">{{ description or ""}}</div>
 		{% endif %}
 	</div>
 	<div class="row">
-		<div class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
-			<div class="row products-list">
-				{% if items %}
-					{% for item in items %}
-						{% include "erpnext/www/all-products/item_row.html" %}
-					{% endfor %}
-				{% else %}
-					{% include "erpnext/www/all-products/not_found.html" %}
-				{% endif %}
-			</div>
+		<div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
+			<!-- Products Rendered in all-products/index.js-->
 		</div>
+
 		<div class="col-12 order-1 col-md-3 order-md-1">
 			<div class="collapse d-md-block mr-4 filters-section" id="product-filters">
 				<div class="d-flex justify-content-between align-items-center mb-5 title-section">
 					<div class="mb-4 filters-title" > {{ _('Filters') }} </div>
 					<a class="mb-4 clear-filters" href="/{{ doc.route }}">{{ _('Clear All') }}</a>
 				</div>
-				{% for field_filter in field_filters %}
-					{%- set item_field =  field_filter[0] %}
-					{%- set values =  field_filter[1] %}
-					<div class="mb-4 filter-block pb-5">
-						<div class="filter-label mb-3">{{ item_field.label }}</div>
+				<!-- field filters -->
+				{{ field_filter_section(field_filters) }}
 
-						{% if values | len > 20 %}
-						<!-- show inline filter if values more than 20 -->
-						<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
-						{% endif %}
+				<!-- attribute filters -->
+				{{ attribute_filter_section(attribute_filters) }}
 
-						{% if values %}
-						<div class="filter-options">
-							{% for value in values %}
-							<div class="checkbox" data-value="{{ value }}">
-								<label for="{{value}}">
-									<input type="checkbox"
-										class="product-filter field-filter"
-										id="{{value}}"
-										data-filter-name="{{ item_field.fieldname }}"
-										data-filter-value="{{ value }}"
-									>
-									<span class="label-area">{{ value }}</span>
-								</label>
-							</div>
-							{% endfor %}
-						</div>
-						{% else %}
-						<i class="text-muted">{{ _('No values') }}</i>
-						{% endif %}
-					</div>
-				{% endfor %}
-
-				{% for attribute in attribute_filters %}
-					<div class="mb-4 filter-block pb-5">
-						<div class="filter-label mb-3">{{ attribute.name}}</div>
-						{% if values | len > 20 %}
-						<!-- show inline filter if values more than 20 -->
-						<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
-						{% endif %}
-
-						{% if attribute.item_attribute_values %}
-						<div class="filter-options">
-							{% for attr_value in attribute.item_attribute_values %}
-							<div class="checkbox">
-								<label data-value="{{ value }}">
-									<input type="checkbox"
-										class="product-filter attribute-filter"
-										id="{{attr_value.name}}"
-										data-attribute-name="{{ attribute.name }}"
-										data-attribute-value="{{ attr_value.attribute_value }}"
-										{% if attr_value.checked %} checked {% endif %}>
-										<span class="label-area">{{ attr_value.attribute_value }}</span>
-								</label>
-							</div>
-							{% endfor %}
-						</div>
-						{% else %}
-						<i class="text-muted">{{ _('No values') }}</i>
-						{% endif %}
-					</div>
-				{% endfor %}
 			</div>
 
 			<script>
@@ -127,23 +72,6 @@
 			</script>
 		</div>
 	</div>
-	<div class="row mt-6">
-		<div class="col-3">
-		</div>
-		<div class="col-9">
-			{% if frappe.form_dict.start|int > 0 %}
-			<button class="btn btn-outline-secondary btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">
-				{{ _("Prev") }}
-			</button>
-			{% endif %}
-			{% if items|length >= page_length %}
-			<button class="btn btn-outline-secondary btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}"
-				style="float: right;">
-				{{ _("Next") }}
-			</button>
-			{% endif %}
-		</div>
-	</div>
 </div>
 
 <script>
diff --git a/erpnext/templates/includes/cart/address_card.html b/erpnext/templates/includes/cart/address_card.html
index 667144b..830ed64 100644
--- a/erpnext/templates/includes/cart/address_card.html
+++ b/erpnext/templates/includes/cart/address_card.html
@@ -1,5 +1,5 @@
 <div class="card address-card h-100">
-	<div class="btn btn-sm btn-default btn-change-address" style="position: absolute; right: 0; top: 0;">
+	<div class="btn btn-sm btn-default btn-change-address font-md" style="position: absolute; right: 0; top: 0;">
 		{{ _('Change') }}
 	</div>
 	<div class="card-body p-0">
diff --git a/erpnext/templates/includes/cart/cart_address.html b/erpnext/templates/includes/cart/cart_address.html
index 4482bc1..cf60017 100644
--- a/erpnext/templates/includes/cart/cart_address.html
+++ b/erpnext/templates/includes/cart/cart_address.html
@@ -4,18 +4,14 @@
 	{% set select_address = True %}
 {% endif %}
 
-{% set show_coupon_code = frappe.db.get_single_value('Shopping Cart Settings', 'show_apply_coupon_code_in_website') %}
-{% if show_coupon_code == 1%}
-<div class="mb-3">
-	<div class="row no-gutters">
-		<input type="text" class="txtcoupon form-control mr-3 w-25" placeholder="Enter Coupon Code" name="txtcouponcode"  ></input>
-		<button class="btn btn-primary btn-sm  bt-coupon">{{ _("Apply Coupon Code") }}</button>
-		<input type="hidden" class="txtreferral_sales_partner" placeholder="Enter Sales Partner" name="txtreferral_sales_partner" type="text"></input>
-		</div>
-</div>
-{% endif %}
 <div class="mb-3 frappe-card p-5" data-section="shipping-address">
-	<h6>{{ _("Shipping Address") }}</h6>
+	<div class="d-flex">
+		<div class="col-6 address-header"><h6>{{ _("Shipping Address") }}</h6></div>
+		<div class="col-6" style="padding: 0;">
+			<a class="ml-4 btn-new-address" role="button">{{ _("Add a new address") }}</a>
+		</div>
+	</div>
+
 	<hr>
 	{% for address in shipping_addresses %}
 	{% if doc.shipping_address_name == address.name %}
@@ -27,26 +23,36 @@
 	{% endif %}
 	{% endfor %}
 </div>
+
+<!-- Billing Address -->
 <div class="checkbox ml-1 mb-2">
 	<label for="input_same_billing">
-		<input type="checkbox" id="input_same_billing" checked>
-		<span class="label-area">{{ _('Billing Address is same as Shipping Address') }}</span>
+		<input type="checkbox" class="product-filter" id="input_same_billing" checked style="width: 14px !important">
+		<span class="label-area font-md">{{ _('Billing Address is same as Shipping Address') }}</span>
 	</label>
 </div>
-<div class="mb-3 frappe-card p-5" data-section="billing-address">
-	<h6>{{ _("Billing Address") }}</h6>
-	<hr>
-	{% for address in billing_addresses %}
-		{% if doc.customer_address == address.name %}
-		<div class="row no-gutters" data-fieldname="customer_address">
-			<div class="w-100 address-container" data-address-name="{{address.name}}" data-address-type="billing" data-active>
-					{% include "templates/includes/cart/address_card.html" %}
-				</div>
+
+{% if billing_addresses %}
+	<div class="mb-3 frappe-card p-5" data-section="billing-address">
+		<div class="d-flex">
+			<div class="col-6 address-header"><h6>{{ _("Billing Address") }}</h6></div>
+			<div class="col-6" style="padding: 0;">
+				<a class="ml-4 btn-new-address" role="button">{{ _("Add a new address") }}</a>
+			</div>
 		</div>
-		{% endif %}
-	{% endfor %}
-</div>
-<button class="btn btn-outline-primary btn-sm mt-1 btn-new-address bg-white">{{ _("Add a new address") }}</button>
+
+		<hr>
+		{% for address in billing_addresses %}
+			{% if doc.customer_address == address.name %}
+			<div class="row no-gutters" data-fieldname="customer_address">
+				<div class="w-100 address-container" data-address-name="{{address.name}}" data-address-type="billing" data-active>
+						{% include "templates/includes/cart/address_card.html" %}
+					</div>
+			</div>
+			{% endif %}
+		{% endfor %}
+	</div>
+{% endif %}
 
 <script>
 frappe.ready(() => {
@@ -125,15 +131,16 @@
 				{
 					fieldname: "phone",
 					fieldtype: "Data",
-					label: "Phone"
+					label: "Phone",
+					reqd: 1
 				},
 			],
 			primary_action_label: __('Save'),
 			primary_action: (values) => {
-				frappe.call('erpnext.shopping_cart.cart.add_new_address', { doc: values })
+				frappe.call('erpnext.e_commerce.shopping_cart.cart.add_new_address', { doc: values })
 					.then(r => {
 						frappe.call({
-							method: "erpnext.shopping_cart.cart.update_cart_address",
+							method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
 							args: {
 								address_type: r.message.address_type,
 								address_name: r.message.name
diff --git a/erpnext/templates/includes/cart/cart_items.html b/erpnext/templates/includes/cart/cart_items.html
index 75441c4..428b36e 100644
--- a/erpnext/templates/includes/cart/cart_items.html
+++ b/erpnext/templates/includes/cart/cart_items.html
@@ -1,42 +1,113 @@
-{% for d in doc.items %}
-<tr data-name="{{ d.name }}">
-	<td>
-		<div class="item-title mb-1">
-			{{ d.item_name }}
-		</div>
-		<div class="item-subtitle">
-			{{ d.item_code }}
-		</div>
-		{%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
-		{% if variant_of %}
-		<span class="item-subtitle">
-			{{ _('Variant of') }} <a href="{{frappe.db.get_value('Item', variant_of, 'route')}}">{{ variant_of }}</a>
-		</span>
-		{% endif %}
-		<div class="mt-2">
-			<textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">{{d.additional_notes or ''}}</textarea>
-		</div>
-	</td>
-	<td class="text-right">
-		<div class="input-group number-spinner">
-			<span class="input-group-prepend d-none d-sm-inline-block">
-				<button class="btn cart-btn" data-dir="dwn">–</button>
-			</span>
-			<input class="form-control text-center cart-qty" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}">
-			<span class="input-group-append d-none d-sm-inline-block">
-				<button class="btn cart-btn" data-dir="up">+</button>
+{% from "erpnext/templates/includes/macros.html" import product_image %}
+
+{% macro item_subtotal(item) %}
+	<div>
+		{{ item.get_formatted('amount') }}
+	</div>
+
+	{% if item.is_free_item %}
+		<div class="text-success mt-4">
+			<span class="free-tag">
+				{{ _('FREE') }}
 			</span>
 		</div>
-	</td>
-	{% if cart_settings.enable_checkout %}
-	<td class="text-right item-subtotal">
-		<div>
-			{{ d.get_formatted('amount') }}
-		</div>
+	{% else %}
 		<span class="item-rate">
-			{{ _('Rate:') }} {{ d.get_formatted('rate') }}
+			{{ _('Rate:') }} {{ item.get_formatted('rate') }}
 		</span>
-	</td>
 	{% endif %}
-</tr>
+{% endmacro %}
+
+{% for d in doc.items %}
+	<tr data-name="{{ d.name }}">
+		<td style="width: 60%;">
+			<div class="d-flex">
+				<div class="cart-item-image mr-4">
+					{% if d.thumbnail %}
+						{{ product_image(d.thumbnail, alt="d.web_item_name", no_border=True) }}
+					{% else %}
+						<div class = "no-image-cart-item">
+							{{ frappe.utils.get_abbr(d.web_item_name) or "NA" }}
+						</div>
+					{% endif %}
+				</div>
+
+				<div class="d-flex w-100" style="flex-direction: column;">
+					<div class="item-title mb-1 mr-3">
+						{{ d.get("web_item_name") or d.item_name }}
+					</div>
+					<div class="item-subtitle mr-2">
+						{{ d.item_code }}
+					</div>
+					{%- set variant_of = frappe.db.get_value('Item', d.item_code, 'variant_of') %}
+					{% if variant_of %}
+					<span class="item-subtitle mr-2">
+						{{ _('Variant of') }}
+						<a href="{{frappe.db.get_value('Website Item', {'item_code': variant_of}, 'route') or '#'}}">
+							{{ variant_of }}
+						</a>
+					</span>
+					{% endif %}
+
+					<div class="mt-2 notes">
+						<textarea data-item-code="{{d.item_code}}" class="form-control" rows="2" placeholder="{{ _('Add notes') }}">
+							{{d.additional_notes or ''}}
+						</textarea>
+					</div>
+				</div>
+			</div>
+		</td>
+
+		<!-- Qty column -->
+		<td class="text-right" style="width: 25%;">
+			<div class="d-flex">
+				{% set disabled = 'disabled' if d.is_free_item else '' %}
+				<div class="input-group number-spinner mt-1 mb-4">
+					<span class="input-group-prepend d-sm-inline-block">
+						<button class="btn cart-btn" data-dir="dwn" {{ disabled }}>
+							{{ '–' if not d.is_free_item else ''}}
+						</button>
+					</span>
+
+					<input class="form-control text-center cart-qty" value="{{ d.get_formatted('qty') }}" data-item-code="{{ d.item_code }}"
+						style="max-width: 70px;" {{ disabled }}>
+
+					<span class="input-group-append d-sm-inline-block">
+						<button class="btn cart-btn" data-dir="up" {{ disabled }}>
+							{{ '+' if not d.is_free_item else ''}}
+						</button>
+					</span>
+					</div>
+
+				<div>
+					{% if not d.is_free_item %}
+						<div class="remove-cart-item column-sm-view d-flex" data-item-code="{{ d.item_code }}">
+							<span>
+								<svg class="icon sm remove-cart-item-logo"
+									width="18" height="18" viewBox="0 0 18 18"
+									xmlns="http://www.w3.org/2000/svg" id="icon-close">
+									<path fill-rule="evenodd" clip-rule="evenodd" d="M4.146 11.217a.5.5 0 1 0 .708.708l3.182-3.182 3.181 3.182a.5.5 0 1 0 .708-.708l-3.182-3.18 3.182-3.182a.5.5 0 1 0-.708-.708l-3.18 3.181-3.183-3.182a.5.5 0 0 0-.708.708l3.182 3.182-3.182 3.181z" stroke-width="0"></path>
+								</svg>
+							</span>
+						</div>
+					{% endif %}
+					</div>
+			</div>
+
+
+			<!-- Shown on mobile view, else hidden -->
+			{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
+				<div class="text-right sm-item-subtotal">
+					{{ item_subtotal(d) }}
+				</div>
+			{% endif %}
+		</td>
+
+		<!-- Subtotal column -->
+		{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
+			<td class="text-right item-subtotal column-sm-view w-100">
+				{{ item_subtotal(d) }}
+			</td>
+		{% endif %}
+	</tr>
 {% endfor %}
diff --git a/erpnext/templates/includes/cart/cart_items_total.html b/erpnext/templates/includes/cart/cart_items_total.html
new file mode 100644
index 0000000..c94fde4
--- /dev/null
+++ b/erpnext/templates/includes/cart/cart_items_total.html
@@ -0,0 +1,10 @@
+<!-- Total at the end of the cart items -->
+<tr>
+	<th></th>
+	<th class="text-left item-grand-total" colspan="1">
+		{{ _("Total") }}
+	</th>
+	<th class="text-left item-grand-total totals" colspan="3">
+		{{ doc.get_formatted("total") }}
+	</th>
+</tr>
\ No newline at end of file
diff --git a/erpnext/templates/includes/cart/cart_payment_summary.html b/erpnext/templates/includes/cart/cart_payment_summary.html
new file mode 100644
index 0000000..b5655a2
--- /dev/null
+++ b/erpnext/templates/includes/cart/cart_payment_summary.html
@@ -0,0 +1,84 @@
+<!-- Payment -->
+{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
+<h6>
+	{{ _("Payment Summary") }}
+</h6>
+{% endif %}
+
+<div class="card h-100">
+	<div class="card-body p-0">
+		{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
+			<table class="table w-100">
+				<tr>
+					{% set total_items = frappe.utils.cstr(frappe.utils.flt(doc.total_qty, 0)) %}
+					<td class="bill-label">{{ _("Net Total (") + total_items + _(" Items)") }}</td>
+					<td class="bill-content net-total text-right">{{ doc.get_formatted("net_total") }}</td>
+				</tr>
+
+				<!-- taxes -->
+				{% for d in doc.taxes %}
+					{% if d.base_tax_amount %}
+						<tr>
+							<td class="bill-label">
+								{{ d.description }}
+							</td>
+							<td class="bill-content text-right">
+								{{ d.get_formatted("base_tax_amount") }}
+							</td>
+						</tr>
+					{% endif %}
+				{% endfor %}
+			</table>
+
+			<!-- TODO: Apply Coupon Dialog-->
+			<!-- {% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
+			{% if show_coupon_code %}
+				<button class="btn btn-coupon-code w-100 text-left">
+					<svg width="24" height="24" viewBox="0 0 24 24" stroke="var(--gray-600)" fill="none" xmlns="http://www.w3.org/2000/svg">
+						<path d="M19 15.6213C19 15.2235 19.158 14.842 19.4393 14.5607L20.9393 13.0607C21.5251 12.4749 21.5251 11.5251 20.9393 10.9393L19.4393 9.43934C19.158 9.15804 19 8.7765 19 8.37868V6.5C19 5.67157 18.3284 5 17.5 5H15.6213C15.2235 5 14.842 4.84196 14.5607 4.56066L13.0607 3.06066C12.4749 2.47487 11.5251 2.47487 10.9393 3.06066L9.43934 4.56066C9.15804 4.84196 8.7765 5 8.37868 5H6.5C5.67157 5 5 5.67157 5 6.5V8.37868C5 8.7765 4.84196 9.15804 4.56066 9.43934L3.06066 10.9393C2.47487 11.5251 2.47487 12.4749 3.06066 13.0607L4.56066 14.5607C4.84196 14.842 5 15.2235 5 15.6213V17.5C5 18.3284 5.67157 19 6.5 19H8.37868C8.7765 19 9.15804 19.158 9.43934 19.4393L10.9393 20.9393C11.5251 21.5251 12.4749 21.5251 13.0607 20.9393L14.5607 19.4393C14.842 19.158 15.2235 19 15.6213 19H17.5C18.3284 19 19 18.3284 19 17.5V15.6213Z" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+						<path d="M15 9L9 15" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
+						<path d="M10.5 9.5C10.5 10.0523 10.0523 10.5 9.5 10.5C8.94772 10.5 8.5 10.0523 8.5 9.5C8.5 8.94772 8.94772 8.5 9.5 8.5C10.0523 8.5 10.5 8.94772 10.5 9.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
+						<path d="M15.5 14.5C15.5 15.0523 15.0523 15.5 14.5 15.5C13.9477 15.5 13.5 15.0523 13.5 14.5C13.5 13.9477 13.9477 13.5 14.5 13.5C15.0523 13.5 15.5 13.9477 15.5 14.5Z" fill="white" stroke-linecap="round" stroke-linejoin="round"/>
+					</svg>
+					<span class="ml-2">Apply Coupon</span>
+				</button>
+			{% endif %} -->
+
+			<table class="table w-100 grand-total mt-6">
+				<tr>
+					<td class="bill-content net-total">{{ _("Grand Total") }}</td>
+					<td class="bill-content net-total text-right">{{ doc.get_formatted("grand_total") }}</td>
+				</tr>
+			</table>
+		{% endif %}
+
+		{% if cart_settings.enable_checkout %}
+			<button class="btn btn-primary btn-place-order font-md w-100" type="button">
+				{{ _('Place Order') }}
+			</button>
+		{% else %}
+			<button class="btn btn-primary btn-request-for-quotation font-md w-100" type="button">
+				{{ _('Request for Quote') }}
+			</button>
+		{% endif %}
+	</div>
+</div>
+
+<!-- TODO: Apply Coupon Dialog-->
+<!-- <script>
+	frappe.ready(() => {
+		$('.btn-coupon-code').click((e) => {
+			const $btn = $(e.currentTarget);
+			const d = new frappe.ui.Dialog({
+				title: __('Coupons'),
+				fields: [
+					{
+						fieldname: 'coupons_area',
+						fieldtype: 'HTML'
+					}
+				]
+			});
+			d.show();
+		});
+	});
+</script> -->
\ No newline at end of file
diff --git a/erpnext/templates/includes/footer/footer_powered.html b/erpnext/templates/includes/footer/footer_powered.html
index 82b2716..faf5e92 100644
--- a/erpnext/templates/includes/footer/footer_powered.html
+++ b/erpnext/templates/includes/footer/footer_powered.html
@@ -1,27 +1 @@
-{% set domains = frappe.get_doc("Domain Settings").active_domains %}
-{% set links = {
-	'Manufacturing': '/manufacturing',
-	'Services': '/services',
-	'Retail': '/retail',
-	'Distribution': '/distribution',
-	'Non Profit': '/non-profit',
-	'Education': '/education',
-	'Healthcare': '/healthcare',
-	'Agriculture': '/agriculture',
-	'Hospitality': ''
-} %}
-
-{% set link = '' %}
-{% set label = '' %}
-{% if domains %}
-	{% set label = domains[0].domain %}
-	{% set link = links[label] %}
-{% endif %}
-
-{% if label == "Services" %}
-	{% set label = "Service" %}
-{% endif %}
-
-
-
-<a href="https://erpnext.com{{ link }}?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext - {{ '' if domains else 'Open Source' }} ERP Software {{ ('for ' + label + ' Companies') if domains else '' }}</a>
+<a href="https://erpnext.com?source=website_footer" target="_blank" class="text-muted">Powered by ERPNext</a>
diff --git a/erpnext/templates/includes/macros.html b/erpnext/templates/includes/macros.html
index be0d47f..4741307 100644
--- a/erpnext/templates/includes/macros.html
+++ b/erpnext/templates/includes/macros.html
@@ -7,9 +7,15 @@
 </div>
 {% endmacro %}
 
-{% macro product_image(website_image, css_class="product-image", alt="") %}
-	<div class="border text-center rounded {{ css_class }}" style="overflow: hidden;">
-		<img itemprop="image" class="website-image h-100 w-100" alt="{{ alt }}" src="{{ frappe.utils.quoted(website_image or 'no-image.jpg') | abs_url }}">
+{% macro product_image(website_image, css_class="product-image", alt="", no_border=False) %}
+	<div class="{{ 'border' if not no_border else ''}} text-center rounded {{ css_class }}" style="overflow: hidden;">
+		{% if website_image %}
+			<img itemprop="image" class="website-image h-100 w-100" alt="{{ alt }}" src="{{ frappe.utils.quoted(website_image) | abs_url }}">
+		{% else %}
+			<div class="card-img-top no-image-item">
+				{{ frappe.utils.get_abbr(alt) or "NA" }}
+			</div>
+		{% endif %}
 	</div>
 {% endmacro %}
 
@@ -59,65 +65,335 @@
 
 {% endmacro %}
 
-{%- macro item_card(title, image, url, description, rate, category, is_featured=False, is_full_width=False, align="Left") -%}
+{%- macro item_card(item, is_featured=False, is_full_width=False, align="Left") -%}
 {%- set align_items_class = resolve_class({
 	'align-items-end': align == 'Right',
 	'align-items-center': align == 'Center',
 	'align-items-start': align == 'Left',
 }) -%}
 {%- set col_size = 3 if is_full_width else 4 -%}
+{%- set title = item.web_item_name or item.item_name or item.item_code -%}
+{%- set title = title[:50] + "..." if title|len > 50 else title -%}
+{%- set image = item.website_image or item.image -%}
+{%- set description = item.website_description or item.description-%}
+
 {% if is_featured %}
 <div class="col-sm-{{ col_size*2 }} item-card">
-	<div class="card featured-item {{ align_items_class }}">
+	<div class="card featured-item {{ align_items_class }}" style="height: 360px;">
 		{% if image %}
 		<div class="row no-gutters">
-			<div class="col-md-6">
+			<div class="col-md-5 ml-4">
 				<img class="card-img" src="{{ image }}" alt="{{ title }}">
 			</div>
 			<div class="col-md-6">
-				{{ item_card_body(title, description, url, rate, category, is_featured, align) }}
+				{{ item_card_body(title, description, item, is_featured, align) }}
 			</div>
 		</div>
 		{% else %}
 			<div class="col-md-12">
-				{{ item_card_body(title, description, url, rate, category, is_featured, align) }}
+				{{ item_card_body(title, description, item, is_featured, align) }}
 			</div>
 		{% endif %}
 	</div>
 </div>
 {% else %}
 <div class="col-sm-{{ col_size }} item-card">
-	<div class="card {{ align_items_class }}">
+	<div class="card {{ align_items_class }}" style="height: 360px;">
 		{% if image %}
-		<div class="card-img-container">
-			<img class="card-img" src="{{ image }}" alt="{{ title }}">
-		</div>
+			<div class="card-img-container">
+				<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
+					<img class="card-img" src="{{ image }}" alt="{{ title }}">
+				</a>
+			</div>
 		{% else %}
-		<div class="card-img-top no-image">
-			{{ frappe.utils.get_abbr(title) }}
-		</div>
+		<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
+			<div class="card-img-top no-image">
+				{{ frappe.utils.get_abbr(title) }}
+			</div>
+		</a>
 		{% endif %}
-		{{ item_card_body(title, description, url, rate, category, is_featured, align) }}
+		{{ item_card_body(title, description, item, is_featured, align) }}
 	</div>
 </div>
 {% endif %}
 {%- endmacro -%}
 
-{%- macro item_card_body(title, description, url, rate, category, is_featured, align) -%}
+{%- macro item_card_body(title, description, item, is_featured, align) -%}
 {%- set align_class = resolve_class({
 	'text-right': align == 'Right',
 	'text-center': align == 'Center' and not is_featured,
 	'text-left': align == 'Left' or is_featured,
 }) -%}
-<div class="card-body {{ align_class }}">
-	<div class="product-title">{{ title or '' }}</div>
+<div class="card-body {{ align_class }}" style="width:100%">
+	<div class="mt-4">
+		<a href="/{{ item.route or '#' }}">
+			<div class="product-title">
+				{{ title or '' }}
+			</div>
+		</a>
+	</div>
 	{% if is_featured %}
-	<div class="product-price">{{ rate or '' }}</div>
-	<div class="product-description ellipsis">{{ description or '' }}</div>
+		<div class="product-description ellipsis text-muted" style="white-space: normal;">
+			{{ description or '' }}
+		</div>
 	{% else %}
-	<div class="product-category">{{ category or '' }}</div>
-	<div class="product-price">{{ rate or '' }}</div>
+		<div class="product-category">{{ item.item_group or '' }}</div>
 	{% endif %}
 </div>
-<a href="/{{ url or '#' }}" class="stretched-link"></a>
+{%- endmacro -%}
+
+
+{%- macro wishlist_card(item, settings) %}
+{%- set title = item.web_item_name or ''-%}
+{%- set title = title[:90] + "..." if title|len > 90 else title -%}
+<div class="col-sm-3 wishlist-card">
+	<div class="card text-center">
+		<div class="card-img-container">
+			<a href="/{{ item.route or '#' }}" style="text-decoration: none;">
+				{% if item.image %}
+					<img class="card-img" src="{{ item.image }}" alt="{{ title }}">
+				{% else %}
+					<div class="card-img-top no-image">
+						{{ frappe.utils.get_abbr(title) }}
+					</div>
+				{% endif %}
+			</a>
+			<div class="remove-wish" data-item-code="{{ item.item_code }}">
+				<svg class="icon icon-md remove-wish-icon">
+					<use class="close" href="#icon-delete"></use>
+				</svg>
+			</div>
+		</div>
+
+		{{ wishlist_card_body(item, title, settings) }}
+	</div>
+</div>
+{%- endmacro -%}
+
+{%- macro wishlist_card_body(item, title, settings) %}
+<div class="card-body card-body-flex text-left" style="width: 100%;">
+	<div class="mt-4">
+		<div class="product-title">{{ title or ''}}</div>
+		<div class="product-category">{{ item.item_group or '' }}</div>
+	</div>
+	<div class="product-price">
+		{{ item.get("formatted_price") or '' }}
+
+		{% if item.get("formatted_mrp") %}
+			<small class="ml-1 striked-price">
+				<s>{{ item.formatted_mrp }}</s>
+			</small>
+			<small class="ml-1 product-info-green" >
+				{{ item.discount }} OFF
+			</small>
+		{% endif %}
+	</div>
+
+	{% if (item.available and settings.show_stock_availability) or (not settings.show_stock_availability) %}
+		<!-- Show move to cart button if in stock or if showing stock availability is disabled -->
+		<button data-item-code="{{ item.item_code}}"
+			class="btn btn-primary btn-add-to-cart-list btn-add-to-cart mt-2 w-100">
+			<span class="mr-2">
+				<svg class="icon icon-md">
+					<use href="#icon-assets"></use>
+				</svg>
+			</span>
+			{{ _("Move to Cart") }}
+		</button>
+	{% else %}
+		<div class="out-of-stock">
+			{{ _("Out of stock") }}
+		</div>
+	{% endif %}
+</div>
+{%- endmacro -%}
+
+{%- macro ratings_with_title(avg_rating, title, size, rating_header_class, for_summary=False) -%}
+<div class="{{ 'd-flex' if not for_summary else '' }}">
+	<p class="mr-4 {{ rating_header_class }}">
+		<span>{{ title }}</span>
+	</p>
+	<div class="rating {{ 'ratings-pill' if for_summary else ''}}">
+		{% for i in range(1,6) %}
+			{% set fill_class = 'star-click' if i <= avg_rating else '' %}
+			<svg class="icon icon-{{ size }} {{ fill_class }}">
+				<use href="#icon-star"></use>
+			</svg>
+		{% endfor %}
+	</div>
+</div>
+{%- endmacro -%}
+
+{%- macro ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=False, total_reviews=None)-%}
+<div class="rating-summary-section mt-4">
+	<div class="rating-summary-numbers col-3">
+		<h2 style="font-size: 2rem;">
+			{{ average_rating or 0 }}
+		</h2>
+		<div class="mb-2" style="margin-top: -.5rem;">
+			{{ frappe.utils.cstr(total_reviews or 0) + " " + _("ratings") }}
+		</div>
+
+		<!-- Ratings Summary -->
+		{% if reviews %}
+			{% set rating_title = frappe.utils.cstr(average_rating) + " " + _("out of 5") if not for_summary else ''%}
+			{{ ratings_with_title(average_whole_rating, rating_title, "md", "rating-summary-title", for_summary) }}
+		{% endif %}
+
+		<div class="mt-2">{{ frappe.utils.cstr(average_rating or 0) + " " + _("out of 5") }}</div>
+	</div>
+
+	<!-- Rating Progress Bars -->
+	<div class="rating-progress-bar-section col-4 ml-4">
+		{% for percent in reviews_per_rating %}
+			<div class="col-sm-4 small rating-bar-title">
+				{{ loop.index }} star
+			</div>
+			<div class="row">
+				<div class="col-md-7">
+					<div class="progress rating-progress-bar" title="{{ percent }} % of reviews are {{ loop.index }} star">
+						<div class="progress-bar progress-bar-cosmetic" role="progressbar"
+							aria-valuenow="{{ percent }}"
+							aria-valuemin="0" aria-valuemax="100"
+							style="width: {{ percent }}%;">
+						</div>
+					</div>
+				</div>
+				<div class="col-sm-1 small">
+					{{ percent }}%
+				</div>
+			</div>
+		{% endfor %}
+	</div>
+</div>
+{%- endmacro -%}
+
+{%- macro user_review(reviews)-%}
+<!-- User Reviews -->
+<div class="user-reviews">
+	{% for review in reviews %}
+		<div class="mb-3 review">
+			{{ ratings_with_title(review.rating, _(review.review_title), "sm", "user-review-title") }}
+
+			<div class="product-description mb-4">
+				<p>
+					{{ _(review.comment) }}
+				</p>
+			</div>
+
+			<div class="review-signature mb-2">
+				<span class="reviewer">{{ _(review.customer) }}</span>
+				<span class="indicator grey" style="--text-on-gray: var(--gray-300);"></span>
+				<span class="reviewer">{{ review.published_on }}</span>
+			</div>
+		</div>
+	{% endfor %}
+</div>
+{%- endmacro -%}
+
+{%- macro field_filter_section(filters)-%}
+{% for field_filter in filters %}
+	{%- set item_field =  field_filter[0] %}
+	{%- set values =  field_filter[1] %}
+	<div class="mb-4 filter-block pb-5">
+		<div class="filter-label mb-3">{{ item_field.label }}</div>
+
+		{% if values | len > 20 %}
+		<!-- show inline filter if values more than 20 -->
+		<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
+		{% endif %}
+
+		{% if values %}
+		<div class="filter-options">
+			{% for value in values %}
+			<div class="checkbox" data-value="{{ value }}">
+				<label for="{{value}}">
+					<input type="checkbox"
+						class="product-filter field-filter"
+						id="{{value}}"
+						data-filter-name="{{ item_field.fieldname }}"
+						data-filter-value="{{ value }}"
+						style="width: 14px !important">
+					<span class="label-area">{{ value }}</span>
+				</label>
+			</div>
+			{% endfor %}
+		</div>
+		{% else %}
+		<i class="text-muted">{{ _('No values') }}</i>
+		{% endif %}
+	</div>
+{% endfor %}
+{%- endmacro -%}
+
+{%- macro attribute_filter_section(filters)-%}
+{% for attribute in filters %}
+	<div class="mb-4 filter-block pb-5">
+		<div class="filter-label mb-3">{{ attribute.name}}</div>
+		{% if values | len > 20 %}
+		<!-- show inline filter if values more than 20 -->
+		<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
+		{% endif %}
+
+		{% if attribute.item_attribute_values %}
+		<div class="filter-options">
+			{% for attr_value in attribute.item_attribute_values %}
+			<div class="checkbox">
+				<label data-value="{{ attr_value }}">
+					<input type="checkbox"
+						class="product-filter attribute-filter"
+						id="{{ attr_value }}"
+						data-attribute-name="{{ attribute.name }}"
+						data-attribute-value="{{ attr_value }}"
+						style="width: 14px !important"
+						{% if attr_value.checked %} checked {% endif %}>
+						<span class="label-area">{{ attr_value }}</span>
+				</label>
+			</div>
+			{% endfor %}
+		</div>
+		{% else %}
+		<i class="text-muted">{{ _('No values') }}</i>
+		{% endif %}
+	</div>
+{% endfor %}
+{%- endmacro -%}
+
+{%- macro recommended_item_row(item)-%}
+<div class="recommended-item mb-6 d-flex">
+	<div class="r-item-image">
+		{% if item.website_item_thumbnail %}
+			{{ product_image(item.website_item_thumbnail, css_class="r-product-image", alt="item.website_item_name", no_border=True) }}
+		{% else %}
+			<div class="no-image-r-item">
+				{{ frappe.utils.get_abbr(item.website_item_name) or "NA" }}
+			</div>
+		{% endif %}
+	</div>
+	<div class="r-item-info">
+		<a href="/{{ item.route or '#'}}" target="_blank">
+			{% set title = item.website_item_name %}
+			{{ title[:70] + "..." if title|len > 70 else title }}
+		</a>
+
+		{% if item.get('price_info') %}
+			{% set price = item.get('price_info') %}
+			<div class="mt-2">
+				<span class="item-price">
+					{{ price.get('formatted_price') or '' }}
+				</span>
+
+				{% if price.get('formatted_mrp') %}
+					<br>
+					<span class="striked-item-price">
+						<s>MRP {{ price.formatted_mrp }}</s>
+					</span>
+					<span class="in-green">
+						- {{ price.get('formatted_discount_percent') or price.get('formatted_discount_rate')}}
+					</span>
+				{% endif %}
+			</div>
+		{% endif %}
+	</div>
+</div>
 {%- endmacro -%}
diff --git a/erpnext/templates/includes/navbar/navbar_items.html b/erpnext/templates/includes/navbar/navbar_items.html
index 2912206..d7adae5 100644
--- a/erpnext/templates/includes/navbar/navbar_items.html
+++ b/erpnext/templates/includes/navbar/navbar_items.html
@@ -6,7 +6,17 @@
 			<svg class="icon icon-lg">
 				<use href="#icon-assets"></use>
 			</svg>
-			<span class="badge badge-primary cart-badge" id="cart-count"></span>
+			<span class="badge badge-primary shopping-badge" id="cart-count"></span>
 		</a>
-	 </li>
+	</li>
+	{% if frappe.db.get_single_value("E Commerce Settings", "enable_wishlist") %}
+		<li class="wishlist wishlist-icon hidden">
+			<a class="nav-link" href="/wishlist">
+				<svg class="icon icon-lg">
+					<use href="#icon-heart"></use>
+				</svg>
+				<span class="badge badge-primary shopping-badge" id="wish-count"></span>
+			</a>
+		</li>
+	{% endif %}
 {% endblock %}
diff --git a/erpnext/templates/includes/order/order_macros.html b/erpnext/templates/includes/order/order_macros.html
index 7b3c9a4..3f2c1f2 100644
--- a/erpnext/templates/includes/order/order_macros.html
+++ b/erpnext/templates/includes/order/order_macros.html
@@ -1,43 +1,49 @@
-{% from "erpnext/templates/includes/macros.html" import product_image_square %}
+{% from "erpnext/templates/includes/macros.html" import product_image %}
 
 {% macro item_name_and_description(d) %}
-    <div class="row item_name_and_description">
-        <div class="col-xs-4 col-sm-2 order-image-col">
-            <div class="order-image">
-                {{ product_image_square(d.thumbnail or d.image) }}
-            </div>
-        </div>
-        <div class="col-xs-8 col-sm-10">
-            {{ d.item_code }}
-            <div class="text-muted small item-description">
+	<div class="row item_name_and_description">
+		<div class="col-xs-4 col-sm-2 order-image-col">
+			<div class="order-image">
+				{% if d.thumbnail or d.image %}
+					{{ product_image(d.thumbnail or d.image, no_border=True) }}
+				{% else %}
+					<div class="no-image-cart-item" style="min-height: 100px;">
+						{{ frappe.utils.get_abbr(d.item_name) or "NA" }}
+					</div>
+				{% endif %}
+			</div>
+		</div>
+		<div class="col-xs-8 col-sm-10">
+			{{ d.item_code }}
+			<div class="text-muted small item-description">
 				{{ html2text(d.description) | truncate(140) }}
 			</div>
-        </div>
-    </div>
+		</div>
+	</div>
 {% endmacro %}
 
 {% macro item_name_and_description_cart(d) %}
-    <div class="row item_name_dropdown">
-        <div class="col-xs-4 col-sm-4 order-image-col">
-            <div class="order-image">
-             {{ product_image_square(d.thumbnail or d.image) }}
-            </div>
-        </div>
-        <div class="col-xs-8 col-sm-8">
-           {{ d.item_name|truncate(25) }}
-			<div class="input-group number-spinner">
-                <span class="input-group-btn">
-                    <button class="btn btn-light cart-btn" data-dir="dwn">
-                        –</button>
-                </span>
-	            <input class="form-control text-right cart-qty"
-		            value = "{{ d.get_formatted('qty') }}"
-		            data-item-code="{{ d.item_code }}">
-                <span class="input-group-btn">
-                    <button class="btn btn-light cart-btn" data-dir="up">
-                        +</button>
-                </span>
+	<div class="row item_name_dropdown">
+		<div class="col-xs-4 col-sm-4 order-image-col">
+			<div class="order-image">
+			 {{ product_image_square(d.thumbnail or d.image) }}
 			</div>
-        </div>
-    </div>
+		</div>
+		<div class="col-xs-8 col-sm-8">
+		   {{ d.item_name|truncate(25) }}
+			<div class="input-group number-spinner">
+				<span class="input-group-btn">
+					<button class="btn btn-light cart-btn" data-dir="dwn">
+						–</button>
+				</span>
+				<input class="form-control text-right cart-qty"
+					value = "{{ d.get_formatted('qty') }}"
+					data-item-code="{{ d.item_code }}">
+				<span class="input-group-btn">
+					<button class="btn btn-light cart-btn" data-dir="up">
+						+</button>
+				</span>
+			</div>
+		</div>
+	</div>
 {% endmacro %}
diff --git a/erpnext/templates/includes/order/order_taxes.html b/erpnext/templates/includes/order/order_taxes.html
index d2c458e..b821e62 100644
--- a/erpnext/templates/includes/order/order_taxes.html
+++ b/erpnext/templates/includes/order/order_taxes.html
@@ -1,9 +1,9 @@
 {% if doc.taxes %}
 <tr>
-	<td class="text-right" colspan="2">
+	<td class="text-left" colspan="1">
 		{{ _("Net Total") }}
 	</td>
-	<td class="text-right">
+	<td class="text-right totals" colspan="3">
 		{{ doc.get_formatted("net_total") }}
 	</td>
 </tr>
@@ -12,10 +12,10 @@
 {% for d in doc.taxes %}
 	{% if d.base_tax_amount %}
 	<tr>
-		<td class="text-right" colspan="2">
+		<td class="text-left" colspan="1">
 			{{ d.description }}
 		</td>
-		<td class="text-right">
+		<td class="text-right totals" colspan="3">
 			{{ d.get_formatted("base_tax_amount") }}
 		</td>
 	</tr>
@@ -23,76 +23,62 @@
 {% endfor %}
 
 {% if doc.doctype == 'Quotation' %}
-{% if doc.coupon_code %}
-<tr>
-	<th class="text-right" colspan="2">
-		{{ _("Discount") }}
-	</th>
-	<th class="text-right tot_quotation_discount">
-		{% set tot_quotation_discount = [] %}
-		{%- for item in doc.items -%}
-		{% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
-			* item.discount_percentage) / 100)) %}{% endif %}
-		{% endfor %}
-		{{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
-	</th>
-</tr>
-{% endif %}
+	{% if doc.coupon_code %}
+		<tr>
+			<td class="text-left total-discount" colspan="1">
+				{{ _("Savings") }}
+			</td>
+			<td class="text-right tot_quotation_discount total-discount totals" colspan="3">
+				{% set tot_quotation_discount = [] %}
+				{%- for item in doc.items -%}
+					{% if tot_quotation_discount.append((((item.price_list_rate * item.qty)
+						* item.discount_percentage) / 100)) %}
+					{% endif %}
+				{% endfor %}
+				{{ frappe.utils.fmt_money((tot_quotation_discount | sum),currency=doc.currency) }}
+			</td>
+		</tr>
+	{% endif %}
 {% endif %}
 
 {% if doc.doctype == 'Sales Order' %}
-{% if doc.coupon_code %}
-<tr>
-	<th class="text-right" colspan="2">
-		{{ _("Total Amount") }}
-	</th>
-	<th class="text-right">
-		<span>
-		{% set total_amount = [] %}
-		{%- for item in doc.items -%}
-		{% if total_amount.append((item.price_list_rate * item.qty)) %}{% endif %}
-		{% endfor %}
-		{{ frappe.utils.fmt_money((total_amount | sum),currency=doc.currency) }}
-		</span>
-	</th>
-</tr>
-<tr>
-	<th class="text-right" colspan="2">
-		{{ _("Applied Coupon Code") }}
-	</th>
-	<th class="text-right">
-		<span>
-		{%- for row in frappe.get_all(doctype="Coupon Code",
-		fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
-			<span>{{ row.coupon_code }}</span>
-		{% endfor %}
-		</span>
-	</th>
-</tr>
-<tr>
-	<th class="text-right" colspan="2">
-		{{ _("Discount") }}
-	</th>
-	<th class="text-right">
-		<span>
-		{% set tot_SO_discount = [] %}
-		{%- for item in doc.items -%}
-		{% if tot_SO_discount.append((((item.price_list_rate * item.qty)
-			* item.discount_percentage) / 100)) %}{% endif %}
-		{% endfor %}
-		{{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
-		</span>
-	</th>
-</tr>
-{% endif %}
+	{% if doc.coupon_code %}
+		<tr>
+			<td class="text-left total-discount" colspan="2" style="padding-right: 2rem;">
+				{{ _("Applied Coupon Code") }}
+			</td>
+			<td class="text-right total-discount">
+				<span>
+				{%- for row in frappe.get_all(doctype="Coupon Code",
+				fields=["coupon_code"], filters={ "name":doc.coupon_code}) -%}
+					<span>{{ row.coupon_code }}</span>
+				{% endfor %}
+				</span>
+			</td>
+		</tr>
+		<tr>
+			<td class="text-left total-discount" colspan="2">
+				{{ _("Savings") }}
+			</td>
+			<td class="text-right total-discount">
+				<span>
+				{% set tot_SO_discount = [] %}
+				{%- for item in doc.items -%}
+				{% if tot_SO_discount.append((((item.price_list_rate * item.qty)
+					* item.discount_percentage) / 100)) %}{% endif %}
+				{% endfor %}
+				{{ frappe.utils.fmt_money((tot_SO_discount | sum),currency=doc.currency) }}
+				</span>
+			</td>
+		</tr>
+	{% endif %}
 {% endif %}
 
 <tr>
-	<th></th>
-	<th class="item-grand-total">
+	<th class="text-left item-grand-total" colspan="1">
 		{{ _("Grand Total") }}
 	</th>
-	<th class="text-right item-grand-total">
+	<th class="text-right item-grand-total totals" colspan="3">
 		{{ doc.get_formatted("grand_total") }}
 	</th>
 </tr>
diff --git a/erpnext/templates/includes/product_page.js b/erpnext/templates/includes/product_page.js
index 90a1d86..a3979d0 100644
--- a/erpnext/templates/includes/product_page.js
+++ b/erpnext/templates/includes/product_page.js
@@ -7,7 +7,7 @@
 
 	frappe.call({
 		type: "POST",
-		method: "erpnext.shopping_cart.product_info.get_product_info_for_website",
+		method: "erpnext.e_commerce.shopping_cart.product_info.get_product_info_for_website",
 		args: {
 			item_code: get_item_code()
 		},
diff --git a/erpnext/templates/includes/products_as_list.html b/erpnext/templates/includes/products_as_list.html
index 9bf9fd9..a9369bb 100644
--- a/erpnext/templates/includes/products_as_list.html
+++ b/erpnext/templates/includes/products_as_list.html
@@ -1,5 +1,5 @@
-{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
-
+{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body, product_image_square %}
+<!-- Used in Product Search -->
 <a class="product-link product-list-link" href="{{ route|abs_url }}">
 	<div class='row'>
 		<div class='col-xs-3 col-sm-2 product-image-wrapper'>
diff --git a/erpnext/templates/pages/cart.html b/erpnext/templates/pages/cart.html
index c64c634..2b7d9e3 100644
--- a/erpnext/templates/pages/cart.html
+++ b/erpnext/templates/pages/cart.html
@@ -4,13 +4,6 @@
 
 {% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Shopping Cart") }}</h1>{% endblock %}
 
-<!--
-{% block script %}
-<script>{% include "templates/includes/cart.js" %}</script>
-{% endblock %}
--->
-
-
 {% block header_actions %}
 {% endblock %}
 
@@ -21,8 +14,9 @@
 {% if doc.items %}
 <div class="cart-container">
 	<div class="row m-0">
-		<div class="col-md-8 frappe-card p-5">
-			<div>
+		<!-- Left section -->
+		<div class="col-md-8">
+			<div class="frappe-card p-5 mb-4">
 				<div id="cart-error" class="alert alert-danger" style="display: none;"></div>
 				<div class="cart-items-header">
 					{{ _('Items') }}
@@ -30,89 +24,82 @@
 				<table class="table mt-3 cart-table">
 					<thead>
 						<tr>
-							<th width="60%">{{ _('Item') }}</th>
+							<th class="item-column">{{ _('Item') }}</th>
 							<th width="20%">{{ _('Quantity') }}</th>
-							{% if cart_settings.enable_checkout %}
-							<th width="20%" class="text-right">{{ _('Subtotal') }}</th>
+							{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
+								<th width="20" class="text-right column-sm-view">{{ _('Subtotal') }}</th>
 							{% endif %}
+							<th width="10%" class="column-sm-view"></th>
 						</tr>
 					</thead>
 					<tbody class="cart-items">
 						{% include "templates/includes/cart/cart_items.html" %}
 					</tbody>
-					{% if cart_settings.enable_checkout %}
-					<tfoot class="cart-tax-items">
-						{% include "templates/includes/order/order_taxes.html" %}
-					</tfoot>
+
+					{% if cart_settings.enable_checkout or cart_settings.show_price_in_quotation %}
+						<tfoot class="cart-tax-items">
+							{% include "templates/includes/cart/cart_items_total.html" %}
+						</tfoot>
 					{% endif %}
 				</table>
-			</div>
-			<div class="row">
-				<div class="col-4">
-					{% if cart_settings.enable_checkout %}
-					<a class="btn btn-outline-primary" href="/orders">
-						{{ _('See past orders') }}
-					</a>
-					{% else %}
-					<a class="btn btn-outline-primary" href="/quotations">
-						{{ _('See past quotations') }}
-					</a>
-					{% endif %}
-				</div>
-				<div class="col-8">
-					{% if doc.items %}
-					<div class="place-order-container">
-						<a class="btn btn-primary-light mr-2" href="/all-products">
-							{{ _("Continue Shopping") }}
-						</a>
+
+				<div class="row mt-2">
+					<div class="col-3">
 						{% if cart_settings.enable_checkout %}
-							<button class="btn btn-primary btn-place-order" type="button">
-								{{ _("Place Order") }}
-							</button>
+							<a class="btn btn-primary-light font-md" href="/orders">
+								{{ _('Past Orders') }}
+							</a>
 						{% else %}
-							<button class="btn btn-primary btn-request-for-quotation" type="button">
-								{{ _("Request for Quotation") }}
-							</button>
+							<a class="btn btn-primary-light font-md" href="/quotations">
+								{{ _('Past Quotes') }}
+							</a>
 						{% endif %}
 					</div>
-					{% endif %}
+					<div class="col-9">
+						{% if doc.items %}
+						<div class="place-order-container">
+							<a class="btn btn-primary-light mr-2 font-md" href="/all-products">
+								{{ _('Continue Shopping') }}
+							</a>
+						</div>
+						{% endif %}
+					</div>
 				</div>
 			</div>
 
-
+			<!-- Terms and Conditions -->
 			{% if doc.items %}
-			{% if doc.tc_name %}
-				<div class="terms-and-conditions-link">
-					<a href class="link-terms-and-conditions" data-terms-name="{{ doc.tc_name }}">
-						{{ _("Terms and Conditions") }}
-					</a>
-					<script>
-						frappe.ready(() => {
-							$('.link-terms-and-conditions').click((e) => {
-								e.preventDefault();
-								const $link = $(e.target);
-								const terms_name = $link.attr('data-terms-name');
-								show_terms_and_conditions(terms_name);
-							})
-						});
-						function show_terms_and_conditions(terms_name) {
-							frappe.call('erpnext.shopping_cart.cart.get_terms_and_conditions', { terms_name })
-							.then(r => {
-								frappe.msgprint({
-									title: terms_name,
-									message: r.message
-								});
-							});
-						}
-					</script>
-				</div>
-			{% endif %}
+				{% if doc.terms %}
+					<div class="t-and-c-container mt-4 frappe-card">
+						<h5>{{ _("Terms and Conditions") }}</h5>
+						<div class="t-and-c-terms mt-2">
+							{{ doc.terms }}
+						</div>
+					</div>
+				{% endif %}
 		</div>
 
+		<!-- Right section -->
 		<div class="col-md-4">
-			<div class="cart-addresses">
-				{% include "templates/includes/cart/cart_address.html" %}
+			<div class="cart-payment-addresses">
+				<!-- Apply Coupon Code  -->
+				{% set show_coupon_code = cart_settings.show_apply_coupon_code_in_website and cart_settings.enable_checkout %}
+				{% if show_coupon_code == 1%}
+					<div class="mb-3">
+						<div class="row no-gutters">
+							<input type="text" class="txtcoupon form-control mr-3 w-50 font-md" placeholder="Enter Coupon Code" name="txtcouponcode"  ></input>
+							<button class="btn btn-primary btn-sm bt-coupon font-md">{{ _("Apply Coupon Code") }}</button>
+							<input type="hidden" class="txtreferral_sales_partner font-md" placeholder="Enter Sales Partner" name="txtreferral_sales_partner" type="text"></input>
+							</div>
+					</div>
+				{% endif %}
+
+				<div class="mb-3 frappe-card p-5 payment-summary">
+					{% include "templates/includes/cart/cart_payment_summary.html" %}
 				</div>
+
+				{% include "templates/includes/cart/cart_address.html" %}
+			</div>
 		</div>
 		{% endif %}
 	</div>
@@ -124,11 +111,11 @@
 	</div>
 	<div class="cart-empty-message mt-4">{{ _('Your cart is Empty') }}</p>
 	{% if cart_settings.enable_checkout %}
-		<a class="btn btn-outline-primary" href="/orders">
+		<a class="btn btn-outline-primary" href="/orders" style="font-size: 16px;">
 			{{ _('See past orders') }}
 		</a>
 		{% else %}
-		<a class="btn btn-outline-primary" href="/quotations">
+		<a class="btn btn-outline-primary" href="/quotations" style="font-size: 16px;">
 			{{ _('See past quotations') }}
 		</a>
 	{% endif %}
diff --git a/erpnext/templates/includes/cart.js b/erpnext/templates/pages/cart.js
similarity index 84%
rename from erpnext/templates/includes/cart.js
rename to erpnext/templates/pages/cart.js
index c390cd1..fb2d159 100644
--- a/erpnext/templates/includes/cart.js
+++ b/erpnext/templates/pages/cart.js
@@ -1,11 +1,9 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-// js inside blog page
-
-// shopping cart
-frappe.provide("erpnext.shopping_cart");
-var shopping_cart = erpnext.shopping_cart;
+// JS exclusive to /cart page
+frappe.provide("erpnext.e_commerce.shopping_cart");
+var shopping_cart = erpnext.e_commerce.shopping_cart;
 
 $.extend(shopping_cart, {
 	show_error: function(title, text) {
@@ -18,8 +16,8 @@
 		shopping_cart.bind_place_order();
 		shopping_cart.bind_request_quotation();
 		shopping_cart.bind_change_qty();
+		shopping_cart.bind_remove_cart_item();
 		shopping_cart.bind_change_notes();
-		shopping_cart.bind_dropdown_cart_buttons();
 		shopping_cart.bind_coupon_code();
 	},
 
@@ -48,7 +46,7 @@
 				const address_name = $card.closest('[data-address-name]').attr('data-address-name');
 				frappe.call({
 					type: "POST",
-					method: "erpnext.shopping_cart.cart.update_cart_address",
+					method: "erpnext.e_commerce.shopping_cart.cart.update_cart_address",
 					freeze: true,
 					args: {
 						address_type,
@@ -57,7 +55,7 @@
 					callback: function(r) {
 						d.hide();
 						if (!r.exc) {
-							$(".cart-tax-items").html(r.message.taxes);
+							$(".cart-tax-items").html(r.message.total);
 							shopping_cart.parent.find(
 								`.address-container[data-address-type="${address_type}"]`
 							).html(r.message.address);
@@ -129,8 +127,14 @@
 				}
 			}
 			input.val(newVal);
+
+			let notes = input.closest("td").siblings().find(".notes").text().trim();
 			var item_code = input.attr("data-item-code");
-			shopping_cart.shopping_cart_update({item_code, qty: newVal});
+			shopping_cart.shopping_cart_update({
+				item_code,
+				qty: newVal,
+				additional_notes: notes
+			});
 		});
 	},
 
@@ -148,6 +152,18 @@
 		});
 	},
 
+	bind_remove_cart_item: function() {
+		$(".cart-items").on("click", ".remove-cart-item", (e) => {
+			const $remove_cart_item_btn = $(e.currentTarget);
+			var item_code = $remove_cart_item_btn.data("item-code");
+
+			shopping_cart.shopping_cart_update({
+				item_code: item_code,
+				qty: 0
+			});
+		});
+	},
+
 	render_tax_row: function($cart_taxes, doc, shipping_rules) {
 		var shipping_selector;
 		if(shipping_rules) {
@@ -185,7 +201,7 @@
 		return frappe.call({
 			btn: btn,
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.apply_shipping_rule",
+			method: "erpnext.e_commerce.shopping_cart.cart.apply_shipping_rule",
 			args: { shipping_rule: rule },
 			callback: function(r) {
 				if(!r.exc) {
@@ -196,12 +212,15 @@
 	},
 
 	place_order: function(btn) {
+		shopping_cart.freeze();
+
 		return frappe.call({
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.place_order",
+			method: "erpnext.e_commerce.shopping_cart.cart.place_order",
 			btn: btn,
 			callback: function(r) {
 				if(r.exc) {
+					shopping_cart.unfreeze();
 					var msg = "";
 					if(r._server_messages) {
 						msg = JSON.parse(r._server_messages || []).join("<br>");
@@ -212,7 +231,6 @@
 						.html(msg || frappe._("Something went wrong!"))
 						.toggle(true);
 				} else {
-					$('.cart-container table').hide();
 					$(btn).hide();
 					window.location.href = '/orders/' + encodeURIComponent(r.message);
 				}
@@ -221,12 +239,15 @@
 	},
 
 	request_quotation: function(btn) {
+		shopping_cart.freeze();
+
 		return frappe.call({
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.request_for_quotation",
+			method: "erpnext.e_commerce.shopping_cart.cart.request_for_quotation",
 			btn: btn,
 			callback: function(r) {
 				if(r.exc) {
+					shopping_cart.unfreeze();
 					var msg = "";
 					if(r._server_messages) {
 						msg = JSON.parse(r._server_messages || []).join("<br>");
@@ -237,7 +258,6 @@
 						.html(msg || frappe._("Something went wrong!"))
 						.toggle(true);
 				} else {
-					$('.cart-container table').hide();
 					$(btn).hide();
 					window.location.href = '/quotations/' + encodeURIComponent(r.message);
 				}
@@ -254,7 +274,7 @@
 	apply_coupon_code: function(btn) {
 		return frappe.call({
 			type: "POST",
-			method: "erpnext.shopping_cart.cart.apply_coupon_code",
+			method: "erpnext.e_commerce.shopping_cart.cart.apply_coupon_code",
 			btn: btn,
 			args : {
 				applied_code : $('.txtcoupon').val(),
@@ -270,7 +290,9 @@
 });
 
 frappe.ready(function() {
-	$(".cart-icon").hide();
+	if (window.location.pathname === "/cart") {
+		$(".cart-icon").hide();
+	}
 	shopping_cart.parent = $(".cart-container");
 	shopping_cart.bind_events();
 });
diff --git a/erpnext/templates/pages/cart.py b/erpnext/templates/pages/cart.py
index 0bba1ff..cadb46f 100644
--- a/erpnext/templates/pages/cart.py
+++ b/erpnext/templates/pages/cart.py
@@ -1,11 +1,11 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
 no_cache = 1
 
-
-from erpnext.shopping_cart.cart import get_cart_quotation
+from erpnext.e_commerce.shopping_cart.cart import get_cart_quotation
 
 
 def get_context(context):
+	context.body_class = "product-page"
 	context.update(get_cart_quotation())
diff --git a/erpnext/templates/pages/cart_terms.html b/erpnext/templates/pages/cart_terms.html
deleted file mode 100644
index 6d84fb8..0000000
--- a/erpnext/templates/pages/cart_terms.html
+++ /dev/null
@@ -1,2 +0,0 @@
-
-<div>{{doc.terms}}</div>
diff --git a/erpnext/templates/pages/customer_reviews.html b/erpnext/templates/pages/customer_reviews.html
new file mode 100644
index 0000000..121bec3
--- /dev/null
+++ b/erpnext/templates/pages/customer_reviews.html
@@ -0,0 +1,67 @@
+{% extends "templates/web.html" %}
+{% from "erpnext/templates/includes/macros.html" import user_review, ratings_summary %}
+
+{% block title %} {{ _("Customer Reviews") }} {% endblock %}
+
+{% block page_content %}
+<div class="product-container reviews-full-page col-md-12">
+	{% if enable_reviews %}
+		<!-- Title and Action -->
+		<div class="w-100 mb-6 d-flex">
+			<div class="reviews-header col-9">
+				{{ _("Customer Reviews") }}
+			</div>
+
+			<div class="write-a-review-btn col-3">
+				<!-- Write a Review for legitimate users -->
+				{% if frappe.session.user != "Guest" and user_is_customer %}
+					<button class="btn btn-write-review"
+						data-web-item="{{ web_item }}">
+						{{ _("Write a Review") }}
+					</button>
+				{% endif %}
+			</div>
+		</div>
+
+		<!-- Summary -->
+		{{ ratings_summary(reviews, reviews_per_rating, average_rating, average_whole_rating, for_summary=True, total_reviews=total_reviews) }}
+
+
+		<!-- Reviews and Comments -->
+		<div class="mt-8">
+			{% if reviews %}
+				{{ user_review(reviews) }}
+
+				{% if not reviews | len >= total_reviews %}
+					<button class="btn btn-light btn-view-more mr-2 mt-4 mb-4 w-30"
+						data-web-item="{{ web_item }}">
+						{{ _("View More") }}
+					</button>
+				{% endif %}
+
+			{% else %}
+				<h6 class="text-muted mt-6">
+					{{ _("No Reviews") }}
+				</h6>
+			{% endif %}
+		</div>
+	{% else %}
+		<!-- If reviews are disabled -->
+		<div class="text-center">
+			<h3 class="text-muted mt-8">
+				{{ _("No Reviews") }}
+			</h3>
+		</div>
+	{% endif %}
+</div>
+
+{% endblock %}
+
+{% block base_scripts %}
+<!-- js should be loaded in body! -->
+<script type="text/javascript" src="/assets/frappe/js/lib/jquery/jquery.min.js"></script>
+<script type="text/javascript" src="/assets/js/frappe-web.min.js"></script>
+<script type="text/javascript" src="/assets/js/control.min.js"></script>
+<script type="text/javascript" src="/assets/js/dialog.min.js"></script>
+<script type="text/javascript" src="/assets/js/bootstrap-4-web.min.js"></script>
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/customer_reviews.py b/erpnext/templates/pages/customer_reviews.py
new file mode 100644
index 0000000..c1f0c93
--- /dev/null
+++ b/erpnext/templates/pages/customer_reviews.py
@@ -0,0 +1,25 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+import frappe
+
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
+	get_shopping_cart_settings,
+)
+from erpnext.e_commerce.doctype.item_review.item_review import get_item_reviews
+from erpnext.e_commerce.doctype.website_item.website_item import check_if_user_is_customer
+
+
+def get_context(context):
+	context.body_class = "product-page"
+	context.no_cache = 1
+	context.full_page = True
+	context.reviews = None
+
+	if frappe.form_dict and frappe.form_dict.get("web_item"):
+		context.web_item = frappe.form_dict.get("web_item")
+		context.user_is_customer = check_if_user_is_customer()
+		context.enable_reviews = get_shopping_cart_settings().enable_reviews
+
+		if context.enable_reviews:
+			reviews_data = get_item_reviews(context.web_item)
+			context.update(reviews_data)
diff --git a/erpnext/templates/pages/home.py b/erpnext/templates/pages/home.py
index 5d046a8..d08e81b 100644
--- a/erpnext/templates/pages/home.py
+++ b/erpnext/templates/pages/home.py
@@ -10,7 +10,7 @@
 	homepage = frappe.get_doc('Homepage')
 
 	for item in homepage.products:
-		route = frappe.db.get_value('Item', item.item_code, 'route')
+		route = frappe.db.get_value('Website Item', {"item_code": item.item_code}, 'route')
 		if route:
 			item.route = '/' + route
 
diff --git a/erpnext/templates/pages/non_profit/__init__.py b/erpnext/templates/pages/non_profit/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/templates/pages/non_profit/__init__.py
+++ /dev/null
diff --git a/erpnext/templates/pages/non_profit/join-chapter.html b/erpnext/templates/pages/non_profit/join-chapter.html
deleted file mode 100644
index 4923efc..0000000
--- a/erpnext/templates/pages/non_profit/join-chapter.html
+++ /dev/null
@@ -1,59 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block page_content %}
-
-{% macro chapter_button() %}
-	<p><a href="/{{ chapter.route }}" class='btn btn-primary'>
-		Go to Chapter Page</a></p>
-{% endmacro %}
-{% if frappe.session.user=='Guest' %}
-	<p>Please signup and login to join this chapter</p>
-	<p><a href="/login?redirect-to=/{{ chapter.route }}" class='btn btn-primary'>Login</a></p>
-{% else %}
-	{% if already_member %}
-		<p>You are already a member of {{ chapter.name }}!</p>
-		{{ chapter_button() }}
-		<p><a href="">Leave Chapter</a></p>
-	{% else %}
-		{% if request.method=='POST' %}
-			<p>Welcome to chapter {{ chapter.name }}!</p>
-			{{ chapter_button() }}
-		{% else %}
-			<div style="padding: 20px 0;">
-				<div class="row">
-					<div class="col-lg-8 col-md-8">
-						<form name="user-intro" action="/non_profit/join-chapter" method='POST'>
-							<div class="form-group">
-								<input name="name" class="hidden form-control" type="text"
-									value="{{chapter.name}}">
-								<input name="csrf_token" class="hidden  form-control" type="text"
-									value="{{frappe.session.csrf_token}}">
-							</div>
-							<div class="form-group">
-								<label for="user" class="">User Name</label>
-								<input name="user" class="form-control" type="text" value="{{ frappe.session.user }}">
-							</div>
-							<div class="form-group">
-								<label for="website_url" class="">Website URL</label>
-								<input name="website_url" required class="form-control" type="text"
-									placeholder="https://example.com" />
-							</div>
-							<div class="form-group">
-								<label for="introduction" class="">Introduction</label>
-								<textarea name="introduction" required class="form-control"
-									placeholder="Your profession and how you are associated with ERPNext"></textarea>
-							</div>
-							<div class="form-group">
-								<button type="Submit" id="update_member" class="btn btn-primary">
-									Submit</button>
-							</div>
-						</form>
-					</div>
-				</div>
-			</div>
-		{% endif %}
-	{% endif %}
-
-{% endif %}
-
-{% endblock %}
diff --git a/erpnext/templates/pages/non_profit/join_chapter.js b/erpnext/templates/pages/non_profit/join_chapter.js
deleted file mode 100644
index e2bc8bc..0000000
--- a/erpnext/templates/pages/non_profit/join_chapter.js
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright (c) 2017, EOSSF and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Chapter Member', {
-	onsubmit: function (frm) {
-		console.log("here" + frappe.session.user)
-		// body...
-	}
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/templates/pages/non_profit/join_chapter.py b/erpnext/templates/pages/non_profit/join_chapter.py
deleted file mode 100644
index 7caf87d..0000000
--- a/erpnext/templates/pages/non_profit/join_chapter.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import frappe
-
-
-def get_context(context):
-	context.no_cache = True
-	chapter = frappe.get_doc('Chapter', frappe.form_dict.name)
-	if frappe.session.user!='Guest':
-		if frappe.session.user in [d.user for d in chapter.members if d.enabled == 1]:
-			context.already_member = True
-		else:
-			if frappe.request.method=='GET':
-				pass
-			elif frappe.request.method=='POST':
-				chapter.append('members', dict(
-					user=frappe.session.user,
-					introduction=frappe.form_dict.introduction,
-					website_url=frappe.form_dict.website_url,
-					enabled=1
-				))
-				chapter.save(ignore_permissions=1)
-				frappe.db.commit()
-
-	context.chapter = chapter
diff --git a/erpnext/templates/pages/non_profit/leave-chapter.html b/erpnext/templates/pages/non_profit/leave-chapter.html
deleted file mode 100644
index fd7658b..0000000
--- a/erpnext/templates/pages/non_profit/leave-chapter.html
+++ /dev/null
@@ -1,42 +0,0 @@
-{% extends "templates/web.html" %}
-{% block page_content %}
-
-	{% if member_deleted %}
-		<p>You are not a member of {{ chapter.name }}!</p>
-		<div>
-			<form>
-				<div class="form-group">
-					<label for="leave">Why do you want to leave this chapter</label>
-					<input type="text" name="leave" class="form-control" id="leave">
-				</div>
-				<button type="button" class="btn btn-light btn-leave" data-title= "{{ chapter.name }}" id="btn-leave">Submit
-				</button>
-			</form>
-		</div>
-		<p>Please signup and login to join this chapter</p>
-
-		<p><a href="/join-chapter?name={{ chapter.name }}" class='btn btn-primary'>Become Member agian</a></p>
-	{% endif %}
-	<script>
-		frappe.ready(function() {
-			$(".btn-leave").on("click", function() {
-				var leave =  $("#leave").val();
-				var user_id = frappe.session.user;
-				var title =  $(this).attr("data-title");
-				frappe.call({
-						method: "erpnext.non_profit.doctype.chapter.chapter.leave",
-						args: {
-							leave_reason: leave,
-							user_id: user_id,
-							title: title
-						},
-						callback: function(r) {
-							if(r.message) {
-								frappe.msgprint(r.message)
-							}
-						}
-				})
-			});
-		})
-	</script>
-{% endblock %}
diff --git a/erpnext/templates/pages/non_profit/leave_chapter.py b/erpnext/templates/pages/non_profit/leave_chapter.py
deleted file mode 100644
index 65908e1..0000000
--- a/erpnext/templates/pages/non_profit/leave_chapter.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import frappe
-
-
-def get_context(context):
-	context.no_cache = True
-	chapter = frappe.get_doc('Chapter', frappe.form_dict.name)
-	context.member_deleted = True
-	context.chapter = chapter
diff --git a/erpnext/templates/pages/order.html b/erpnext/templates/pages/order.html
index 28faea8..a10870d 100644
--- a/erpnext/templates/pages/order.html
+++ b/erpnext/templates/pages/order.html
@@ -12,172 +12,173 @@
 {% endblock %}
 
 {% block header_actions %}
-<div class="dropdown">
-	<button class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
-		<span>{{ _('Actions') }}</span>
-		<b class="caret"></b>
-	</button>
-	<ul class="dropdown-menu dropdown-menu-right" role="menu">
-		{% if doc.doctype == 'Purchase Order' %}
-		<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
-		{% endif %}
-		<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
-			target="_blank" rel="noopener noreferrer">
-			{{ _("Print") }}
-		</a>
-	</ul>
-</div>
-
+	<div class="dropdown">
+		<button class="btn btn-outline-secondary dropdown-toggle" data-toggle="dropdown" aria-expanded="false">
+			<span class="font-md">{{ _('Actions') }}</span>
+			<b class="caret"></b>
+		</button>
+		<ul class="dropdown-menu dropdown-menu-right" role="menu">
+			{% if doc.doctype == 'Purchase Order' %}
+				<a class="dropdown-item" href="/api/method/erpnext.buying.doctype.purchase_order.purchase_order.make_purchase_invoice_from_portal?purchase_order_name={{ doc.name }}" data-action="make_purchase_invoice">{{ _("Make Purchase Invoice") }}</a>
+			{% endif %}
+			<a class="dropdown-item" href='/printview?doctype={{ doc.doctype}}&name={{ doc.name }}&format={{ print_format }}'
+				target="_blank" rel="noopener noreferrer">
+				{{ _("Print") }}
+			</a>
+		</ul>
+	</div>
 {% endblock %}
 
 {% block page_content %}
-
-<div class="row transaction-subheading">
-	<div class="col-6">
-		<span class="indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
-			{% if doc.doctype == "Quotation" and not doc.docstatus %}
-				{{ _("Pending") }}
-			{% else %}
-				{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
-			{% endif %}
-		</span>
-	</div>
-	<div class="col-6 text-muted text-right small pt-3">
-		{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
-		{% if doc.valid_till %}
-		<p>
-		{{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}
-		</p>
-		{% endif %}
-	</div>
-</div>
-
-<p class="small my-3">
-	{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %}
-	<b>{{ party_name }}</b>
-
-	{% if doc.contact_display and doc.contact_display != party_name %}
-		<br>
-		{{ doc.contact_display }}
-	{% endif %}
-</p>
-
-{% if doc._header %}
-{{ doc._header }}
-{% endif %}
-
-<div class="order-container">
-	<!-- items -->
-	<table class="order-item-table w-100 table">
-		<thead class="order-items order-item-header">
-			<th width="60%">
-				{{ _("Item") }}
-			</th>
-			<th width="20%" class="text-right">
-				{{ _("Quantity") }}
-			</th>
-			<th width="20%" class="text-right">
-				{{ _("Amount") }}
-			</th>
-		</thead>
-		<tbody>
-		{% for d in doc.items %}
-		<tr class="order-items">
-			<td>
-				{{ item_name_and_description(d) }}
-			</td>
-			<td class="text-right">
-				{{ d.qty }}
-				{% if d.delivered_qty is defined and d.delivered_qty != None %}
-				<p class="text-muted small">{{ _("Delivered") }}&nbsp;{{ d.delivered_qty }}</p>
+	<div class="row transaction-subheading">
+		<div class="col-6">
+			<span class="font-md indicator-pill {{ doc.indicator_color or ("blue" if doc.docstatus==1 else "darkgrey") }}">
+				{% if doc.doctype == "Quotation" and not doc.docstatus %}
+					{{ _("Pending") }}
+				{% else %}
+					{{ _(doc.get('indicator_title')) or _(doc.status) or _("Submitted") }}
 				{% endif %}
-			</td>
-			<td class="text-right">
-				{{ d.get_formatted("amount")	 }}
-				<p class="text-muted small">{{ _("Rate:") }}&nbsp;{{ d.get_formatted("rate") }}</p>
-			</td>
-		</tr>
-		{% endfor %}
-		</tbody>
-	</table>
-	<!-- taxes -->
-	<div class="order-taxes d-flex justify-content-end">
-		<table>
-			{% include "erpnext/templates/includes/order/order_taxes.html" %}
-		</table>
-	</div>
-</div>
-
-{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0)
-	or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0)) %}
-
-<div class="panel panel-default">
-	<div class="panel-heading">
-		<div class="row">
-			<div class="form-column col-sm-6 address-title">
-				<strong>Payment</strong>
-			</div>
+			</span>
 		</div>
-	</div>
-	<div class="panel-collapse">
-		<div class="panel-body text-muted small">
-			<div class="row">
-				<div class="form-column col-sm-6">
-					{% if available_loyalty_points %}
-					<div class="form-group">
-						<div class="h6">Enter Loyalty Points</div>
-						<div class="control-input-wrapper">
-							<div class="control-input">
-								<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
-							</div>
-							<p class="help-box small text-muted d-none d-sm-block"> Available Points: {{ available_loyalty_points }} </p>
-						</div>
-					</div>
-					{% endif %}
-				</div>
-
-				<div class="form-column col-sm-6">
-					<div id="loyalty-points-status" style="text-align: right"></div>
-					<div class="page-header-actions-block" data-html-block="header-actions">
-						<p>
-							<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
-								class="btn btn-primary btn-sm" id="pay-for-order">{{ _("Pay") }} {{ doc.get_formatted("grand_total") }} </a>
-						</p>
-					</div>
-				</div>
-
-			</div>
-
-		</div>
-	</div>
-</div>
-{% endif %}
-
-
-{% if attachments %}
-<div class="order-item-table">
-	<div class="row order-items order-item-header text-muted">
-		<div class="col-sm-12 h6 text-uppercase">
-			{{ _("Attachments") }}
-		</div>
-	</div>
-	<div class="row order-items">
-		<div class="col-sm-12">
-			{% for attachment in attachments %}
-			<p class="small">
-				<a href="{{ attachment.file_url }}" target="blank"> {{ attachment.file_name }} </a>
+		<div class="col-6 text-muted text-right small pt-3">
+			{{ frappe.utils.format_date(doc.transaction_date, 'medium') }}
+			{% if doc.valid_till %}
+			<p>
+			{{ _("Valid Till") }}: {{ frappe.utils.format_date(doc.valid_till, 'medium') }}
 			</p>
-			{% endfor %}
+			{% endif %}
 		</div>
 	</div>
-</div>
-{% endif %}
-</div>
-{% if doc.terms %}
-<div class="terms-and-condition text-muted small">
-	<hr><p>{{ doc.terms }}</p>
-</div>
-{% endif %}
+
+	<p class="small my-3">
+		{%- set party_name = doc.supplier_name if doc.doctype in ['Supplier Quotation', 'Purchase Invoice', 'Purchase Order'] else doc.customer_name %}
+		<b>{{ party_name }}</b>
+
+		{% if doc.contact_display and doc.contact_display != party_name %}
+			<br>
+			{{ doc.contact_display }}
+		{% endif %}
+	</p>
+
+	{% if doc._header %}
+		{{ doc._header }}
+	{% endif %}
+
+	<div class="order-container">
+		<!-- items -->
+		<table class="order-item-table w-100 table">
+			<thead class="order-items order-item-header">
+				<th width="60%">
+					{{ _("Item") }}
+				</th>
+				<th width="20%" class="text-right">
+					{{ _("Quantity") }}
+				</th>
+				<th width="20%" class="text-right">
+					{{ _("Amount") }}
+				</th>
+			</thead>
+			<tbody>
+			{% for d in doc.items %}
+			<tr class="order-items">
+				<td>
+					{{ item_name_and_description(d) }}
+				</td>
+				<td class="text-right">
+					{{ d.qty }}
+					{% if d.delivered_qty is defined and d.delivered_qty != None %}
+						<p class="text-muted small">{{ _("Delivered") }}&nbsp;{{ d.delivered_qty }}</p>
+					{% endif %}
+				</td>
+				<td class="text-right">
+					{{ d.get_formatted("amount")	 }}
+					<p class="text-muted small">{{ _("Rate:") }}&nbsp;{{ d.get_formatted("rate") }}</p>
+				</td>
+			</tr>
+			{% endfor %}
+			</tbody>
+		</table>
+		<!-- taxes -->
+		<div class="order-taxes d-flex justify-content-end">
+			<table>
+				{% include "erpnext/templates/includes/order/order_taxes.html" %}
+			</table>
+		</div>
+	</div>
+
+	{% if enabled_checkout and ((doc.doctype=="Sales Order" and doc.per_billed <= 0)
+		or (doc.doctype=="Sales Invoice" and doc.outstanding_amount > 0)) %}
+		<div class="panel panel-default">
+			<div class="panel-heading">
+				<div class="row">
+					<div class="form-column col-sm-6 address-title">
+						<strong>Payment</strong>
+					</div>
+				</div>
+			</div>
+			<div class="panel-collapse">
+				<div class="panel-body text-muted small">
+					<div class="row">
+						<div class="form-column col-sm-6">
+							{% if available_loyalty_points %}
+							<div class="form-group">
+								<div class="h6">Enter Loyalty Points</div>
+								<div class="control-input-wrapper">
+									<div class="control-input">
+										<input class="form-control" type="number" min="0" max="{{ available_loyalty_points }}" id="loyalty-point-to-redeem">
+									</div>
+									<p class="help-box small text-muted d-none d-sm-block"> Available Points: {{ available_loyalty_points }} </p>
+								</div>
+							</div>
+							{% endif %}
+						</div>
+
+						<div class="form-column col-sm-6">
+							<div id="loyalty-points-status" style="text-align: right"></div>
+							<div class="page-header-actions-block" data-html-block="header-actions">
+								<p class="mt-2" style="float: right;">
+									<a href="/api/method/erpnext.accounts.doctype.payment_request.payment_request.make_payment_request?dn={{ doc.name }}&dt={{ doc.doctype }}&submit_doc=1&order_type=Shopping Cart"
+										class="btn btn-primary btn-sm"
+										id="pay-for-order">
+										{{ _("Pay") }} {{ doc.get_formatted("grand_total") }}
+									</a>
+								</p>
+							</div>
+						</div>
+
+					</div>
+
+				</div>
+			</div>
+		</div>
+	{% endif %}
+
+
+	{% if attachments %}
+		<div class="order-item-table">
+			<div class="row order-items order-item-header text-muted">
+				<div class="col-sm-12 h6 text-uppercase">
+					{{ _("Attachments") }}
+				</div>
+			</div>
+			<div class="row order-items">
+				<div class="col-sm-12">
+					{% for attachment in attachments %}
+					<p class="small">
+						<a href="{{ attachment.file_url }}" target="blank"> {{ attachment.file_name }} </a>
+					</p>
+					{% endfor %}
+				</div>
+			</div>
+		</div>
+	{% endif %}
+	</div>
+
+	{% if doc.terms %}
+		<div class="terms-and-condition text-muted small">
+			<hr><p>{{ doc.terms }}</p>
+		</div>
+	{% endif %}
 {% endblock %}
 
 {% block script %}
diff --git a/erpnext/templates/pages/order.py b/erpnext/templates/pages/order.py
index 2aa0f9c..712b141 100644
--- a/erpnext/templates/pages/order.py
+++ b/erpnext/templates/pages/order.py
@@ -1,13 +1,10 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
 from frappe import _
 
-from erpnext.shopping_cart.doctype.shopping_cart_settings.shopping_cart_settings import (
-	show_attachments,
-)
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import show_attachments
 
 
 def get_context(context):
@@ -25,7 +22,7 @@
 	context.payment_ref = frappe.db.get_value("Payment Request",
 		{"reference_name": frappe.form_dict.name}, "name")
 
-	context.enabled_checkout = frappe.get_doc("Shopping Cart Settings").enable_checkout
+	context.enabled_checkout = frappe.get_doc("E Commerce Settings").enable_checkout
 
 	default_print_format = frappe.db.get_value('Property Setter', dict(property='default_print_format', doc_type=frappe.form_dict.doctype), "value")
 	if default_print_format:
diff --git a/erpnext/templates/pages/product_search.py b/erpnext/templates/pages/product_search.py
index 5aa1f1e..237adf9 100644
--- a/erpnext/templates/pages/product_search.py
+++ b/erpnext/templates/pages/product_search.py
@@ -1,12 +1,19 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
-from frappe.utils import cint, cstr, nowdate
+from frappe.utils import cint, cstr
+from redisearch import AutoCompleter, Client, Query
 
+from erpnext.e_commerce.redisearch_utils import (
+	WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE,
+	WEBSITE_ITEM_INDEX,
+	WEBSITE_ITEM_NAME_AUTOCOMPLETE,
+	is_search_module_loaded,
+	make_key,
+)
+from erpnext.e_commerce.shopping_cart.product_info import set_product_info_for_website
 from erpnext.setup.doctype.item_group.item_group import get_item_for_list_in_html
-from erpnext.shopping_cart.product_info import set_product_info_for_website
 
 no_cache = 1
 
@@ -15,36 +22,116 @@
 
 @frappe.whitelist(allow_guest=True)
 def get_product_list(search=None, start=0, limit=12):
-	# limit = 12 because we show 12 items in the grid view
-
-	# base query
-	query = """select I.name, I.item_name, I.item_code, I.route, I.image, I.website_image, I.thumbnail, I.item_group,
-			I.description, I.web_long_description as website_description, I.is_stock_item,
-			case when (S.actual_qty - S.reserved_qty) > 0 then 1 else 0 end as in_stock, I.website_warehouse,
-			I.has_batch_no
-		from `tabItem` I
-		left join tabBin S on I.item_code = S.item_code and I.website_warehouse = S.warehouse
-		where (I.show_in_website = 1)
-			and I.disabled = 0
-			and (I.end_of_life is null or I.end_of_life='0000-00-00' or I.end_of_life > %(today)s)"""
-
-	# search term condition
-	if search:
-		query += """ and (I.web_long_description like %(search)s
-				or I.description like %(search)s
-				or I.item_name like %(search)s
-				or I.name like %(search)s)"""
-		search = "%" + cstr(search) + "%"
-
-	# order by
-	query += """ order by I.weightage desc, in_stock desc, I.modified desc limit %s, %s""" % (cint(start), cint(limit))
-
-	data = frappe.db.sql(query, {
-		"search": search,
-		"today": nowdate()
-	}, as_dict=1)
+	data = get_product_data(search, start, limit)
 
 	for item in data:
 		set_product_info_for_website(item)
 
 	return [get_item_for_list_in_html(r) for r in data]
+
+def get_product_data(search=None, start=0, limit=12):
+	# limit = 12 because we show 12 items in the grid view
+	# base query
+	query = """
+		SELECT
+			web_item_name, item_name, item_code, brand, route,
+			website_image, thumbnail, item_group,
+			description, web_long_description as website_description,
+			website_warehouse, ranking
+		FROM `tabWebsite Item`
+		WHERE published = 1
+		"""
+
+	# search term condition
+	if search:
+		query += """ and (item_name like %(search)s
+				or web_item_name like %(search)s
+				or brand like %(search)s
+				or web_long_description like %(search)s)"""
+		search = "%" + cstr(search) + "%"
+
+	# order by
+	query += """ ORDER BY ranking desc, modified desc limit %s, %s""" % (cint(start), cint(limit))
+
+	return frappe.db.sql(query, {"search": search}, as_dict=1) # nosemgrep
+
+@frappe.whitelist(allow_guest=True)
+def search(query):
+	product_results = product_search(query)
+	category_results = get_category_suggestions(query)
+
+	return {
+		"product_results": product_results.get("results") or [],
+		"category_results": category_results.get("results") or []
+	}
+
+@frappe.whitelist(allow_guest=True)
+def product_search(query, limit=10, fuzzy_search=True):
+	search_results = {"from_redisearch": True, "results": []}
+
+	if not is_search_module_loaded():
+		# Redisearch module not loaded
+		search_results["from_redisearch"] = False
+		search_results["results"] = get_product_data(query, 0, limit)
+		return search_results
+
+	if not query:
+		return search_results
+
+	red = frappe.cache()
+	query = clean_up_query(query)
+
+	ac = AutoCompleter(make_key(WEBSITE_ITEM_NAME_AUTOCOMPLETE), conn=red)
+	client = Client(make_key(WEBSITE_ITEM_INDEX), conn=red)
+	suggestions = ac.get_suggestions(
+		query,
+		num=limit,
+		fuzzy= fuzzy_search and len(query) > 3 # Fuzzy on length < 3 can be real slow
+	)
+
+	# Build a query
+	query_string = query
+
+	for s in suggestions:
+		query_string += f"|('{clean_up_query(s.string)}')"
+
+	q = Query(query_string)
+
+	results = client.search(q)
+	search_results['results'] = list(map(convert_to_dict, results.docs))
+	search_results['results'] = sorted(search_results['results'], key=lambda k: frappe.utils.cint(k['ranking']), reverse=True)
+
+	return search_results
+
+def clean_up_query(query):
+	return ''.join(c for c in query if c.isalnum() or c.isspace())
+
+def convert_to_dict(redis_search_doc):
+	return redis_search_doc.__dict__
+
+@frappe.whitelist(allow_guest=True)
+def get_category_suggestions(query):
+	search_results = {"results": []}
+
+	if not is_search_module_loaded():
+		# Redisearch module not loaded, query db
+		categories = frappe.db.get_all(
+			"Item Group",
+			filters={
+				"name": ["like", "%{0}%".format(query)],
+				"show_in_website": 1
+			},
+			fields=["name", "route"]
+		)
+		search_results['results'] = categories
+		return search_results
+
+	if not query:
+		return search_results
+
+	ac = AutoCompleter(make_key(WEBSITE_ITEM_CATEGORY_AUTOCOMPLETE), conn=frappe.cache())
+	suggestions = ac.get_suggestions(query, num=10)
+
+	search_results['results'] = [s.string for s in suggestions]
+
+	return search_results
\ No newline at end of file
diff --git a/erpnext/templates/pages/wishlist.html b/erpnext/templates/pages/wishlist.html
new file mode 100644
index 0000000..7a81ded
--- /dev/null
+++ b/erpnext/templates/pages/wishlist.html
@@ -0,0 +1,28 @@
+{% extends "templates/web.html" %}
+
+{% block title %} {{ _("Wishlist") }} {% endblock %}
+
+{% block header %}<h3 class="shopping-cart-header mt-2 mb-6">{{ _("Wishlist") }}</h1>{% endblock %}
+
+{% block page_content %}
+{% if items %}
+	<div class="row">
+		<div class="col-md-12 item-card-group-section">
+			<div class="row products-list">
+					{% from "erpnext/templates/includes/macros.html" import wishlist_card %}
+					{% for item in items %}
+						{{ wishlist_card(item, settings) }}
+					{% endfor %}
+			</div>
+		</div>
+	</div>
+{% else %}
+	<div class="cart-empty frappe-card">
+		<div class="cart-empty-state">
+			<img src="/assets/erpnext/images/ui-states/cart-empty-state.png" alt="Empty Cart">
+		</div>
+		<div class="cart-empty-message mt-4">{{ _('Wishlist is empty!') }}</p>
+	</div>
+{% endif %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/templates/pages/wishlist.py b/erpnext/templates/pages/wishlist.py
new file mode 100644
index 0000000..72ee34e
--- /dev/null
+++ b/erpnext/templates/pages/wishlist.py
@@ -0,0 +1,71 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+import frappe
+
+from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
+	get_shopping_cart_settings,
+)
+from erpnext.e_commerce.shopping_cart.cart import _set_price_list
+from erpnext.utilities.product import get_price
+
+
+def get_context(context):
+	is_guest = frappe.session.user == "Guest"
+
+	settings = get_shopping_cart_settings()
+	items = get_wishlist_items() if not is_guest else []
+	selling_price_list = _set_price_list(settings) if not is_guest else None
+
+	items = set_stock_price_details(items, settings, selling_price_list)
+
+	context.body_class = "product-page"
+	context.items = items
+	context.settings = settings
+	context.no_cache = 1
+
+def get_stock_availability(item_code, warehouse):
+	stock_qty = frappe.utils.flt(
+		frappe.db.get_value("Bin",
+			{
+				"item_code": item_code,
+				"warehouse": warehouse
+			},
+			"actual_qty")
+	)
+	return bool(stock_qty)
+
+def get_wishlist_items():
+	if not frappe.db.exists("Wishlist", frappe.session.user):
+		return []
+
+	return frappe.db.get_all(
+		"Wishlist Item",
+		filters={
+			"parent": frappe.session.user
+		},
+		fields=[
+			"web_item_name", "item_code", "item_name",
+			"website_item", "warehouse",
+			"image", "item_group", "route"
+		])
+
+def set_stock_price_details(items, settings, selling_price_list):
+	for item in items:
+		if settings.show_stock_availability:
+			item.available = get_stock_availability(item.item_code, item.get("warehouse"))
+
+		price_details = get_price(
+			item.item_code,
+			selling_price_list,
+			settings.default_customer_group,
+			settings.company
+		)
+
+		if price_details:
+			item.formatted_price = price_details.get('formatted_price')
+			item.formatted_mrp = price_details.get('formatted_mrp')
+			if item.formatted_mrp:
+				item.discount = price_details.get('formatted_discount_percent') or \
+					price_details.get('formatted_discount_rate')
+
+	return items
\ No newline at end of file
diff --git a/erpnext/tests/test_point_of_sale.py b/erpnext/tests/test_point_of_sale.py
new file mode 100644
index 0000000..38f2c16
--- /dev/null
+++ b/erpnext/tests/test_point_of_sale.py
@@ -0,0 +1,63 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+import unittest
+
+import frappe
+
+from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
+from erpnext.selling.page.point_of_sale.point_of_sale import get_items
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+
+
+class TestPointOfSale(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls) -> None:
+		frappe.db.savepoint('before_test_point_of_sale')
+
+	@classmethod
+	def tearDownClass(cls) -> None:
+		frappe.db.rollback(save_point='before_test_point_of_sale')
+
+	def test_item_search(self):
+		"""
+		Test Stock and Service Item Search.
+		"""
+
+		pos_profile = make_pos_profile(name="Test POS Profile for Search")
+		item1 = make_item("Test Search Stock Item", {"is_stock_item": 1})
+		make_stock_entry(
+			item_code="Test Search Stock Item",
+			qty=10,
+			to_warehouse="_Test Warehouse - _TC",
+			rate=500,
+		)
+
+		result = get_items(
+			start=0,
+			page_length=20,
+			price_list=None,
+			item_group=item1.item_group,
+			pos_profile=pos_profile.name,
+			search_term="Test Search Stock Item",
+		)
+		filtered_items = result.get("items")
+
+		self.assertEqual(len(filtered_items), 1)
+		self.assertEqual(filtered_items[0]["item_code"], item1.item_code)
+		self.assertEqual(filtered_items[0]["actual_qty"], 10)
+
+		item2 = make_item("Test Search Service Item", {"is_stock_item": 0})
+		result = get_items(
+			start=0,
+			page_length=20,
+			price_list=None,
+			item_group=item2.item_group,
+			pos_profile=pos_profile.name,
+			search_term="Test Search Service Item",
+		)
+		filtered_items = result.get("items")
+
+		self.assertEqual(len(filtered_items), 1)
+		self.assertEqual(filtered_items[0]["item_code"], item2.item_code)
diff --git a/erpnext/tests/test_zform_loads.py b/erpnext/tests/test_zform_loads.py
new file mode 100644
index 0000000..b6fb636
--- /dev/null
+++ b/erpnext/tests/test_zform_loads.py
@@ -0,0 +1,30 @@
+""" dumb test to check all function calls on known form loads """
+
+import unittest
+
+import frappe
+from frappe.desk.form.load import getdoc
+
+
+class TestFormLoads(unittest.TestCase):
+
+	def test_load(self):
+		erpnext_modules = frappe.get_all("Module Def", filters={"app_name": "erpnext"}, pluck="name")
+		doctypes = frappe.get_all("DocType", {"istable": 0, "issingle": 0, "is_virtual": 0, "module": ("in", erpnext_modules)}, pluck="name")
+
+		for doctype in doctypes:
+			last_doc = frappe.db.get_value(doctype, {}, "name", order_by="modified desc")
+			if not last_doc:
+				continue
+			with self.subTest(msg=f"Loading {doctype} - {last_doc}", doctype=doctype, last_doc=last_doc):
+				try:
+					# reset previous response
+					frappe.response = frappe._dict({"docs":[]})
+					frappe.response.docinfo = None
+
+					getdoc(doctype, last_doc)
+				except Exception as e:
+					self.fail(f"Failed to load {doctype} - {last_doc}: {e}")
+
+				self.assertTrue(frappe.response.docs, msg=f"expected document in reponse, found: {frappe.response.docs}")
+				self.assertTrue(frappe.response.docinfo, msg=f"expected docinfo in reponse, found: {frappe.response.docinfo}")
diff --git a/erpnext/tests/ui_test_bulk_transaction_processing.py b/erpnext/tests/ui_test_bulk_transaction_processing.py
new file mode 100644
index 0000000..d78689e
--- /dev/null
+++ b/erpnext/tests/ui_test_bulk_transaction_processing.py
@@ -0,0 +1,21 @@
+import frappe
+
+from erpnext.bulk_transaction.doctype.bulk_transaction_logger.test_bulk_transaction_logger import (
+	create_company,
+	create_customer,
+	create_item,
+	create_so,
+)
+
+
+@frappe.whitelist()
+def create_records():
+	create_company()
+	create_customer()
+	create_item()
+
+	gd = frappe.get_doc("Global Defaults")
+	gd.set("default_company", "Test Bulk")
+	gd.save()
+	frappe.clear_cache()
+	create_so()
\ No newline at end of file
diff --git a/erpnext/tests/utils.py b/erpnext/tests/utils.py
index bc9f04e..d795253 100644
--- a/erpnext/tests/utils.py
+++ b/erpnext/tests/utils.py
@@ -1,10 +1,6 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-import copy
-import signal
-import unittest
-from contextlib import contextmanager
 from typing import Any, Dict, NewType, Optional
 
 import frappe
@@ -13,22 +9,6 @@
 ReportFilters = Dict[str, Any]
 ReportName = NewType("ReportName", str)
 
-
-class ERPNextTestCase(unittest.TestCase):
-	"""A sane default test class for ERPNext tests."""
-
-
-	@classmethod
-	def setUpClass(cls) -> None:
-		frappe.db.commit()
-		return super().setUpClass()
-
-	@classmethod
-	def tearDownClass(cls) -> None:
-		frappe.db.rollback()
-		return super().tearDownClass()
-
-
 def create_test_contact_and_address():
 	frappe.db.sql('delete from tabContact')
 	frappe.db.sql('delete from `tabContact Email`')
@@ -66,40 +46,19 @@
 	contact.add_phone("+91 0000000000", is_primary_phone=True)
 	contact.insert()
 
-
-@contextmanager
-def change_settings(doctype, settings_dict):
-	""" A context manager to ensure that settings are changed before running
-	function and restored after running it regardless of exceptions occured.
-	This is useful in tests where you want to make changes in a function but
-	don't retain those changes.
-	import and use as decorator to cover full function or using `with` statement.
-
-	example:
-	@change_settings("Stock Settings", {"item_naming_by": "Naming Series"})
-	def test_case(self):
-		...
-	"""
-
-	try:
-		settings = frappe.get_doc(doctype)
-		# remember setting
-		previous_settings = copy.deepcopy(settings_dict)
-		for key in previous_settings:
-			previous_settings[key] = getattr(settings, key)
-
-		# change setting
-		for key, value in settings_dict.items():
-			setattr(settings, key, value)
-		settings.save()
-		yield # yield control to calling function
-
-	finally:
-		# restore settings
-		settings = frappe.get_doc(doctype)
-		for key, value in previous_settings.items():
-			setattr(settings, key, value)
-		settings.save()
+	contact_two = frappe.get_doc({
+		"doctype": 'Contact',
+		"first_name": "_Test Contact 2 for _Test Customer",
+		"links": [
+			{
+				"link_doctype": "Customer",
+				"link_name": "_Test Customer"
+			}
+		]
+	})
+	contact_two.add_email("test_contact_two_customer@example.com", is_primary=True)
+	contact_two.add_phone("+92 0000000000", is_primary_phone=True)
+	contact_two.insert()
 
 
 def execute_script_report(
@@ -141,24 +100,3 @@
 		except Exception:
 			print(f"Report failed to execute with filters: {test_filter}")
 			raise
-
-
-
-def timeout(seconds=30, error_message="Test timed out."):
-	""" Timeout decorator to ensure a test doesn't run for too long.
-
-		adapted from https://stackoverflow.com/a/2282656"""
-	def decorator(func):
-		def _handle_timeout(signum, frame):
-			raise Exception(error_message)
-
-		def wrapper(*args, **kwargs):
-			signal.signal(signal.SIGALRM, _handle_timeout)
-			signal.alarm(seconds)
-			try:
-				result = func(*args, **kwargs)
-			finally:
-				signal.alarm(0)
-			return result
-		return wrapper
-	return decorator
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 0aca1a0..b882b9d 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -783,6 +783,7 @@
 Default BOM ({0}) must be active for this item or its template,Standardstückliste ({0}) muss für diesen Artikel oder dessen Vorlage aktiv sein,
 Default BOM for {0} not found,Standardstückliste für {0} nicht gefunden,
 Default BOM not found for Item {0} and Project {1},Standard-Stückliste nicht gefunden für Position {0} und Projekt {1},
+Default In-Transit Warehouse, Standardlager für Waren im Transit,
 Default Letter Head,Standardbriefkopf,
 Default Tax Template,Standardsteuervorlage,
 Default Unit of Measure for Item {0} cannot be changed directly because you have already made some transaction(s) with another UOM. You will need to create a new Item to use a different Default UOM.,"Die Standard-Maßeinheit für Artikel {0} kann nicht direkt geändert werden, weil Sie bereits einige Transaktionen mit einer anderen Maßeinheit durchgeführt haben. Sie müssen einen neuen Artikel erstellen, um eine andere Standard-Maßeinheit verwenden zukönnen.",
@@ -912,6 +913,7 @@
 Email Address,E-Mail-Adresse,
 "Email Address must be unique, already exists for {0}","E-Mail-Adresse muss eindeutig sein, diese wird bereits für {0} verwendet",
 Email Digest: ,E-Mail-Bericht:,
+Email Digest Recipient,E-Mail-Berichtsempfänger,
 Email Reminders will be sent to all parties with email contacts,E-Mail-Erinnerungen werden an alle Parteien mit E-Mail-Kontakten gesendet,
 Email Sent,E-Mail wurde versandt,
 Email Template,E-Mail-Vorlage,
@@ -1054,6 +1056,7 @@
 Fiscal Year {0} not found,Das Geschäftsjahr {0} nicht gefunden,
 Fixed Asset,Anlagevermögen,
 Fixed Asset Item must be a non-stock item.,Posten des Anlagevermögens muss ein Nichtlagerposition sein.,
+Fixed Asset Defaults, Standards für Anlagevermögen,
 Fixed Assets,Anlagevermögen,
 Following Material Requests have been raised automatically based on Item's re-order level,Folgende Materialanfragen wurden automatisch auf der Grundlage der Nachbestellmenge des Artikels generiert,
 Following accounts might be selected in GST Settings:,In den GST-Einstellungen können folgende Konten ausgewählt werden:,
@@ -1594,6 +1597,7 @@
 Middle Income,Mittleres Einkommen,
 Middle Name,Zweiter Vorname,
 Middle Name (Optional),Weiterer Vorname (optional),
+Milestonde,Meilenstein,
 Min Amt can not be greater than Max Amt,Min. Amt kann nicht größer als Max. Amt sein,
 Min Qty can not be greater than Max Qty,Mindestmenge kann nicht größer als Maximalmenge sein,
 Minimum Lead Age (Days),Mindest Lead-Alter (in Tagen),
@@ -2352,6 +2356,7 @@
 Reopen,Wieder öffnen,
 Reorder Level,Meldebestand,
 Reorder Qty,Nachbestellmenge,
+Repair and Maintenance Account, Konto für Reparatur/Instandhaltung von Anlagen und Maschinen,
 Repeat Customer Revenue,Umsatz Bestandskunden,
 Repeat Customers,Bestandskunden,
 Replace BOM and update latest price in all BOMs,Ersetzen Sie die Stückliste und aktualisieren Sie den aktuellen Preis in allen Stücklisten,
@@ -2941,7 +2946,7 @@
 Temporary Opening,Temporäre Eröffnungskonten,
 Terms and Conditions,Allgemeine Geschäftsbedingungen,
 Terms and Conditions Template,Vorlage für Allgemeine Geschäftsbedingungen,
-Territory,Region,
+Territory,Gebiet,
 Test,Test,
 Thank you,Danke,
 Thank you for your business!,Vielen Dank für Ihr Unternehmen!,
@@ -3726,7 +3731,7 @@
 Edit Details,Details bearbeiten,
 Edit Profile,Profil bearbeiten,
 Either GST Transporter ID or Vehicle No is required if Mode of Transport is Road,Bei Straßentransport ist entweder die GST-Transporter-ID oder die Fahrzeug-Nr. Erforderlich,
-Email,Email,
+Email,E-Mail,
 Email Campaigns,E-Mail-Kampagnen,
 Employee ID is linked with another instructor,Die Mitarbeiter-ID ist mit einem anderen Ausbilder verknüpft,
 Employee Tax and Benefits,Mitarbeitersteuern und -leistungen,
@@ -3796,7 +3801,7 @@
 Invalid Barcode. There is no Item attached to this barcode.,Ungültiger Barcode. Es ist kein Artikel an diesen Barcode angehängt.,
 Invalid credentials,Ungültige Anmeldeinformationen,
 Invite as User,Als Benutzer einladen,
-Issue Priority.,Ausgabepriorität.,
+Issue Priority.,Anfragepriorität.,
 Issue Type.,Problemtyp.,
 "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.","Es scheint, dass ein Problem mit der Stripe-Konfiguration des Servers vorliegt. Im Falle eines Fehlers wird der Betrag Ihrem Konto gutgeschrieben.",
 Item Reported,Gegenstand gemeldet,
@@ -4857,6 +4862,7 @@
 Allocated,Zugewiesen,
 Payment Gateway Account,Payment Gateway Konto,
 Payment Account,Zahlungskonto,
+Default Payment Discount Account, Standard Rabattkonto für Zahlungen,
 Default Payment Request Message,Standard Payment Request Message,
 PMO-,PMO-,
 Payment Order Type,Zahlungsauftragsart,
@@ -6481,7 +6487,7 @@
 Send Emails At,Die E-Mails senden um,
 Reminder,Erinnerung,
 Daily Work Summary Group User,Tägliche Arbeit Zusammenfassung Gruppenbenutzer,
-email,Email,
+email,E-Mail,
 Parent Department,Elternabteilung,
 Leave Block List,Urlaubssperrenliste,
 Days for which Holidays are blocked for this department.,"Tage, an denen eine Urlaubssperre für diese Abteilung gilt.",
@@ -7789,7 +7795,8 @@
 Discount Allowed Account,Rabatt erlaubtes Konto,
 Discount Received Account,Discount Received Account,
 Exchange Gain / Loss Account,Konto für Wechselkursdifferenzen,
-Unrealized Exchange Gain/Loss Account,Konto für unrealisierte Wechselkurs-Gewinne / -Verluste,
+Unrealized Exchange Gain/Loss Account,Konto für nicht realisierte Wechselkurs-Gewinne/ -Verluste,
+Unrealized Profit / Loss Account, Konto für nicht realisierten Gewinn/Verlust,
 Allow Account Creation Against Child Company,Kontoerstellung für untergeordnete Unternehmen zulassen,
 Default Payable Account,Standard-Verbindlichkeitenkonto,
 Default Employee Advance Account,Standardkonto für Vorschüsse an Arbeitnehmer,
diff --git a/erpnext/utilities/bulk_transaction.py b/erpnext/utilities/bulk_transaction.py
new file mode 100644
index 0000000..64e2ff4
--- /dev/null
+++ b/erpnext/utilities/bulk_transaction.py
@@ -0,0 +1,201 @@
+import json
+from datetime import date, datetime
+
+import frappe
+from frappe import _
+
+
+@frappe.whitelist()
+def transaction_processing(data, from_doctype, to_doctype):
+	if isinstance(data, str):
+		deserialized_data = json.loads(data)
+
+	else:
+		deserialized_data = data
+
+	length_of_data = len(deserialized_data)
+
+	if length_of_data > 10:
+		frappe.msgprint(
+			_("Started a background job to create {1} {0}").format(to_doctype, length_of_data)
+		)
+		frappe.enqueue(
+			job,
+			deserialized_data=deserialized_data,
+			from_doctype=from_doctype,
+			to_doctype=to_doctype,
+		)
+	else:
+		job(deserialized_data, from_doctype, to_doctype)
+
+
+def job(deserialized_data, from_doctype, to_doctype):
+	failed_history = []
+	i = 0
+	for d in deserialized_data:
+		failed = []
+
+		try:
+			i += 1
+			doc_name = d.get("name")
+			frappe.db.savepoint("before_creation_state")
+			task(doc_name, from_doctype, to_doctype)
+
+		except Exception as e:
+			frappe.db.rollback(save_point="before_creation_state")
+			failed_history.append(e)
+			failed.append(e)
+			update_logger(doc_name, e, from_doctype, to_doctype, status="Failed", log_date=str(date.today()))
+		if not failed:
+			update_logger(doc_name, None, from_doctype, to_doctype, status="Success", log_date=str(date.today()))
+
+	show_job_status(failed_history, deserialized_data, to_doctype)
+
+
+def task(doc_name, from_doctype, to_doctype):
+	from erpnext.accounts.doctype.payment_entry import payment_entry
+	from erpnext.accounts.doctype.purchase_invoice import purchase_invoice
+	from erpnext.accounts.doctype.sales_invoice import sales_invoice
+	from erpnext.buying.doctype.purchase_order import purchase_order
+	from erpnext.buying.doctype.supplier_quotation import supplier_quotation
+	from erpnext.selling.doctype.quotation import quotation
+	from erpnext.selling.doctype.sales_order import sales_order
+	from erpnext.stock.doctype.delivery_note import delivery_note
+	from erpnext.stock.doctype.purchase_receipt import purchase_receipt
+
+	mapper = {
+		"Sales Order": {
+			"Sales Invoice": sales_order.make_sales_invoice,
+			"Delivery Note": sales_order.make_delivery_note,
+			"Advance Payment": payment_entry.get_payment_entry,
+		},
+		"Sales Invoice": {
+			"Delivery Note": sales_invoice.make_delivery_note,
+			"Payment": payment_entry.get_payment_entry,
+		},
+		"Delivery Note": {
+			"Sales Invoice": delivery_note.make_sales_invoice,
+			"Packing Slip": delivery_note.make_packing_slip,
+		},
+		"Quotation": {
+			"Sales Order": quotation.make_sales_order,
+			"Sales Invoice": quotation.make_sales_invoice,
+		},
+		"Supplier Quotation": {
+			"Purchase Order": supplier_quotation.make_purchase_order,
+			"Purchase Invoice": supplier_quotation.make_purchase_invoice,
+			"Advance Payment": payment_entry.get_payment_entry,
+		},
+		"Purchase Order": {
+			"Purchase Invoice": purchase_order.make_purchase_invoice,
+			"Purchase Receipt": purchase_order.make_purchase_receipt,
+		},
+		"Purhcase Invoice": {
+			"Purchase Receipt": purchase_invoice.make_purchase_receipt,
+			"Payment": payment_entry.get_payment_entry,
+		},
+		"Purchase Receipt": {"Purchase Invoice": purchase_receipt.make_purchase_invoice},
+	}
+	if to_doctype in ['Advance Payment', 'Payment']:
+		obj = mapper[from_doctype][to_doctype](from_doctype, doc_name)
+	else:
+		obj = mapper[from_doctype][to_doctype](doc_name)
+
+	obj.flags.ignore_validate = True
+	obj.insert(ignore_mandatory=True)
+
+
+def check_logger_doc_exists(log_date):
+	return frappe.db.exists("Bulk Transaction Log", log_date)
+
+
+def get_logger_doc(log_date):
+	return frappe.get_doc("Bulk Transaction Log", log_date)
+
+
+def create_logger_doc():
+	log_doc = frappe.new_doc("Bulk Transaction Log")
+	log_doc.set_new_name(set_name=str(date.today()))
+	log_doc.log_date = date.today()
+
+	return log_doc
+
+
+def append_data_to_logger(log_doc, doc_name, error, from_doctype, to_doctype, status, restarted):
+	row = log_doc.append("logger_data", {})
+	row.transaction_name = doc_name
+	row.date = date.today()
+	now = datetime.now()
+	row.time = now.strftime("%H:%M:%S")
+	row.transaction_status = status
+	row.error_description = str(error)
+	row.from_doctype = from_doctype
+	row.to_doctype = to_doctype
+	row.retried = restarted
+
+
+def update_logger(doc_name, e, from_doctype, to_doctype, status, log_date=None, restarted=0):
+	if not check_logger_doc_exists(log_date):
+		log_doc = create_logger_doc()
+		append_data_to_logger(log_doc, doc_name, e, from_doctype, to_doctype, status, restarted)
+		log_doc.insert()
+	else:
+		log_doc = get_logger_doc(log_date)
+		if record_exists(log_doc, doc_name, status):
+			append_data_to_logger(
+				log_doc, doc_name, e, from_doctype, to_doctype, status, restarted
+			)
+			log_doc.save()
+
+
+def show_job_status(failed_history, deserialized_data, to_doctype):
+	if not failed_history:
+		frappe.msgprint(
+			_("Creation of {0} successful").format(to_doctype),
+			title="Successful",
+			indicator="green",
+		)
+
+	if len(failed_history) != 0 and len(failed_history) < len(deserialized_data):
+		frappe.msgprint(
+			_("""Creation of {0} partially successful.
+				Check <b><a href="/app/bulk-transaction-log">Bulk Transaction Log</a></b>""").format(
+				to_doctype
+			),
+			title="Partially successful",
+			indicator="orange",
+		)
+
+	if len(failed_history) == len(deserialized_data):
+		frappe.msgprint(
+			_("""Creation of {0} failed.
+				Check <b><a href="/app/bulk-transaction-log">Bulk Transaction Log</a></b>""").format(
+				to_doctype
+			),
+			title="Failed",
+			indicator="red",
+		)
+
+
+def record_exists(log_doc, doc_name, status):
+
+	record = mark_retrired_transaction(log_doc, doc_name)
+
+	if record and status == "Failed":
+		return False
+	elif record and status == "Success":
+		return True
+	else:
+		return True
+
+
+def mark_retrired_transaction(log_doc, doc_name):
+	record = 0
+	for d in log_doc.get("logger_data"):
+		if d.transaction_name == doc_name and d.transaction_status == "Failed":
+			d.retried = 1
+			record = record + 1
+
+	log_doc.save()
+
+	return record
\ No newline at end of file
diff --git a/erpnext/utilities/doctype/rename_tool/rename_tool.js b/erpnext/utilities/doctype/rename_tool/rename_tool.js
index 7823055..5553e44 100644
--- a/erpnext/utilities/doctype/rename_tool/rename_tool.js
+++ b/erpnext/utilities/doctype/rename_tool/rename_tool.js
@@ -13,6 +13,12 @@
 	},
 	refresh: function(frm) {
 		frm.disable_save();
+
+		frm.get_field("file_to_rename").df.options = {
+			restrictions: {
+				allowed_file_types: [".csv"],
+			},
+		};
 		if (!frm.doc.file_to_rename) {
 			frm.get_field("rename_log").$wrapper.html("");
 		}
diff --git a/erpnext/utilities/product.py b/erpnext/utilities/product.py
index e9e4baa..0a45002 100644
--- a/erpnext/utilities/product.py
+++ b/erpnext/utilities/product.py
@@ -1,7 +1,6 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
 from frappe.utils import cint, flt, fmt_money, getdate, nowdate
 
@@ -9,15 +8,15 @@
 from erpnext.stock.doctype.batch.batch import get_batch_qty
 
 
-def get_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
+def get_web_item_qty_in_stock(item_code, item_warehouse_field, warehouse=None):
 	in_stock, stock_qty = 0, ''
 	template_item_code, is_stock_item = frappe.db.get_value("Item", item_code, ["variant_of", "is_stock_item"])
 
 	if not warehouse:
-		warehouse = frappe.db.get_value("Item", item_code, item_warehouse_field)
+		warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field)
 
 	if not warehouse and template_item_code and template_item_code != item_code:
-		warehouse = frappe.db.get_value("Item", template_item_code, item_warehouse_field)
+		warehouse = frappe.db.get_value("Website Item", {"item_code": template_item_code}, item_warehouse_field)
 
 	if warehouse:
 		stock_qty = frappe.db.sql("""
@@ -69,6 +68,8 @@
 	return qty
 
 def get_price(item_code, price_list, customer_group, company, qty=1):
+	from erpnext.e_commerce.shopping_cart.cart import get_party
+
 	template_item_code = frappe.db.get_value("Item", item_code, "variant_of")
 
 	if price_list:
@@ -80,7 +81,8 @@
 				filters={"price_list": price_list, "item_code": template_item_code})
 
 		if price:
-			pricing_rule = get_pricing_rule_for_item(frappe._dict({
+			party = get_party()
+			pricing_rule_dict = frappe._dict({
 				"item_code": item_code,
 				"qty": qty,
 				"stock_qty": qty,
@@ -91,18 +93,33 @@
 				"conversion_rate": 1,
 				"for_shopping_cart": True,
 				"currency": frappe.db.get_value("Price List", price_list, "currency")
-			}))
+			})
+
+			if party and party.doctype == "Customer":
+				pricing_rule_dict.update({"customer": party.name})
+
+			pricing_rule = get_pricing_rule_for_item(pricing_rule_dict)
+			price_obj = price[0]
 
 			if pricing_rule:
+				# price without any rules applied
+				mrp = price_obj.price_list_rate or 0
+
 				if pricing_rule.pricing_rule_for == "Discount Percentage":
-					price[0].price_list_rate = flt(price[0].price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0)))
+					price_obj.discount_percent = pricing_rule.discount_percentage
+					price_obj.formatted_discount_percent = str(flt(pricing_rule.discount_percentage, 0)) + "%"
+					price_obj.price_list_rate = flt(price_obj.price_list_rate * (1.0 - (flt(pricing_rule.discount_percentage) / 100.0)))
 
 				if pricing_rule.pricing_rule_for == "Rate":
-					price[0].price_list_rate = pricing_rule.price_list_rate
+					rate_discount = flt(mrp) - flt(pricing_rule.price_list_rate)
+					if rate_discount > 0:
+						price_obj.formatted_discount_rate = fmt_money(rate_discount, currency=price_obj["currency"])
+					price_obj.price_list_rate = pricing_rule.price_list_rate or 0
 
-			price_obj = price[0]
 			if price_obj:
 				price_obj["formatted_price"] = fmt_money(price_obj["price_list_rate"], currency=price_obj["currency"])
+				if mrp != price_obj["price_list_rate"]:
+					price_obj["formatted_mrp"] = fmt_money(mrp, currency=price_obj["currency"])
 
 				price_obj["currency_symbol"] = not cint(frappe.db.get_default("hide_currency_symbol")) \
 					and (frappe.db.get_value("Currency", price_obj.currency, "symbol", cache=True) or price_obj.currency) \
@@ -123,15 +140,15 @@
 					price_obj["currency"] = ""
 
 				if not price_obj["formatted_price"]:
-					price_obj["formatted_price"] = ""
+					price_obj["formatted_price"], price_obj["formatted_mrp"] = "", ""
 
 			return price_obj
 
 def get_non_stock_item_status(item_code, item_warehouse_field):
-	#if item belongs to product bundle, check if bundle items are in stock
+	# if item is a product bundle, check if its bundle items are in stock
 	if frappe.db.exists("Product Bundle", item_code):
 		items = frappe.get_doc("Product Bundle", item_code).get_all_children()
-		bundle_warehouse = frappe.db.get_value('Item', item_code, item_warehouse_field)
-		return all(get_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items)
+		bundle_warehouse = frappe.db.get_value("Website Item", {"item_code": item_code}, item_warehouse_field)
+		return all(get_web_item_qty_in_stock(d.item_code, item_warehouse_field, bundle_warehouse).in_stock for d in items)
 	else:
 		return 1
diff --git a/erpnext/www/all-products/index.html b/erpnext/www/all-products/index.html
index a7838ee..3d5517c 100644
--- a/erpnext/www/all-products/index.html
+++ b/erpnext/www/all-products/index.html
@@ -1,120 +1,34 @@
+{% from "erpnext/templates/includes/macros.html" import attribute_filter_section, field_filter_section, discount_range_filters %}
 {% extends "templates/web.html" %}
-{% block title %}{{ _('Products') }}{% endblock %}
+
+{% block title %}{{ _('All Products') }}{% endblock %}
 {% block header %}
-<div class="mb-6">{{ _('Products') }}</div>
+<div class="mb-6">{{ _('All Products') }}</div>
 {% endblock header %}
 
 {% block page_content %}
-<div class="row" style="display: none;">
-	<div class="col-8">
-		<div class="input-group input-group-sm mb-3">
-			<input type="search" class="form-control" placeholder="{{_('Search')}}"
-				aria-label="{{_('Product Search')}}" aria-describedby="product-search"
-				value="{{ frappe.sanitize_html(frappe.form_dict.search) or '' }}"
-			>
-		</div>
-	</div>
-
-	<div class="col-4 pl-0">
-		<button class="btn btn-light btn-sm btn-block d-md-none"
-			type="button"
-			data-toggle="collapse"
-			data-target="#product-filters"
-			aria-expanded="false"
-			aria-controls="product-filters"
-			style="white-space: nowrap;"
-		>
-			{{ _('Toggle Filters') }}
-		</button>
-	</div>
-</div>
-
 <div class="row">
-	<div class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
-		<div class="row products-list">
-			{% if items %}
-				{% for item in items %}
-					{% include "erpnext/www/all-products/item_row.html" %}
-				{% endfor %}
-			{% else %}
-				{% include "erpnext/www/all-products/not_found.html" %}
-			{% endif %}
-		</div>
+	<!-- Items section -->
+	<div id="product-listing" class="col-12 order-2 col-md-9 order-md-2 item-card-group-section">
+		<!-- Rendered via JS -->
 	</div>
+
+	<!-- Filters Section -->
 	<div class="col-12 order-1 col-md-3 order-md-1">
-
-		{% if frappe.form_dict.start or frappe.form_dict.field_filters or frappe.form_dict.attribute_filters or frappe.form_dict.search %}
-
-
-		{% endif  %}
-
 		<div class="collapse d-md-block mr-4 filters-section" id="product-filters">
 			<div class="d-flex justify-content-between align-items-center mb-5 title-section">
 				<div class="mb-4 filters-title" > {{ _('Filters') }} </div>
 				<a class="mb-4 clear-filters" href="/all-products">{{ _('Clear All') }}</a>
 			</div>
-			{% for field_filter in field_filters %}
-				{%- set item_field =  field_filter[0] %}
-				{%- set values =  field_filter[1] %}
-				<div class="mb-4 filter-block pb-5">
-					<div class="filter-label mb-3">{{ item_field.label }}</div>
+			<!-- field filters -->
+			{% if field_filters %}
+				{{ field_filter_section(field_filters) }}
+			{% endif %}
 
-					{% if values | len > 20 %}
-					<!-- show inline filter if values more than 20 -->
-					<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
-					{% endif %}
-
-					{% if values %}
-					<div class="filter-options">
-						{% for value in values %}
-						<div class="checkbox" data-value="{{ value }}">
-							<label for="{{value}}">
-								<input type="checkbox"
-									class="product-filter field-filter"
-									id="{{value}}"
-									data-filter-name="{{ item_field.fieldname }}"
-									data-filter-value="{{ value }}"
-								>
-								<span class="label-area">{{ value }}</span>
-							</label>
-						</div>
-						{% endfor %}
-					</div>
-					{% else %}
-					<i class="text-muted">{{ _('No values') }}</i>
-					{% endif %}
-				</div>
-			{% endfor %}
-
-			{% for attribute in attribute_filters %}
-				<div class="mb-4 filter-block pb-5">
-					<div class="filter-label mb-3">{{ attribute.name}}</div>
-					{% if values | len > 20 %}
-					<!-- show inline filter if values more than 20 -->
-					<input type="text" class="form-control form-control-sm mb-2 product-filter-filter"/>
-					{% endif %}
-
-					{% if attribute.item_attribute_values %}
-					<div class="filter-options">
-						{% for attr_value in attribute.item_attribute_values %}
-						<div class="checkbox">
-							<label>
-								<input type="checkbox"
-									class="product-filter attribute-filter"
-									id="{{attr_value}}"
-									data-attribute-name="{{ attribute.name }}"
-									data-attribute-value="{{ attr_value }}"
-									{% if attr_value.checked %} checked {% endif %}>
-									<span class="label-area">{{ attr_value }}</span>
-							</label>
-						</div>
-						{% endfor %}
-					</div>
-					{% else %}
-					<i class="text-muted">{{ _('No values') }}</i>
-					{% endif %}
-				</div>
-			{% endfor %}
+			<!-- attribute filters -->
+			{% if attribute_filters %}
+				{{ attribute_filter_section(attribute_filters) }}
+			{% endif %}
 		</div>
 
 		<script>
@@ -137,18 +51,6 @@
 		</script>
 	</div>
 </div>
-<div class="row product-paging-area mt-5">
-	<div class="col-3">
-	</div>
-	<div class="col-9 text-right">
-		{% if frappe.form_dict.start|int > 0 %}
-		<button class="btn btn-default btn-prev" data-start="{{ frappe.form_dict.start|int - page_length }}">{{ _("Prev") }}</button>
-		{% endif %}
-		{% if items|length >= page_length %}
-		<button class="btn btn-default btn-next" data-start="{{ frappe.form_dict.start|int + page_length }}">{{ _("Next") }}</button>
-		{% endif %}
-	</div>
-</div>
 
 <script>
 	frappe.ready(() => {
diff --git a/erpnext/www/all-products/index.js b/erpnext/www/all-products/index.js
index 1c641b5..98a8441 100644
--- a/erpnext/www/all-products/index.js
+++ b/erpnext/www/all-products/index.js
@@ -1,165 +1,27 @@
 $(() => {
 	class ProductListing {
 		constructor() {
-			this.bind_filters();
-			this.bind_search();
-			this.restore_filters_state();
-		}
+			let me = this;
+			let is_item_group_page = $(".item-group-content").data("item-group");
+			this.item_group = is_item_group_page || null;
 
-		bind_filters() {
-			this.field_filters = {};
-			this.attribute_filters = {};
+			let view_type = localStorage.getItem("product_view") || "List View";
 
-			$('.product-filter').on('change', frappe.utils.debounce((e) => {
-				const $checkbox = $(e.target);
-				const is_checked = $checkbox.is(':checked');
-
-				if ($checkbox.is('.attribute-filter')) {
-					const {
-						attributeName: attribute_name,
-						attributeValue: attribute_value
-					} = $checkbox.data();
-
-					if (is_checked) {
-						this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
-						this.attribute_filters[attribute_name].push(attribute_value);
-					} else {
-						this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name] || [];
-						this.attribute_filters[attribute_name] = this.attribute_filters[attribute_name].filter(v => v !== attribute_value);
-					}
-
-					if (this.attribute_filters[attribute_name].length === 0) {
-						delete this.attribute_filters[attribute_name];
-					}
-				} else if ($checkbox.is('.field-filter')) {
-					const {
-						filterName: filter_name,
-						filterValue: filter_value
-					} = $checkbox.data();
-
-					if (is_checked) {
-						this.field_filters[filter_name] = this.field_filters[filter_name] || [];
-						this.field_filters[filter_name].push(filter_value);
-					} else {
-						this.field_filters[filter_name] = this.field_filters[filter_name] || [];
-						this.field_filters[filter_name] = this.field_filters[filter_name].filter(v => v !== filter_value);
-					}
-
-					if (this.field_filters[filter_name].length === 0) {
-						delete this.field_filters[filter_name];
-					}
-				}
-
-				const query_string = get_query_string({
-					field_filters: JSON.stringify(if_key_exists(this.field_filters)),
-					attribute_filters: JSON.stringify(if_key_exists(this.attribute_filters)),
-				});
-				window.history.pushState('filters', '', `${location.pathname}?` + query_string);
-
-				$('.page_content input').prop('disabled', true);
-				this.get_items_with_filters()
-					.then(html => {
-						$('.products-list').html(html);
-					})
-					.then(data => {
-						$('.page_content input').prop('disabled', false);
-						return data;
-					})
-					.catch(() => {
-						$('.page_content input').prop('disabled', false);
-					});
-			}, 1000));
-		}
-
-		make_filters() {
-
-		}
-
-		bind_search() {
-			$('input[type=search]').on('keydown', (e) => {
-				if (e.keyCode === 13) {
-					// Enter
-					const value = e.target.value;
-					if (value) {
-						window.location.search = 'search=' + e.target.value;
-					} else {
-						window.location.search = '';
-					}
-				}
+			// Render Product Views, Filters & Search
+			new erpnext.ProductView({
+				view_type: view_type,
+				products_section: $('#product-listing'),
+				item_group: me.item_group
 			});
+
+			this.bind_card_actions();
 		}
 
-		restore_filters_state() {
-			const filters = frappe.utils.get_query_params();
-			let {field_filters, attribute_filters} = filters;
-
-			if (field_filters) {
-				field_filters = JSON.parse(field_filters);
-				for (let fieldname in field_filters) {
-					const values = field_filters[fieldname];
-					const selector = values.map(value => {
-						return `input[data-filter-name="${fieldname}"][data-filter-value="${value}"]`;
-					}).join(',');
-					$(selector).prop('checked', true);
-				}
-				this.field_filters = field_filters;
-			}
-			if (attribute_filters) {
-				attribute_filters = JSON.parse(attribute_filters);
-				for (let attribute in attribute_filters) {
-					const values = attribute_filters[attribute];
-					const selector = values.map(value => {
-						return `input[data-attribute-name="${attribute}"][data-attribute-value="${value}"]`;
-					}).join(',');
-					$(selector).prop('checked', true);
-				}
-				this.attribute_filters = attribute_filters;
-			}
-		}
-
-		get_items_with_filters() {
-			const { attribute_filters, field_filters } = this;
-			const args = {
-				field_filters: if_key_exists(field_filters),
-				attribute_filters: if_key_exists(attribute_filters)
-			};
-
-			const item_group = $(".item-group-content").data('item-group');
-			if (item_group) {
-				Object.assign(field_filters, { item_group });
-			}
-			return new Promise((resolve, reject) => {
-				frappe.call('erpnext.portal.product_configurator.utils.get_products_html_for_website', args)
-					.then(r => {
-						if (r.exc) reject(r.exc);
-						else resolve(r.message);
-					})
-					.fail(reject);
-			});
+		bind_card_actions() {
+			erpnext.e_commerce.shopping_cart.bind_add_to_cart_action();
+			erpnext.e_commerce.wishlist.bind_wishlist_action();
 		}
 	}
 
 	new ProductListing();
-
-	function get_query_string(object) {
-		const url = new URLSearchParams();
-		for (let key in object) {
-			const value = object[key];
-			if (value) {
-				url.append(key, value);
-			}
-		}
-		return url.toString();
-	}
-
-	function if_key_exists(obj) {
-		let exists = false;
-		for (let key in obj) {
-			if (obj.hasOwnProperty(key) && obj[key]) {
-				exists = true;
-				break;
-			}
-		}
-		return exists ? obj : undefined;
-	}
 });
diff --git a/erpnext/www/all-products/index.py b/erpnext/www/all-products/index.py
index df5258b..ffaead6 100644
--- a/erpnext/www/all-products/index.py
+++ b/erpnext/www/all-products/index.py
@@ -1,36 +1,19 @@
 import frappe
+from frappe.utils import cint
 
-from erpnext.portal.product_configurator.utils import get_product_settings
-from erpnext.shopping_cart.filters import ProductFiltersBuilder
-from erpnext.shopping_cart.product_query import ProductQuery
+from erpnext.e_commerce.product_data_engine.filters import ProductFiltersBuilder
 
 sitemap = 1
 
 def get_context(context):
-
-	if frappe.form_dict:
-		search = frappe.form_dict.search
-		field_filters = frappe.parse_json(frappe.form_dict.field_filters)
-		attribute_filters = frappe.parse_json(frappe.form_dict.attribute_filters)
-		start = frappe.parse_json(frappe.form_dict.start)
-	else:
-		search = field_filters = attribute_filters = None
-		start = 0
-
-	engine = ProductQuery()
-	context.items = engine.query(attribute_filters, field_filters, search, start)
-
 	# Add homepage as parent
+	context.body_class = "product-page"
 	context.parents = [{"name": frappe._("Home"), "route":"/"}]
 
-	product_settings = get_product_settings()
 	filter_engine = ProductFiltersBuilder()
-
 	context.field_filters = filter_engine.get_field_filters()
 	context.attribute_filters = filter_engine.get_attribute_filters()
 
-	context.product_settings = product_settings
-	context.body_class = "product-page"
-	context.page_length = product_settings.products_per_page or 20
+	context.page_length = cint(frappe.db.get_single_value('E Commerce Settings', 'products_per_page'))or 20
 
-	context.no_cache = 1
+	context.no_cache = 1
\ No newline at end of file
diff --git a/erpnext/www/all-products/item_row.html b/erpnext/www/all-products/item_row.html
deleted file mode 100644
index a7e994c..0000000
--- a/erpnext/www/all-products/item_row.html
+++ /dev/null
@@ -1,6 +0,0 @@
-{% from "erpnext/templates/includes/macros.html" import item_card, item_card_body %}
-
-{{ item_card(
-	item.item_name or item.name, item.website_image or item.image, item.route, item.website_description or item.description,
-	item.formatted_price, item.item_group
-) }}
diff --git a/erpnext/www/lms/macros/hero.html b/erpnext/www/lms/macros/hero.html
index e72bfc8..95ba8f7 100644
--- a/erpnext/www/lms/macros/hero.html
+++ b/erpnext/www/lms/macros/hero.html
@@ -11,7 +11,7 @@
 			{% if frappe.session.user == 'Guest' %}
 			<a id="signup" class="btn btn-primary btn-lg" href="/login#signup">{{_('Sign Up')}}</a>
 			{% elif not has_access %}
-			<button id="enroll" class="btn btn-primary btn-lg" onclick="enroll()" disabled>{{_('Enroll')}}</button>
+			<button id="enroll" class="btn btn-primary btn-lg" onclick="enroll()">{{_('Enroll')}}</button>
 			{% endif %}
 		</p>
 	</div>
@@ -20,34 +20,35 @@
 <script type="text/javascript">
 	frappe.ready(() => {
 		btn = document.getElementById('enroll');
-		if (btn) btn.disabled = false;
 	})
 
 	function enroll() {
 		let params = frappe.utils.get_query_params()
 
 		let btn = document.getElementById('enroll');
-		btn.disbaled = true;
-		btn.innerText = __('Enrolling...')
 
 		let opts = {
 			method: 'erpnext.education.utils.enroll_in_program',
 			args: {
 				program_name: params.program
-			}
+			},
+			freeze: true,
+			freeze_message: __('Enrolling...')
 		}
 
 		frappe.call(opts).then(res => {
 			let success_dialog = new frappe.ui.Dialog({
 				title: __('Success'),
+				primary_action_label: __('View Program Content'),
+				primary_action: function() {
+					window.location.reload();
+				},
 				secondary_action: function() {
-					window.location.reload()
+					window.location.reload();
 				}
 			})
-			success_dialog.set_message(__('You have successfully enrolled for the program '));
-			success_dialog.$message.show()
 			success_dialog.show();
-			btn.disbaled = false;
+			success_dialog.set_message(__('You have successfully enrolled for the program '));
 		})
 	}
 </script>
diff --git a/erpnext/demo/__init__.py b/erpnext/www/shop-by-category/__init__.py
similarity index 100%
copy from erpnext/demo/__init__.py
copy to erpnext/www/shop-by-category/__init__.py
diff --git a/erpnext/www/shop-by-category/category_card_section.html b/erpnext/www/shop-by-category/category_card_section.html
new file mode 100644
index 0000000..56cb63a
--- /dev/null
+++ b/erpnext/www/shop-by-category/category_card_section.html
@@ -0,0 +1,30 @@
+{%- macro card(title, image, type, url=None, text_primary=False) -%}
+<!-- style defined at shop-by-category index -->
+<div class="card category-card" data-type="{{ type }}" data-name="{{ title }}">
+	{% if image %}
+	<img class="card-img-top" src="{{ image }}" alt="{{ title }}" style="height: 80%;">
+	{% else %}
+	<div class="placeholder-div">
+		<span class="placeholder">
+			{{ frappe.utils.get_abbr(title) }}
+		</span>
+	</div>
+	{% endif %}
+	<div class="card-body text-center text-muted">
+		{{ title or '' }}
+	</div>
+	<a href="{{ url or '#' }}" class="stretched-link"></a>
+</div>
+{%- endmacro -%}
+
+<div class="col-12 item-card-group-section">
+	<div class="row products-list product-category-section">
+		{%- for row in data -%}
+			{%- set title = row.name -%}
+			{%- set image = row.get("image") -%}
+			{%- if title -%}
+				{{ card(title, image, type, row.get("route")) }}
+			{%- endif -%}
+		{%- endfor -%}
+	</div>
+</div>
\ No newline at end of file
diff --git a/erpnext/www/shop-by-category/index.html b/erpnext/www/shop-by-category/index.html
new file mode 100644
index 0000000..04d2d57
--- /dev/null
+++ b/erpnext/www/shop-by-category/index.html
@@ -0,0 +1,48 @@
+{% extends "templates/web.html" %}
+{% block title %}{{ _('Shop by Category') }}{% endblock %}
+
+{% block head_include %}
+<style>
+	.category-slideshow {
+		margin-bottom: 2rem;
+	}
+	.category-card {
+		height: 300px !important;
+		width: 300px !important;
+		margin: 30px !important;
+	}
+</style>
+{% endblock %}
+
+{% block script %}
+<script type="text/javascript" src="/shop-by-category/index.js"></script>
+{% endblock %}
+
+{% block page_content %}
+<div class="shop-by-category-content">
+	<div class="category-slideshow">
+		{% if slideshow %}
+		<!-- slideshow -->
+			{{ web_block(
+				"Hero Slider",
+				values=slideshow,
+				add_container=0,
+				add_top_padding=0,
+				add_bottom_padding=0,
+			) }}
+		{% endif %}
+	</div>
+	<div class="category-tabs">
+		{% if tabs %}
+		<!-- tabs -->
+			{{ web_block(
+				"Section with Tabs",
+				values=tabs,
+				add_container=0,
+				add_top_padding=0,
+				add_bottom_padding=0
+			) }}
+		{% endif %}
+	</div>
+</div>
+{% endblock %}
\ No newline at end of file
diff --git a/erpnext/www/shop-by-category/index.js b/erpnext/www/shop-by-category/index.js
new file mode 100644
index 0000000..1b3116f
--- /dev/null
+++ b/erpnext/www/shop-by-category/index.js
@@ -0,0 +1,12 @@
+$(() => {
+	$('.category-card').on('click', (e) => {
+		let category_type = e.currentTarget.dataset.type;
+		let category_name = e.currentTarget.dataset.name;
+
+		if (category_type != "item_group") {
+			let filters = {};
+			filters[category_type] =  [category_name];
+			window.location.href = "/all-products?field_filters=" + JSON.stringify(filters);
+		}
+	});
+});
\ No newline at end of file
diff --git a/erpnext/www/shop-by-category/index.py b/erpnext/www/shop-by-category/index.py
new file mode 100644
index 0000000..09f97ba
--- /dev/null
+++ b/erpnext/www/shop-by-category/index.py
@@ -0,0 +1,76 @@
+import frappe
+from frappe import _
+
+sitemap = 1
+
+def get_context(context):
+	context.body_class = "product-page"
+
+	settings = frappe.get_cached_doc("E Commerce Settings")
+	context.categories_enabled = settings.enable_field_filters
+
+	if context.categories_enabled:
+		categories = [row.fieldname for row in settings.filter_fields]
+		context.tabs = get_tabs(categories)
+
+	if settings.slideshow:
+		context.slideshow = get_slideshow(settings.slideshow)
+
+	context.no_cache = 1
+
+def get_slideshow(slideshow):
+	values = {
+		'show_indicators': 1,
+		'show_controls': 1,
+		'rounded': 1,
+		'slider_name': "Categories"
+	}
+	slideshow = frappe.get_cached_doc("Website Slideshow", slideshow)
+	slides = slideshow.get({"doctype": "Website Slideshow Item"})
+	for index, slide in enumerate(slides, start=1):
+		values[f"slide_{index}_image"] = slide.image
+		values[f"slide_{index}_title"] = slide.heading
+		values[f"slide_{index}_subtitle"] = slide.description
+		values[f"slide_{index}_theme"] = slide.get("theme") or "Light"
+		values[f"slide_{index}_content_align"] = slide.get("content_align") or "Centre"
+		values[f"slide_{index}_primary_action"] = slide.url
+
+	return values
+
+def get_tabs(categories):
+	tab_values = {
+		'title': _("Shop by Category"),
+	}
+
+	categorical_data = get_category_records(categories)
+	for index, tab in enumerate(categorical_data, start=1):
+		tab_values[f"tab_{index + 1}_title"] = frappe.unscrub(tab)
+		# pre-render cards for each tab
+		tab_values[f"tab_{index + 1}_content"] = frappe.render_template(
+			"erpnext/www/shop-by-category/category_card_section.html",
+			{"data": categorical_data[tab], "type": tab}
+		)
+	return tab_values
+
+def get_category_records(categories):
+	categorical_data = {}
+	for category in categories:
+		if category == "item_group":
+			categorical_data["item_group"] = frappe.db.get_all(
+				"Item Group",
+				filters={
+					"parent_item_group": "All Item Groups",
+					"show_in_website": 1
+				},
+				fields=["name", "parent_item_group", "is_group", "image", "route"]
+			)
+		else:
+			doctype = frappe.unscrub(category)
+			fields = ["name"]
+			if frappe.get_meta(doctype, cached=True).get_field("image"):
+				fields += ["image"]
+
+			categorical_data[category] = frappe.db.get_all(doctype, fields=fields)
+
+	return categorical_data
+
diff --git a/requirements.txt b/requirements.txt
index f447fac..39591ca 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -10,3 +10,4 @@
 taxjar~=1.9.2
 tweepy~=3.10.0
 Unidecode~=1.2.0
+redisearch==2.0.0
\ No newline at end of file