Merge branch 'develop' into payment-reconc-party-validation-fix
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 7c6b843..bd62227 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -1,16 +1,26 @@
 name: Backport
 on:
-  pull_request:
+  pull_request_target:
     types:
       - closed
       - labeled
 
 jobs:
-  backport:
-    runs-on: ubuntu-18.04
-    name: Backport
+  main:
+    runs-on: ubuntu-latest
+    timeout-minutes: 60
     steps:
-      - name: Backport
-        uses: tibdex/backport@v1
+      - name: Checkout Actions
+        uses: actions/checkout@v2
         with:
-          github_token: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
+          repository: "frappe/backport"
+          path: ./actions
+          ref: develop
+      - name: Install Actions
+        run: npm install --production --prefix ./actions
+      - name: Run backport
+        uses: ./actions/backport
+        with:
+          token: ${{secrets.BACKPORT_BOT_TOKEN}}
+          labelsToAdd: "backport"
+          title: "{{originalTitle}}"
diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml
index cdf676d..db46c56 100644
--- a/.github/workflows/docs-checker.yml
+++ b/.github/workflows/docs-checker.yml
@@ -6,6 +6,7 @@
 jobs:
   build:
     runs-on: ubuntu-latest
+    timeout-minutes: 10
 
     steps:
       - name: 'Setup Environment'
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index b96a3d6..dc72987 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -5,6 +5,7 @@
 jobs:
   test:
     runs-on: ubuntu-18.04
+    timeout-minutes: 60
 
     name: Patch Test
 
diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml
index 69afa15..606002e 100644
--- a/.github/workflows/server-tests.yml
+++ b/.github/workflows/server-tests.yml
@@ -9,6 +9,7 @@
 jobs:
   test:
     runs-on: ubuntu-18.04
+    timeout-minutes: 60
 
     strategy:
       fail-fast: false
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 412a05b..9e29b6f 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -7,6 +7,7 @@
 jobs:
   test:
     runs-on: ubuntu-18.04
+    timeout-minutes: 60
 
     strategy:
       fail-fast: false
diff --git a/CODEOWNERS b/CODEOWNERS
index 219b6bb..a4a14de 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -21,13 +21,13 @@
 erpnext/shopping_cart/          @marination
 erpnext/stock/                  @marination @rohitwaghchaure @ankush
 
-erpnext/crm/                    @ruchamahabal
-erpnext/education/              @ruchamahabal
-erpnext/healthcare/             @ruchamahabal
-erpnext/hr/                     @ruchamahabal
+erpnext/crm/                    @ruchamahabal @pateljannat
+erpnext/education/              @ruchamahabal @pateljannat
+erpnext/healthcare/             @ruchamahabal @pateljannat @chillaranand
+erpnext/hr/                     @ruchamahabal @pateljannat
 erpnext/non_profit/             @ruchamahabal
-erpnext/payroll                 @ruchamahabal
-erpnext/projects/               @ruchamahabal
+erpnext/payroll                 @ruchamahabal @pateljannat
+erpnext/projects/               @ruchamahabal @pateljannat
 
 erpnext/controllers             @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
 
diff --git a/erpnext/__init__.py b/erpnext/__init__.py
index a181c2d..c90e01c 100644
--- a/erpnext/__init__.py
+++ b/erpnext/__init__.py
@@ -5,7 +5,7 @@
 from erpnext.hooks import regional_overrides
 from frappe.utils import getdate
 
-__version__ = '13.7.1'
+__version__ = '13.8.0'
 
 def get_default_company(user=None):
 	'''Get default company for user'''
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 5619321..f2b0a8c 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -27,6 +27,9 @@
 		if not (self.company and self.posting_date):
 			frappe.throw(_("Please select Company and Posting Date to getting entries"))
 
+	def on_cancel(self):
+		self.ignore_linked_doctypes = ('GL Entry')
+
 	@frappe.whitelist()
 	def check_journal_entry_condition(self):
 		total_debit = frappe.db.get_value("Journal Entry Account", {
@@ -99,10 +102,12 @@
 					sum(debit) - sum(credit) as balance
 				from `tabGL Entry`
 				where account in (%s)
-				group by account, party_type, party
+				and posting_date <= %s
+				and is_cancelled = 0
+				group by account, NULLIF(party_type,''), NULLIF(party,'')
 				having sum(debit) != sum(credit)
 				order by account
-			""" % ', '.join(['%s']*len(accounts)), tuple(accounts), as_dict=1)
+			""" % (', '.join(['%s']*len(accounts)), '%s'), tuple(accounts + [self.posting_date]), as_dict=1)
 
 		return account_details
 
@@ -143,9 +148,9 @@
 				"party_type": d.get("party_type"),
 				"party": d.get("party"),
 				"account_currency": d.get("account_currency"),
-				"balance": d.get("balance_in_account_currency"),
-				dr_or_cr: abs(d.get("balance_in_account_currency")),
-				"exchange_rate":d.get("new_exchange_rate"),
+				"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
+				dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
+				"exchange_rate": flt(d.get("new_exchange_rate"), d.precision("new_exchange_rate")),
 				"reference_type": "Exchange Rate Revaluation",
 				"reference_name": self.name,
 				})
@@ -154,9 +159,9 @@
 				"party_type": d.get("party_type"),
 				"party": d.get("party"),
 				"account_currency": d.get("account_currency"),
-				"balance": d.get("balance_in_account_currency"),
-				reverse_dr_or_cr: abs(d.get("balance_in_account_currency")),
-				"exchange_rate": d.get("current_exchange_rate"),
+				"balance": flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")),
+				reverse_dr_or_cr: flt(abs(d.get("balance_in_account_currency")), d.precision("balance_in_account_currency")),
+				"exchange_rate": flt(d.get("current_exchange_rate"), d.precision("current_exchange_rate")),
 				"reference_type": "Exchange Rate Revaluation",
 				"reference_name": self.name
 				})
@@ -185,9 +190,9 @@
 
 	account_details = {}
 	company_currency = erpnext.get_company_currency(company)
-	balance = get_balance_on(account, party_type=party_type, party=party, in_account_currency=False)
+	balance = get_balance_on(account, date=posting_date, party_type=party_type, party=party, in_account_currency=False)
 	if balance:
-		balance_in_account_currency = get_balance_on(account, party_type=party_type, party=party)
+		balance_in_account_currency = get_balance_on(account, date=posting_date, party_type=party_type, party=party)
 		current_exchange_rate = balance / balance_in_account_currency if balance_in_account_currency else 0
 		new_exchange_rate = get_exchange_rate(account_currency, company_currency, posting_date)
 		new_balance_in_base_currency = balance_in_account_currency * new_exchange_rate
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 6635128..d788d91 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -306,5 +306,5 @@
 				}
 			]
 		})
-
+		jv.flags.ignore_mandatory = True
 		jv.submit()
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 7459c11..33c3e04 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -1545,6 +1545,7 @@
    "fieldname": "consolidated_invoice",
    "fieldtype": "Link",
    "label": "Consolidated Sales Invoice",
+   "no_copy": 1,
    "options": "Sales Invoice",
    "read_only": 1
   }
@@ -1552,7 +1553,7 @@
  "icon": "fa fa-file-text",
  "is_submittable": 1,
  "links": [],
- "modified": "2021-02-01 15:03:33.800707",
+ "modified": "2021-07-29 13:37:20.636171",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index ffe8be1..3173db1 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -15,6 +15,7 @@
 class TestPricingRule(unittest.TestCase):
 	def setUp(self):
 		delete_existing_pricing_rules()
+		setup_pricing_rule_data()
 
 	def tearDown(self):
 		delete_existing_pricing_rules()
@@ -554,6 +555,8 @@
 		for doc in [si, si1]:
 			doc.delete()
 
+test_dependencies = ["Campaign"]
+
 def make_pricing_rule(**args):
 	args = frappe._dict(args)
 
@@ -600,6 +603,13 @@
 	if args.get(applicable_for):
 		doc.db_set(applicable_for, args.get(applicable_for))
 
+def setup_pricing_rule_data():
+	if not frappe.db.exists('Campaign', '_Test Campaign'):
+		frappe.get_doc({
+			'doctype': 'Campaign',
+			'campaign_name': '_Test Campaign',
+			'name': '_Test Campaign'
+		}).insert()
 
 def delete_existing_pricing_rules():
 	for doctype in ["Pricing Rule", "Pricing Rule Item Code",
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index b54d0e7..94abf3b 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -168,7 +168,7 @@
 			frappe.throw(_("Invalid {0}").format(args.get(field)))
 
 		parent_groups = frappe.db.sql_list("""select name from `tab%s`
-			where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+			where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
 
 		if parenttype in ["Customer Group", "Item Group", "Territory"]:
 			parent_field = "parent_{0}".format(frappe.scrub(parenttype))
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index b99d75e..863c104 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -27,6 +27,8 @@
 from erpnext.accounts.deferred_revenue import validate_service_stop_date
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import get_item_account_wise_additional_cost
 
+class WarehouseMissingError(frappe.ValidationError): pass
+
 form_grid_templates = {
 	"items": "templates/form_grid/item_grid.html"
 }
@@ -207,8 +209,8 @@
 		if self.update_stock and for_validate:
 			for d in self.get('items'):
 				if not d.warehouse:
-					frappe.throw(_("Warehouse required at Row No {0}, please set default warehouse for the item {1} for the company {2}").
-						format(d.idx, d.item_code, self.company))
+					frappe.throw(_("Row No {0}: Warehouse is required. Please set a Default Warehouse for Item {1} and Company {2}").
+						format(d.idx, d.item_code, self.company), exc=WarehouseMissingError)
 
 		super(PurchaseInvoice, self).validate_warehouse()
 
@@ -246,7 +248,7 @@
 				and (not item.po_detail or
 					not frappe.db.get_value("Purchase Order Item", item.po_detail, "delivered_by_supplier")):
 
-				if self.update_stock and (not item.from_warehouse):
+				if self.update_stock and item.warehouse and (not item.from_warehouse):
 					if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
 						msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
 							item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
@@ -657,7 +659,7 @@
 								)
 								gl_entries.append(
 									self.get_gl_dict({
-										"account": self.get_company_default("exchange_gain_loss_account"),		
+										"account": self.get_company_default("exchange_gain_loss_account"),
 										"against": self.supplier,
 										"credit": discrepancy_caused_by_exchange_rate_difference,
 										"cost_center": item.cost_center,
@@ -1193,7 +1195,7 @@
 			purchase_receipts_or_invoices.append(item.get(doc_reference))
 		if item.get(items_reference):
 			items.append(item.get(items_reference))
-	
+
 	exchange_rate_map = frappe._dict(frappe.get_all(parent_doctype, filters={'name': ('in',
 		purchase_receipts_or_invoices)}, fields=['name', 'conversion_rate'], as_list=1))
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index db6f143..e90b35f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -240,7 +240,7 @@
 		pi.conversion_rate = 80
 
 		pi.insert()
-		pi.submit()		
+		pi.submit()
 
 		# Get exchnage gain and loss account
 		exchange_gain_loss_account = frappe.db.get_value('Company', pi.company, 'exchange_gain_loss_account')
@@ -978,7 +978,7 @@
 		unlink_enabled = frappe.db.get_value(
 			"Accounts Settings", "Accounts Settings",
 			"unlink_payment_on_cancel_of_invoice")
-		
+
 		frappe.db.set_value(
 			"Accounts Settings", "Accounts Settings",
 			"unlink_payment_on_cancel_of_invoice", 1)
@@ -1018,8 +1018,8 @@
 
 		expected_gle = [
 			["_Test Account Cost for Goods Sold - _TC", 37500.0],
-			["_Test Payable USD - _TC", -40000.0],
-			["Exchange Gain/Loss - _TC", 2500.0]
+			["_Test Payable USD - _TC", -35000.0],
+			["Exchange Gain/Loss - _TC", -2500.0]
 		]
 
 		gl_entries = frappe.db.sql("""
@@ -1027,7 +1027,7 @@
 			where voucher_no=%s
 			group by account
 			order by account asc""", (pi.name), as_dict=1)
-		
+
 		for i, gle in enumerate(gl_entries):
 			self.assertEqual(expected_gle[i][0], gle.account)
 			self.assertEqual(expected_gle[i][1], gle.balance)
@@ -1049,8 +1049,8 @@
 
 		expected_gle = [
 			["_Test Account Cost for Goods Sold - _TC", 36500.0],
-			["_Test Payable USD - _TC", -38000.0],
-			["Exchange Gain/Loss - _TC", 1500.0]
+			["_Test Payable USD - _TC", -35000.0],
+			["Exchange Gain/Loss - _TC", -1500.0]
 		]
 
 		gl_entries = frappe.db.sql("""
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c6e6e3d..be20b18 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2,13 +2,14 @@
 # License: GNU General Public License v3. See license.txt
 from __future__ import unicode_literals
 
-import frappe
+import frappe, erpnext
 
 import unittest, copy, time
 from frappe.utils import nowdate, flt, getdate, cint, add_days, add_months
 from frappe.model.dynamic_links import get_dynamic_link_map
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry, get_qty_after_transaction
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import unlink_payment_on_cancel_of_invoice
+from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import WarehouseMissingError
 from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_data
 from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
@@ -1073,7 +1074,7 @@
 	def test_gle_made_when_asset_is_returned(self):
 		create_asset_data()
 		asset = create_asset(item_code="Macbook Pro")
-	
+
 		si = create_sales_invoice(item_code="Macbook Pro", asset=asset.name, qty=1, rate=90000)
 		return_si = create_sales_invoice(is_return=1, return_against=si.name, item_code="Macbook Pro", asset=asset.name, qty=-1, rate=90000)
 
@@ -1081,7 +1082,7 @@
 
 		# Asset value is 100,000 but it was sold for 90,000, so there should be a loss of 10,000
 		loss_for_si = frappe.get_all(
-			"GL Entry", 
+			"GL Entry",
 			filters = {
 				"voucher_no": si.name,
 				"account": disposal_account
@@ -1090,7 +1091,7 @@
 		)[0]
 
 		loss_for_return_si = frappe.get_all(
-			"GL Entry", 
+			"GL Entry",
 			filters = {
 				"voucher_no": return_si.name,
 				"account": disposal_account
@@ -1836,6 +1837,89 @@
 		self.assertEqual(target_doc.company, "_Test Company 1")
 		self.assertEqual(target_doc.supplier, "_Test Internal Supplier")
 
+	def test_inter_company_transaction_without_default_warehouse(self):
+		"Check mapping (expense account) of inter company SI to PI in absence of default warehouse."
+		# setup
+		old_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
+		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
+
+		old_perpetual_inventory = erpnext.is_perpetual_inventory_enabled('_Test Company 1')
+		frappe.local.enable_perpetual_inventory['_Test Company 1'] = 1
+
+		frappe.db.set_value("Company", '_Test Company 1', "stock_received_but_not_billed", "Stock Received But Not Billed - _TC1")
+		frappe.db.set_value("Company", '_Test Company 1', "expenses_included_in_valuation", "Expenses Included In Valuation - _TC1")
+
+
+		if not frappe.db.exists("Customer", "_Test Internal Customer"):
+			customer = frappe.get_doc({
+				"customer_group": "_Test Customer Group",
+				"customer_name": "_Test Internal Customer",
+				"customer_type": "Individual",
+				"doctype": "Customer",
+				"territory": "_Test Territory",
+				"is_internal_customer": 1,
+				"represents_company": "_Test Company 1"
+			})
+
+			customer.append("companies", {
+				"company": "Wind Power LLC"
+			})
+
+			customer.insert()
+
+		if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
+			supplier = frappe.get_doc({
+				"supplier_group": "_Test Supplier Group",
+				"supplier_name": "_Test Internal Supplier",
+				"doctype": "Supplier",
+				"is_internal_supplier": 1,
+				"represents_company": "Wind Power LLC"
+			})
+
+			supplier.append("companies", {
+				"company": "_Test Company 1"
+			})
+
+			supplier.insert()
+
+		# begin test
+		si = create_sales_invoice(
+			company = "Wind Power LLC",
+			customer = "_Test Internal Customer",
+			debit_to = "Debtors - WP",
+			warehouse = "Stores - WP",
+			income_account = "Sales - WP",
+			expense_account = "Cost of Goods Sold - WP",
+			cost_center = "Main - WP",
+			currency = "USD",
+			update_stock = 1,
+			do_not_save = 1
+		)
+		si.selling_price_list = "_Test Price List Rest of the World"
+		si.submit()
+
+		target_doc = make_inter_company_transaction("Sales Invoice", si.name)
+
+		# in absence of warehouse Stock Received But Not Billed is set as expense account while mapping
+		# mapping is not obstructed
+		self.assertIsNone(target_doc.items[0].warehouse)
+		self.assertEqual(target_doc.items[0].expense_account, "Stock Received But Not Billed - _TC1")
+
+		target_doc.items[0].update({"cost_center": "Main - _TC1"})
+
+		# missing warehouse is validated on save, after mapping
+		self.assertRaises(WarehouseMissingError, target_doc.save)
+
+		target_doc.items[0].update({"warehouse": "Stores - _TC1"})
+		target_doc.save()
+
+		# after warehouse is set, linked account or default inventory account is set
+		self.assertEqual(target_doc.items[0].expense_account, 'Stock In Hand - _TC1')
+
+		# tear down
+		frappe.local.enable_perpetual_inventory['_Test Company 1'] = old_perpetual_inventory
+		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", old_negative_stock)
+
 	def test_internal_transfer_gl_entry(self):
 		## Create internal transfer account
 		account = create_account(account_name="Unrealized Profit",
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
index 1b7a0fe..cfdb167 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges/sales_taxes_and_charges.json
@@ -27,7 +27,8 @@
   "base_tax_amount",
   "base_total",
   "base_tax_amount_after_discount_amount",
-  "item_wise_tax_detail"
+  "item_wise_tax_detail",
+  "dont_recompute_tax"
  ],
  "fields": [
   {
@@ -200,13 +201,22 @@
    "fieldname": "included_in_paid_amount",
    "fieldtype": "Check",
    "label": "Considered In Paid Amount"
+  },
+  {
+   "default": "0",
+   "fieldname": "dont_recompute_tax",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Dont Recompute tax",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-06-14 01:44:36.899147",
+ "modified": "2021-07-27 12:40:59.051803",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Taxes and Charges",
diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py b/erpnext/accounts/doctype/south_africa_vat_account/__init__.py
similarity index 100%
rename from erpnext/erpnext_integrations/doctype/shopify_tax_account/__init__.py
rename to erpnext/accounts/doctype/south_africa_vat_account/__init__.py
diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json
new file mode 100644
index 0000000..fa1aa7d
--- /dev/null
+++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.json
@@ -0,0 +1,34 @@
+{
+ "actions": [],
+ "autoname": "account",
+ "creation": "2021-07-08 22:04:24.634967",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "account"
+ ],
+ "fields": [
+  {
+   "allow_in_quick_entry": 1,
+   "fieldname": "account",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_preview": 1,
+   "label": "Account",
+   "options": "Account"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-08 22:35:33.202911",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "South Africa VAT Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py
new file mode 100644
index 0000000..4bd8c65
--- /dev/null
+++ b/erpnext/accounts/doctype/south_africa_vat_account/south_africa_vat_account.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class SouthAfricaVATAccount(Document):
+	pass
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
index f9160e2..153906f 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.json
@@ -1,263 +1,151 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "Prompt", 
- "beta": 0, 
- "creation": "2018-04-13 18:42:06.431683", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "Prompt",
+ "creation": "2018-04-13 18:42:06.431683",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "category_details_section",
+  "category_name",
+  "round_off_tax_amount",
+  "column_break_2",
+  "consider_party_ledger_amount",
+  "tax_on_excess_amount",
+  "section_break_8",
+  "rates",
+  "section_break_7",
+  "accounts"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "fieldname": "category_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": "Category Name",
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "show_days": 1,
+   "show_seconds": 1
+  },
   {
-   "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,
    "label": "Tax Withholding Rates",
-   "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
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "rates",
    "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": "Rates",
-   "length": 0,
-   "no_copy": 0,
    "options": "Tax Withholding Rate",
-   "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
+   "show_days": 1,
+   "show_seconds": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_7", 
-   "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, 
+   "fieldname": "section_break_7",
+   "fieldtype": "Section Break",
    "label": "Account 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
-  }, 
+   "show_days": 1,
+   "show_seconds": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "accounts", 
-   "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": "Accounts", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Tax Withholding Account", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "fieldname": "accounts",
+   "fieldtype": "Table",
+   "label": "Accounts",
+   "options": "Tax Withholding Account",
+   "reqd": 1,
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "category_details_section",
+   "fieldtype": "Section Break",
+   "label": "Category Details",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "default": "0",
+   "description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
+   "fieldname": "consider_party_ledger_amount",
+   "fieldtype": "Check",
+   "label": "Consider Entire Party Ledger Amount",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "default": "0",
+   "description": "Tax will be withheld only for amount exceeding the cumulative threshold",
+   "fieldname": "tax_on_excess_amount",
+   "fieldtype": "Check",
+   "label": "Only Deduct Tax On Excess Amount ",
+   "show_days": 1,
+   "show_seconds": 1
+  },
+  {
+   "description": "Checking this will round off the tax amount to the nearest integer",
+   "fieldname": "round_off_tax_amount",
+   "fieldtype": "Check",
+   "label": "Round Off Tax Amount",
+   "show_days": 1,
+   "show_seconds": 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-07-17 22:53:26.193179",
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Tax Withholding Category", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-07-27 21:47:34.396071",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Tax Withholding Category",
+ "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, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
    "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": "Accounts Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
    "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": "Accounts User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 0,
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
index b9ee4a0..481ef28 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/tax_withholding_category.py
@@ -6,7 +6,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import flt, getdate
+from frappe.utils import flt, getdate, cint
 from erpnext.accounts.utils import get_fiscal_year
 
 class TaxWithholdingCategory(Document):
@@ -86,7 +86,10 @@
 				"rate": tax_rate_detail.tax_withholding_rate,
 				"threshold": tax_rate_detail.single_threshold,
 				"cumulative_threshold": tax_rate_detail.cumulative_threshold,
-				"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category
+				"description": tax_withholding.category_name if tax_withholding.category_name else tax_withholding_category,
+				"consider_party_ledger_amount": tax_withholding.consider_party_ledger_amount,
+				"tax_on_excess_amount": tax_withholding.tax_on_excess_amount,
+				"round_off_tax_amount": tax_withholding.round_off_tax_amount
 			})
 
 def get_tax_withholding_rates(tax_withholding, fiscal_year):
@@ -145,6 +148,7 @@
 def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
 	fiscal_year = fiscal_year_details[0]
 
+
 	vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
 	advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
 	taxable_vouchers = vouchers + advance_vouchers
@@ -235,10 +239,18 @@
 
 def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
 	tds_amount = 0
+	invoice_filters = {
+		'name': ('in', vouchers), 
+		'docstatus': 1
+	}
 
-	supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
-		'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
-	}, 'sum(net_total)') or 0.0
+	field = 'sum(net_total)'
+
+	if not cint(tax_details.consider_party_ledger_amount):
+		invoice_filters.update({'apply_tds': 1})
+		field = 'sum(grand_total)'
+
+	supp_credit_amt = frappe.db.get_value('Purchase Invoice', invoice_filters, field) or 0.0
 
 	supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
 		'parent': ('in', vouchers), 'docstatus': 1,
@@ -255,6 +267,13 @@
 	cumulative_threshold = tax_details.get('cumulative_threshold', 0)
 
 	if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
+		if (cumulative_threshold and supp_credit_amt >= cumulative_threshold) and cint(tax_details.tax_on_excess_amount):
+			# Get net total again as TDS is calculated on net total
+			# Grand is used to just check for threshold breach
+			net_total = frappe.db.get_value('Purchase Invoice', invoice_filters, 'sum(net_total)') or 0.0
+			net_total += inv.net_total
+			supp_credit_amt = net_total - cumulative_threshold
+
 		if ldc and is_valid_certificate(
 			ldc.valid_from, ldc.valid_upto,
 			inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
@@ -263,6 +282,9 @@
 			tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
 		else:
 			tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
+	
+	if cint(tax_details.round_off_tax_amount):
+		tds_amount = round(tds_amount)
 
 	return tds_amount
 
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index dd26be7..2ba22ca 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -87,6 +87,31 @@
 		for d in invoices:
 			d.cancel()
 
+	def test_tax_withholding_category_checks(self):
+		invoices = []
+		frappe.db.set_value("Supplier", "Test TDS Supplier3", "tax_withholding_category", "New TDS Category")
+
+		# First Invoice with no tds check
+		pi = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000, do_not_save=True)
+		pi.apply_tds = 0
+		pi.save()
+		pi.submit()
+		invoices.append(pi)
+		
+		# Second Invoice will apply TDS checked
+		pi1 = create_purchase_invoice(supplier = "Test TDS Supplier3", rate = 20000)
+		pi1.submit()
+		invoices.append(pi1)
+
+		# Cumulative threshold is 30000
+		# Threshold calculation should be on both the invoices
+		# TDS should be applied only on 1000
+		self.assertEqual(pi1.taxes[0].tax_amount, 1000)
+
+		for d in invoices:
+			d.cancel()
+
+
 	def test_cumulative_threshold_tcs(self):
 		frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
 		invoices = []
@@ -195,7 +220,7 @@
 
 def create_records():
 	# create a new suppliers
-	for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
+	for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2', 'Test TDS Supplier3']:
 		if frappe.db.exists('Supplier', name):
 			continue
 
@@ -311,3 +336,23 @@
 				'account': 'TDS - _TC'
 			}]
 		}).insert()
+
+	if not frappe.db.exists("Tax Withholding Category", "New TDS Category"):
+		frappe.get_doc({
+			"doctype": "Tax Withholding Category",
+			"name": "New TDS Category",
+			"category_name": "New TDS Category",
+			"round_off_tax_amount": 1,
+			"consider_party_ledger_amount": 1,
+			"tax_on_excess_amount": 1,
+			"rates": [{
+				'fiscal_year': fiscal_year,
+				'tax_withholding_rate': 10,
+				'single_threshold': 0,
+				'cumulative_threshold': 30000
+			}],
+			"accounts": [{
+				'company': '_Test Company',
+				'account': 'TDS - _TC'
+			}]
+		}).insert()
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a11b77a..b54646f 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -99,7 +99,6 @@
 					voucher_no = gle.voucher_no,
 					party = gle.party,
 					posting_date = gle.posting_date,
-					remarks = gle.remarks,
 					account_currency = gle.account_currency,
 					invoiced = 0.0,
 					paid = 0.0,
@@ -579,7 +578,7 @@
 		self.gl_entries = frappe.db.sql("""
 			select
 				name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
-				against_voucher_type, against_voucher, account_currency, remarks, {0}
+				against_voucher_type, against_voucher, account_currency, {0}
 			from
 				`tabGL Entry`
 			where
@@ -792,8 +791,6 @@
 			self.add_column(label=_('Supplier Group'), fieldname='supplier_group', fieldtype='Link',
 				options='Supplier Group')
 
-		self.add_column(label=_('Remarks'), fieldname='remarks', fieldtype='Text', width=200)
-
 	def add_column(self, label, fieldname=None, fieldtype='Currency', options=None, width=120):
 		if not fieldname: fieldname = scrub(label)
 		if fieldtype=='Currency': options='currency'
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index 84c7454..6d8623c 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -241,6 +241,7 @@
 						sle.voucher_detail_no == row.item_row:
 							previous_stock_value = len(my_sle) > i+1 and \
 								flt(my_sle[i+1].stock_value) or 0.0
+
 							if previous_stock_value:
 								return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
 							else:
@@ -335,7 +336,7 @@
 		res = frappe.db.sql("""select item_code, voucher_type, voucher_no,
 				voucher_detail_no, stock_value, warehouse, actual_qty as qty
 			from `tabStock Ledger Entry`
-			where company=%(company)s
+			where company=%(company)s and is_cancelled = 0
 			order by
 				item_code desc, warehouse desc, posting_date desc,
 				posting_time desc, creation desc""", self.filters, as_dict=True)
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index d569e1c..f5adc46 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -568,10 +568,10 @@
 		frappe.msgprint(_("Payment Entries {0} are un-linked").format("\n".join(linked_pe)))
 
 @frappe.whitelist()
-def get_company_default(company, fieldname):
-	value = frappe.get_cached_value('Company',  company,  fieldname)
+def get_company_default(company, fieldname, ignore_validation=False):
+	value = frappe.get_cached_value('Company', company, fieldname)
 
-	if not value:
+	if not ignore_validation and not value:
 		throw(_("Please set default {0} in Company {1}")
 			.format(frappe.get_meta("Company").get_label(fieldname), company))
 
@@ -968,7 +968,7 @@
 		for e in existing_gle:
 			if entry.account == e.account:
 				account_existed = True
-			if (entry.account == e.account and entry.against_account == e.against_account
+			if (entry.account == e.account
 					and (not entry.cost_center or not e.cost_center or entry.cost_center == e.cost_center)
 					and ( flt(entry.debit, precision) != flt(e.debit, precision) or
 						flt(entry.credit, precision) != flt(e.credit, precision))):
diff --git a/erpnext/change_log/v13/v13_8_0.md b/erpnext/change_log/v13/v13_8_0.md
new file mode 100644
index 0000000..98ed95a
--- /dev/null
+++ b/erpnext/change_log/v13/v13_8_0.md
@@ -0,0 +1,39 @@
+# Version 13.8.0 Release Notes
+
+### Features & Enhancements
+- Report to show COGS by item groups ([#26222](https://github.com/frappe/erpnext/pull/26222))
+- Enhancements in TDS ([#26677](https://github.com/frappe/erpnext/pull/26677))
+- API Endpoint to update halted Razorpay subscriptions ([#26564](https://github.com/frappe/erpnext/pull/26564))
+
+### Fixes
+- Incorrect bom name ([#26600](https://github.com/frappe/erpnext/pull/26600))
+- Exchange rate revaluation posting date and precision fixes ([#26651](https://github.com/frappe/erpnext/pull/26651))
+- POS item cart dom updates ([#26460](https://github.com/frappe/erpnext/pull/26460))
+- General Ledger report not working with filter group by ([#26439](https://github.com/frappe/erpnext/pull/26438))
+- Tax calculation for Recurring additional salary ([#24206](https://github.com/frappe/erpnext/pull/24206))
+- Validation check for batch for stock reconciliation type in stock entry ([#26487](https://github.com/frappe/erpnext/pull/26487))
+- Improved UX for additional discount field ([#26502](https://github.com/frappe/erpnext/pull/26502))
+- Add missing cess amount in GSTR-3B report ([#26644](https://github.com/frappe/erpnext/pull/26644))
+- Optimized code for reposting item valuation ([#26431](https://github.com/frappe/erpnext/pull/26431))
+- FG item not fetched in manufacture entry ([#26508](https://github.com/frappe/erpnext/pull/26508))
+- Errors on parallel requests creation of company for India  ([#26420](https://github.com/frappe/erpnext/pull/26420))
+- Incorrect valuation rate calculation in gross profit report ([#26558](https://github.com/frappe/erpnext/pull/26558))
+- Empty "against account" in Purchase Receipt GLE ([#26712](https://github.com/frappe/erpnext/pull/26712))
+- Remove cancelled entries from Stock and Account Value comparison report ([#26721](https://github.com/frappe/erpnext/pull/26721))
+- Remove manual permission checking ([#26691](https://github.com/frappe/erpnext/pull/26691))
+- Delete child docs when parent doc is deleted ([#26518](https://github.com/frappe/erpnext/pull/26518))
+- GST Reports timeout issue ([#26646](https://github.com/frappe/erpnext/pull/26646))
+- Parent condition in pricing rules ([#26727](https://github.com/frappe/erpnext/pull/26727))
+- Added Company filters for Loan ([#26294](https://github.com/frappe/erpnext/pull/26294))
+- Incorrect discount amount on amended document ([#26292](https://github.com/frappe/erpnext/pull/26292))
+- Exchange gain loss not set for advances linked with invoices ([#26436](https://github.com/frappe/erpnext/pull/26436))
+- Unallocated amount in Payment Entry after taxes ([#26412](https://github.com/frappe/erpnext/pull/26412))
+- Wrong operation time in Work Order ([#26613](https://github.com/frappe/erpnext/pull/26613))
+- Serial No and Batch validation ([#26614](https://github.com/frappe/erpnext/pull/26614))
+- Gl Entries for exchange gain loss ([#26734](https://github.com/frappe/erpnext/pull/26734))
+- TDS computation summary shows cancelled invoices ([#26485](https://github.com/frappe/erpnext/pull/26485))
+- Price List rate not fetched for return sales invoice fixed ([#26560](https://github.com/frappe/erpnext/pull/26560))
+- Included company in link document type filters for contact ([#26576](https://github.com/frappe/erpnext/pull/26576))
+- Ignore mandatory fields while creating payment reconciliation Journal Entry ([#26643](https://github.com/frappe/erpnext/pull/26643))
+- Unable to download GSTR-1 json ([#26418](https://github.com/frappe/erpnext/pull/26418))
+- Paging buttons not working on item group portal page ([#26498](https://github.com/frappe/erpnext/pull/26498))
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 4c313c4..bf4ab1a 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -674,19 +674,24 @@
 		if self.get('doctype') in ['Purchase Invoice', 'Sales Invoice']:
 			for d in self.get("advances"):
 				if d.exchange_gain_loss:
-					party = self.supplier if self.get('doctype') == 'Purchase Invoice' else self.customer
-					party_account = self.credit_to if self.get('doctype') == 'Purchase Invoice' else self.debit_to
-					party_type = "Supplier" if self.get('doctype') == 'Purchase Invoice' else "Customer"
+					is_purchase_invoice = self.get('doctype') == 'Purchase Invoice'
+					party = self.supplier if is_purchase_invoice else self.customer
+					party_account = self.credit_to if is_purchase_invoice else self.debit_to
+					party_type = "Supplier" if is_purchase_invoice else "Customer"
 
 					gain_loss_account = frappe.db.get_value('Company', self.company, 'exchange_gain_loss_account')
+					if not gain_loss_account:
+						frappe.throw(_("Please set Default Exchange Gain/Loss Account in Company {}")
+							.format(self.get('company')))
 					account_currency = get_account_currency(gain_loss_account)
 					if account_currency != self.company_currency:
-						frappe.throw(_("Currency for {0} must be {1}").format(d.account, self.company_currency))
+						frappe.throw(_("Currency for {0} must be {1}").format(gain_loss_account, self.company_currency))
 
 					# for purchase
 					dr_or_cr = 'debit' if d.exchange_gain_loss > 0 else 'credit'
-					# just reverse for sales?
-					dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
+					if not is_purchase_invoice:
+						# just reverse for sales?
+						dr_or_cr = 'debit' if dr_or_cr == 'credit' else 'credit'
 
 					gl_entries.append(
 						self.get_gl_dict({
@@ -904,9 +909,9 @@
 		frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
 			.format(item.item_code, item.idx, max_allowed_amt))
 
-	def get_company_default(self, fieldname):
+	def get_company_default(self, fieldname, ignore_validation=False):
 		from erpnext.accounts.utils import get_company_default
-		return get_company_default(self.company, fieldname)
+		return get_company_default(self.company, fieldname, ignore_validation=ignore_validation)
 
 	def get_stock_items(self):
 		stock_items = []
@@ -1112,8 +1117,11 @@
 			for d in self.get("payment_schedule"):
 				if d.invoice_portion:
 					d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
-					d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
+					d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('base_payment_amount'))
 					d.outstanding = d.payment_amount
+				elif not d.invoice_portion:
+					d.base_payment_amount = flt(base_grand_total * self.get("conversion_rate"), d.precision('base_payment_amount'))
+
 
 	def set_due_date(self):
 		due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
diff --git a/erpnext/controllers/employee_boarding_controller.py b/erpnext/controllers/employee_boarding_controller.py
new file mode 100644
index 0000000..1898222
--- /dev/null
+++ b/erpnext/controllers/employee_boarding_controller.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from frappe import _
+from frappe.desk.form import assign_to
+from frappe.model.document import Document
+from frappe.utils import flt, unique
+
+class EmployeeBoardingController(Document):
+	'''
+		Create the project and the task for the boarding process
+		Assign to the concerned person and roles as per the onboarding/separation template
+	'''
+	def validate(self):
+		# remove the task if linked before submitting the form
+		if self.amended_from:
+			for activity in self.activities:
+				activity.task = ''
+
+	def on_submit(self):
+		# create the project for the given employee onboarding
+		project_name = _(self.doctype) + ' : '
+		if self.doctype == 'Employee Onboarding':
+			project_name += self.job_applicant
+		else:
+			project_name += self.employee
+
+		project = frappe.get_doc({
+				'doctype': 'Project',
+				'project_name': project_name,
+				'expected_start_date': self.date_of_joining if self.doctype == 'Employee Onboarding' else self.resignation_letter_date,
+				'department': self.department,
+				'company': self.company
+			}).insert(ignore_permissions=True, ignore_mandatory=True)
+
+		self.db_set('project', project.name)
+		self.db_set('boarding_status', 'Pending')
+		self.reload()
+		self.create_task_and_notify_user()
+
+	def create_task_and_notify_user(self):
+		# create the task for the given project and assign to the concerned person
+		for activity in self.activities:
+			if activity.task:
+				continue
+
+			task = frappe.get_doc({
+				'doctype': 'Task',
+				'project': self.project,
+				'subject': activity.activity_name + ' : ' + self.employee_name,
+				'description': activity.description,
+				'department': self.department,
+				'company': self.company,
+				'task_weight': activity.task_weight
+			}).insert(ignore_permissions=True)
+			activity.db_set('task', task.name)
+
+			users = [activity.user] if activity.user else []
+			if activity.role:
+				user_list = frappe.db.sql_list('''
+					SELECT
+						DISTINCT(has_role.parent)
+					FROM
+						`tabHas Role` has_role
+							LEFT JOIN `tabUser` user
+								ON has_role.parent = user.name
+					WHERE
+						has_role.parenttype = 'User'
+							AND user.enabled = 1
+							AND has_role.role = %s
+				''', activity.role)
+				users = unique(users + user_list)
+
+				if 'Administrator' in users:
+					users.remove('Administrator')
+
+			# assign the task the users
+			if users:
+				self.assign_task_to_users(task, users)
+
+	def assign_task_to_users(self, task, users):
+		for user in users:
+			args = {
+				'assign_to': [user],
+				'doctype': task.doctype,
+				'name': task.name,
+				'description': task.description or task.subject,
+				'notify': self.notify_users_by_email
+			}
+			assign_to.add(args)
+
+	def on_cancel(self):
+		# delete task project
+		for task in frappe.get_all('Task', filters={'project': self.project}):
+			frappe.delete_doc('Task', task.name, force=1)
+		frappe.delete_doc('Project', self.project, force=1)
+		self.db_set('project', '')
+		for activity in self.activities:
+			activity.db_set('task', '')
+
+
+@frappe.whitelist()
+def get_onboarding_details(parent, parenttype):
+	return frappe.get_all('Employee Boarding Activity',
+		fields=['activity_name', 'role', 'user', 'required_for_employee_creation', 'description', 'task_weight'],
+		filters={'parent': parent, 'parenttype': parenttype},
+		order_by= 'idx')
+
+
+def update_employee_boarding_status(project):
+	employee_onboarding = frappe.db.exists('Employee Onboarding', {'project': project.name})
+	employee_separation = frappe.db.exists('Employee Separation', {'project': project.name})
+
+	if not (employee_onboarding or employee_separation):
+		return
+
+	status = 'Pending'
+	if flt(project.percent_complete) > 0.0 and flt(project.percent_complete) < 100.0:
+		status = 'In Process'
+	elif flt(project.percent_complete) == 100.0:
+		status = 'Completed'
+
+	if employee_onboarding:
+		frappe.db.set_value('Employee Onboarding', employee_onboarding, 'boarding_status', status)
+	elif employee_separation:
+		frappe.db.set_value('Employee Separation', employee_separation, 'boarding_status', status)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 2803193..21c052a 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -407,6 +407,7 @@
 				INNER JOIN `tabBatch` batch on sle.batch_no = batch.name
 			where
 				batch.disabled = 0
+				and sle.is_cancelled = 0
 				and sle.item_code = %(item_code)s
 				and sle.warehouse = %(warehouse)s
 				and (sle.batch_no like %(txt)s
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 2526e6d..17bd735 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -53,12 +53,17 @@
 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 		for d in self.get("items"):
 			if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
-				serial_nos = get_serial_nos(d.serial_no)
-				for serial_no_data in frappe.get_all("Serial No",
-					filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
-					if serial_no_data.batch_no != d.batch_no:
+				serial_nos = frappe.get_all("Serial No",
+					fields=["batch_no", "name", "warehouse"],
+					filters={
+						"name": ("in", get_serial_nos(d.serial_no))
+					}
+				)
+
+				for row in serial_nos:
+					if row.warehouse and row.batch_no != d.batch_no:
 						frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
-							.format(d.idx, serial_no_data.name, d.batch_no))
+							.format(d.idx, row.name, d.batch_no))
 
 			if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
 				expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 56da5b7..099c7d4 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -152,7 +152,7 @@
 				validate_taxes_and_charges(tax)
 				validate_inclusive_tax(tax, self.doc)
 
-			if not self.doc.get('is_consolidated'):
+			if not (self.doc.get('is_consolidated') or tax.get("dont_recompute_tax")):
 				tax.item_wise_tax_detail = {}
 
 			tax_fields = ["total", "tax_amount_after_discount_amount",
@@ -347,7 +347,7 @@
 		elif tax.charge_type == "On Item Quantity":
 			current_tax_amount = tax_rate * item.qty
 
-		if not self.doc.get("is_consolidated"):
+		if not (self.doc.get("is_consolidated") or tax.get("dont_recompute_tax")):
 			self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
 
 		return current_tax_amount
@@ -455,7 +455,8 @@
 	def _cleanup(self):
 		if not self.doc.get('is_consolidated'):
 			for tax in self.doc.get("taxes"):
-				tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
+				if not tax.get("dont_recompute_tax"):
+					tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
 
 	def set_discount_amount(self):
 		if self.doc.additional_discount_percentage:
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/crm/doctype/campaign/__init__.py
similarity index 100%
rename from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
rename to erpnext/crm/doctype/campaign/__init__.py
diff --git a/erpnext/crm/doctype/campaign/campaign.js b/erpnext/crm/doctype/campaign/campaign.js
new file mode 100644
index 0000000..11bfa74
--- /dev/null
+++ b/erpnext/crm/doctype/campaign/campaign.js
@@ -0,0 +1,17 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Campaign', {
+	refresh: function(frm) {
+		erpnext.toggle_naming_series();
+
+		if (frm.doc.__islocal) {
+			frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
+		} else {
+			cur_frm.add_custom_button(__("View Leads"), function() {
+				frappe.route_options = {"source": "Campaign", "campaign_name": frm.doc.name};
+				frappe.set_route("List", "Lead");
+			}, "fa fa-list", true);
+		}
+	}
+});
diff --git a/erpnext/selling/doctype/campaign/campaign.json b/erpnext/crm/doctype/campaign/campaign.json
similarity index 95%
rename from erpnext/selling/doctype/campaign/campaign.json
rename to erpnext/crm/doctype/campaign/campaign.json
index 986ac13..f833f4c 100644
--- a/erpnext/selling/doctype/campaign/campaign.json
+++ b/erpnext/crm/doctype/campaign/campaign.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "naming_series:",
@@ -39,17 +40,9 @@
    "set_only_once": 1
   },
   {
-   "fieldname": "description",
-   "fieldtype": "Text",
-   "in_list_view": 1,
-   "label": "Description",
-   "oldfieldname": "description",
-   "oldfieldtype": "Text",
-   "width": "300px"
-  },
-  {
-   "fieldname": "description_section",
-   "fieldtype": "Section Break"
+   "fieldname": "campaign_schedules_section",
+   "fieldtype": "Section Break",
+   "label": "Campaign Schedules"
   },
   {
    "fieldname": "campaign_schedules",
@@ -58,16 +51,25 @@
    "options": "Campaign Email Schedule"
   },
   {
-   "fieldname": "campaign_schedules_section",
-   "fieldtype": "Section Break",
-   "label": "Campaign Schedules"
+   "fieldname": "description_section",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "description",
+   "fieldtype": "Text",
+   "in_list_view": 1,
+   "label": "Description",
+   "oldfieldname": "description",
+   "oldfieldtype": "Text",
+   "width": "300px"
   }
  ],
  "icon": "fa fa-bullhorn",
  "idx": 1,
- "modified": "2019-07-22 12:03:39.832342",
+ "links": [],
+ "modified": "2021-06-30 18:05:06.412712",
  "modified_by": "Administrator",
- "module": "Selling",
+ "module": "CRM",
  "name": "Campaign",
  "owner": "Administrator",
  "permissions": [
diff --git a/erpnext/selling/doctype/campaign/campaign.py b/erpnext/crm/doctype/campaign/campaign.py
similarity index 66%
rename from erpnext/selling/doctype/campaign/campaign.py
rename to erpnext/crm/doctype/campaign/campaign.py
index 1094542..e32799f 100644
--- a/erpnext/selling/doctype/campaign/campaign.py
+++ b/erpnext/crm/doctype/campaign/campaign.py
@@ -1,9 +1,7 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
 
-from __future__ import unicode_literals
 import frappe
-
 from frappe.model.document import Document
 from frappe.model.naming import set_name_by_naming_series
 
diff --git a/erpnext/crm/doctype/campaign/test_campaign.py b/erpnext/crm/doctype/campaign/test_campaign.py
new file mode 100644
index 0000000..7124b8c
--- /dev/null
+++ b/erpnext/crm/doctype/campaign/test_campaign.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestCampaign(unittest.TestCase):
+	pass
diff --git a/erpnext/education/api.py b/erpnext/education/api.py
index afa0be9..4493a3f 100644
--- a/erpnext/education/api.py
+++ b/erpnext/education/api.py
@@ -34,11 +34,14 @@
 			}
 		}}, ignore_permissions=True)
 	student.save()
+
+	student_applicant = frappe.db.get_value("Student Applicant", source_name,
+		["student_category", "program"], as_dict=True)
 	program_enrollment = frappe.new_doc("Program Enrollment")
 	program_enrollment.student = student.name
-	program_enrollment.student_category = student.student_category
+	program_enrollment.student_category = student_applicant.student_category
 	program_enrollment.student_name = student.title
-	program_enrollment.program = frappe.db.get_value("Student Applicant", source_name, "program")
+	program_enrollment.program = student_applicant.program
 	frappe.publish_realtime('enroll_student_progress', {"progress": [2, 4]}, user=frappe.session.user)
 	return program_enrollment
 
diff --git a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
index 9be292b..1d74973 100644
--- a/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
+++ b/erpnext/education/doctype/program_enrollment_tool_student/program_enrollment_tool_student.json
@@ -1,195 +1,68 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2016-06-10 03:29:02.539914", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
+ "actions": [],
+ "creation": "2016-06-10 03:29:02.539914",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "student_applicant",
+  "student",
+  "student_name",
+  "column_break_3",
+  "student_batch_name",
+  "student_category"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "", 
-   "fieldname": "student_applicant", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Student Applicant", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Student Applicant", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "student_applicant",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Student Applicant",
+   "options": "Student Applicant"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "", 
-   "fieldname": "student", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Student", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Student", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "student",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Student",
+   "options": "Student"
+  },
   {
-   "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
-  }, 
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "student_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": "Student 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, 
-   "unique": 0
-  }, 
+   "fieldname": "student_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Student Name",
+   "read_only": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "student_batch_name", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Student Batch Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Student Batch Name", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
+   "fieldname": "student_batch_name",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Student Batch Name",
+   "options": "Student Batch Name"
+  },
+  {
+   "fieldname": "student_category",
+   "fieldtype": "Link",
+   "label": "Student Category",
+   "options": "Student Category",
+   "read_only": 1
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-01-02 12:03:53.890741", 
- "modified_by": "Administrator", 
- "module": "Education", 
- "name": "Program Enrollment Tool Student", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "restrict_to_domain": "Education", 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 0, 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-29 18:19:54.471594",
+ "modified_by": "Administrator",
+ "module": "Education",
+ "name": "Program Enrollment Tool Student",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "restrict_to_domain": "Education",
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/connectors/shopify_connection.py b/erpnext/erpnext_integrations/connectors/shopify_connection.py
deleted file mode 100644
index 5d5b2e1..0000000
--- a/erpnext/erpnext_integrations/connectors/shopify_connection.py
+++ /dev/null
@@ -1,353 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-import json
-from frappe.utils import cstr, cint, nowdate, getdate, flt, get_request_session, get_datetime
-from erpnext.erpnext_integrations.utils import validate_webhooks_request
-from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note, make_sales_invoice
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import sync_item_from_shopify
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
-from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log, dump_request_data
-from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
-
-@frappe.whitelist(allow_guest=True)
-@validate_webhooks_request("Shopify Settings", 'X-Shopify-Hmac-Sha256', secret_key='shared_secret')
-def store_request_data(order=None, event=None):
-	if frappe.request:
-		order = json.loads(frappe.request.data)
-		event = frappe.request.headers.get('X-Shopify-Topic')
-
-	dump_request_data(order, event)
-
-def sync_sales_order(order, request_id=None, old_order_sync=False):
-	frappe.set_user('Administrator')
-	shopify_settings = frappe.get_doc("Shopify Settings")
-	frappe.flags.request_id = request_id
-
-	if not frappe.db.get_value("Sales Order", filters={"shopify_order_id": cstr(order['id'])}):
-		try:
-			validate_customer(order, shopify_settings)
-			validate_item(order, shopify_settings)
-			create_order(order, shopify_settings, old_order_sync=old_order_sync)
-		except Exception as e:
-			make_shopify_log(status="Error", exception=e)
-
-		else:
-			make_shopify_log(status="Success")
-
-def prepare_sales_invoice(order, request_id=None):
-	frappe.set_user('Administrator')
-	shopify_settings = frappe.get_doc("Shopify Settings")
-	frappe.flags.request_id = request_id
-
-	try:
-		sales_order = get_sales_order(cstr(order['id']))
-		if sales_order:
-			create_sales_invoice(order, shopify_settings, sales_order)
-			make_shopify_log(status="Success")
-	except Exception as e:
-		make_shopify_log(status="Error", exception=e, rollback=True)
-
-def prepare_delivery_note(order, request_id=None):
-	frappe.set_user('Administrator')
-	shopify_settings = frappe.get_doc("Shopify Settings")
-	frappe.flags.request_id = request_id
-
-	try:
-		sales_order = get_sales_order(cstr(order['id']))
-		if sales_order:
-			create_delivery_note(order, shopify_settings, sales_order)
-		make_shopify_log(status="Success")
-	except Exception as e:
-		make_shopify_log(status="Error", exception=e, rollback=True)
-
-def get_sales_order(shopify_order_id):
-	sales_order = frappe.db.get_value("Sales Order", filters={"shopify_order_id": shopify_order_id})
-	if sales_order:
-		so = frappe.get_doc("Sales Order", sales_order)
-		return so
-
-def validate_customer(order, shopify_settings):
-	customer_id = order.get("customer", {}).get("id")
-	if customer_id:
-		if not frappe.db.get_value("Customer", {"shopify_customer_id": customer_id}, "name"):
-			create_customer(order.get("customer"), shopify_settings)
-
-def validate_item(order, shopify_settings):
-	for item in order.get("line_items"):
-		if item.get("product_id") and not frappe.db.get_value("Item", {"shopify_product_id": item.get("product_id")}, "name"):
-			sync_item_from_shopify(shopify_settings, item)
-
-def create_order(order, shopify_settings, old_order_sync=False, company=None):
-	so = create_sales_order(order, shopify_settings, company)
-	if so:
-		if order.get("financial_status") == "paid":
-			create_sales_invoice(order, shopify_settings, so, old_order_sync=old_order_sync)
-
-		if order.get("fulfillments") and not old_order_sync:
-			create_delivery_note(order, shopify_settings, so)
-
-def create_sales_order(shopify_order, shopify_settings, company=None):
-	product_not_exists = []
-	customer = frappe.db.get_value("Customer", {"shopify_customer_id": shopify_order.get("customer", {}).get("id")}, "name")
-	so = frappe.db.get_value("Sales Order", {"shopify_order_id": shopify_order.get("id")}, "name")
-
-	if not so:
-		items = get_order_items(shopify_order.get("line_items"), shopify_settings, getdate(shopify_order.get('created_at')))
-
-		if not items:
-			message = 'Following items exists in the shopify order but relevant records were not found in the shopify Product master'
-			message += "\n" + ", ".join(product_not_exists)
-
-			make_shopify_log(status="Error", exception=message, rollback=True)
-
-			return ''
-
-		so = frappe.get_doc({
-			"doctype": "Sales Order",
-			"naming_series": shopify_settings.sales_order_series or "SO-Shopify-",
-			"shopify_order_id": shopify_order.get("id"),
-			"shopify_order_number": shopify_order.get("name"),
-			"customer": customer or shopify_settings.default_customer,
-			"transaction_date": getdate(shopify_order.get("created_at")) or nowdate(),
-			"delivery_date": getdate(shopify_order.get("created_at")) or nowdate(),
-			"company": shopify_settings.company,
-			"selling_price_list": shopify_settings.price_list,
-			"ignore_pricing_rule": 1,
-			"items": items,
-			"taxes": get_order_taxes(shopify_order, shopify_settings),
-			"apply_discount_on": "Grand Total",
-			"discount_amount": get_discounted_amount(shopify_order),
-		})
-
-		if company:
-			so.update({
-				"company": company,
-				"status": "Draft"
-			})
-		so.flags.ignore_mandatory = True
-		so.save(ignore_permissions=True)
-		so.submit()
-
-	else:
-		so = frappe.get_doc("Sales Order", so)
-
-	frappe.db.commit()
-	return so
-
-def create_sales_invoice(shopify_order, shopify_settings, so, old_order_sync=False):
-	if not frappe.db.get_value("Sales Invoice", {"shopify_order_id": shopify_order.get("id")}, "name")\
-		and so.docstatus==1 and not so.per_billed and cint(shopify_settings.sync_sales_invoice):
-
-		if old_order_sync:
-			posting_date = getdate(shopify_order.get('created_at'))
-		else:
-			posting_date = nowdate()
-
-		si = make_sales_invoice(so.name, ignore_permissions=True)
-		si.shopify_order_id = shopify_order.get("id")
-		si.shopify_order_number = shopify_order.get("name")
-		si.set_posting_time = 1
-		si.posting_date = posting_date
-		si.due_date = posting_date
-		si.naming_series = shopify_settings.sales_invoice_series or "SI-Shopify-"
-		si.flags.ignore_mandatory = True
-		set_cost_center(si.items, shopify_settings.cost_center)
-		si.insert(ignore_mandatory=True)
-		si.submit()
-		make_payament_entry_against_sales_invoice(si, shopify_settings, posting_date)
-		frappe.db.commit()
-
-def set_cost_center(items, cost_center):
-	for item in items:
-		item.cost_center = cost_center
-
-def make_payament_entry_against_sales_invoice(doc, shopify_settings, posting_date=None):
-	from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
-	payment_entry = get_payment_entry(doc.doctype, doc.name, bank_account=shopify_settings.cash_bank_account)
-	payment_entry.flags.ignore_mandatory = True
-	payment_entry.reference_no = doc.name
-	payment_entry.posting_date = posting_date or nowdate()
-	payment_entry.reference_date = posting_date or nowdate()
-	payment_entry.insert(ignore_permissions=True)
-	payment_entry.submit()
-
-def create_delivery_note(shopify_order, shopify_settings, so):
-	if not cint(shopify_settings.sync_delivery_note):
-		return
-
-	for fulfillment in shopify_order.get("fulfillments"):
-		if not frappe.db.get_value("Delivery Note", {"shopify_fulfillment_id": fulfillment.get("id")}, "name")\
-			and so.docstatus==1:
-
-			dn = make_delivery_note(so.name)
-			dn.shopify_order_id = fulfillment.get("order_id")
-			dn.shopify_order_number = shopify_order.get("name")
-			dn.set_posting_time = 1
-			dn.posting_date = getdate(fulfillment.get("created_at"))
-			dn.shopify_fulfillment_id = fulfillment.get("id")
-			dn.naming_series = shopify_settings.delivery_note_series or "DN-Shopify-"
-			dn.items = get_fulfillment_items(dn.items, fulfillment.get("line_items"), shopify_settings)
-			dn.flags.ignore_mandatory = True
-			dn.save()
-			dn.submit()
-			frappe.db.commit()
-
-def get_fulfillment_items(dn_items, fulfillment_items, shopify_settings):
-	return [dn_item.update({"qty": item.get("quantity")}) for item in fulfillment_items for dn_item in dn_items\
-			if get_item_code(item) == dn_item.item_code]
-
-def get_discounted_amount(order):
-	discounted_amount = 0.0
-	for discount in order.get("discount_codes"):
-		discounted_amount += flt(discount.get("amount"))
-	return discounted_amount
-
-def get_order_items(order_items, shopify_settings, delivery_date):
-	items = []
-	all_product_exists = True
-	product_not_exists = []
-
-	for shopify_item in order_items:
-		if not shopify_item.get('product_exists'):
-			all_product_exists = False
-			product_not_exists.append({'title':shopify_item.get('title'),
-				'shopify_order_id': shopify_item.get('id')})
-			continue
-
-		if all_product_exists:
-			item_code = get_item_code(shopify_item)
-			items.append({
-				"item_code": item_code,
-				"item_name": shopify_item.get("name"),
-				"rate": shopify_item.get("price"),
-				"delivery_date": delivery_date,
-				"qty": shopify_item.get("quantity"),
-				"stock_uom": shopify_item.get("uom") or _("Nos"),
-				"warehouse": shopify_settings.warehouse
-			})
-		else:
-			items = []
-
-	return items
-
-def get_item_code(shopify_item):
-	item_code = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("variant_id")}, "item_code")
-	if not item_code:
-		item_code = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("product_id")}, "item_code")
-	if not item_code:
-		item_code = frappe.db.get_value("Item", {"item_name": shopify_item.get("title")}, "item_code")
-
-	return item_code
-
-def get_order_taxes(shopify_order, shopify_settings):
-	taxes = []
-	for tax in shopify_order.get("tax_lines"):
-		taxes.append({
-			"charge_type": _("On Net Total"),
-			"account_head": get_tax_account_head(tax),
-			"description": "{0} - {1}%".format(tax.get("title"), tax.get("rate") * 100.0),
-			"rate": tax.get("rate") * 100.00,
-			"included_in_print_rate": 1 if shopify_order.get("taxes_included") else 0,
-			"cost_center": shopify_settings.cost_center
-		})
-
-	taxes = update_taxes_with_shipping_lines(taxes, shopify_order.get("shipping_lines"), shopify_settings)
-
-	return taxes
-
-def update_taxes_with_shipping_lines(taxes, shipping_lines, shopify_settings):
-	"""Shipping lines represents the shipping details,
-		each such shipping detail consists of a list of tax_lines"""
-	for shipping_charge in shipping_lines:
-		if shipping_charge.get("price"):
-			taxes.append({
-				"charge_type": _("Actual"),
-				"account_head": get_tax_account_head(shipping_charge),
-				"description": shipping_charge["title"],
-				"tax_amount": shipping_charge["price"],
-				"cost_center": shopify_settings.cost_center
-			})
-
-		for tax in shipping_charge.get("tax_lines"):
-			taxes.append({
-				"charge_type": _("Actual"),
-				"account_head": get_tax_account_head(tax),
-				"description": tax["title"],
-				"tax_amount": tax["price"],
-				"cost_center": shopify_settings.cost_center
-			})
-
-	return taxes
-
-def get_tax_account_head(tax):
-	tax_title = tax.get("title").encode("utf-8")
-
-	tax_account =  frappe.db.get_value("Shopify Tax Account", \
-		{"parent": "Shopify Settings", "shopify_tax": tax_title}, "tax_account")
-
-	if not tax_account:
-		frappe.throw(_("Tax Account not specified for Shopify Tax {0}").format(tax.get("title")))
-
-	return tax_account
-
-@frappe.whitelist(allow_guest=True)
-def sync_old_orders():
-	frappe.set_user('Administrator')
-	shopify_settings = frappe.get_doc('Shopify Settings')
-
-	if not shopify_settings.sync_missing_orders:
-		return
-
-	url = get_url(shopify_settings)
-	session = get_request_session()
-
-	try:
-		res = session.get(url, headers=get_header(shopify_settings))
-		res.raise_for_status()
-		orders = res.json()["orders"]
-
-		for order in orders:
-			if is_sync_complete(shopify_settings, order):
-				stop_sync(shopify_settings)
-				return
-
-			sync_sales_order(order=order, old_order_sync=True)
-			last_order_id = order.get('id')
-
-		if last_order_id:
-			shopify_settings.load_from_db()
-			shopify_settings.last_order_id = last_order_id
-			shopify_settings.save()
-			frappe.db.commit()
-
-	except Exception as e:
-		raise e
-
-def stop_sync(shopify_settings):
-	shopify_settings.sync_missing_orders = 0
-	shopify_settings.last_order_id = ''
-	shopify_settings.save()
-	frappe.db.commit()
-
-def get_url(shopify_settings):
-	last_order_id = shopify_settings.last_order_id
-
-	if not last_order_id:
-		if shopify_settings.sync_based_on == 'Date':
-			url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&created_at_min={0}&since_id=0".format(
-				get_datetime(shopify_settings.from_date)), shopify_settings)
-		else:
-			url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(
-				shopify_settings.from_order_id), shopify_settings)
-	else:
-		url = get_shopify_url("admin/api/2021-04/orders.json?limit=250&since_id={0}".format(last_order_id), shopify_settings)
-
-	return url
-
-def is_sync_complete(shopify_settings, order):
-	if shopify_settings.sync_based_on == 'Date':
-		return getdate(shopify_settings.to_date) < getdate(order.get('created_at'))
-	else:
-		return cstr(order.get('id')) == cstr(shopify_settings.to_order_id)
-
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
deleted file mode 100644
index d3fe7d2b..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Shopify Log', {
-	refresh: function(frm) {
-		if (frm.doc.request_data && frm.doc.status=='Error'){
-			frm.add_custom_button('Resync', function() {
-				frappe.call({
-					method:"erpnext.erpnext_integrations.doctype.shopify_log.shopify_log.resync",
-					args:{
-						method:frm.doc.method,
-						name: frm.doc.name,
-						request_data: frm.doc.request_data
-					},
-					callback: function(r){
-						frappe.msgprint(__("Order rescheduled for sync"))
-					}
-				})
-			}).addClass('btn-primary');
-		}
-	}
-});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json
deleted file mode 100644
index ab373ee..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.json
+++ /dev/null
@@ -1,268 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2016-03-14 10:02:06.227184", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "System", 
- "editable_grid": 0, 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "title", 
-   "fieldtype": "Data", 
-   "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": "Title", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Queued", 
-   "fieldname": "status", 
-   "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": "Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "method", 
-   "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": "Method", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "message", 
-   "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": "Message", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "traceback", 
-   "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": "Traceback", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "request_data", 
-   "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": "Request Data", 
-   "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
-  }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 1, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2018-04-20 16:23:36.862381", 
- "modified_by": "Administrator", 
- "module": "ERPNext Integrations", 
- "name": "Shopify Log", 
- "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": "Administrator", 
-   "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": "System Manager", 
-   "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": "title", 
- "track_changes": 0, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
deleted file mode 100644
index a2b6af9..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-import json
-from frappe.model.document import Document
-from erpnext.erpnext_integrations.utils import get_webhook_address
-
-class ShopifyLog(Document):
-	pass
-
-
-def make_shopify_log(status="Queued", exception=None, rollback=False):
-	# if name not provided by log calling method then fetch existing queued state log
-	make_new = False
-
-	if not frappe.flags.request_id:
-		make_new = True
-
-	if rollback:
-		frappe.db.rollback()
-
-	if make_new:
-		log = frappe.get_doc({"doctype":"Shopify Log"}).insert(ignore_permissions=True)
-	else:
-		log = log = frappe.get_doc("Shopify Log", frappe.flags.request_id)
-
-	log.message = get_message(exception)
-	log.traceback = frappe.get_traceback()
-	log.status = status
-	log.save(ignore_permissions=True)
-	frappe.db.commit()
-
-def get_message(exception):
-	message = None
-
-	if hasattr(exception, 'message'):
-		message = exception.message
-	elif hasattr(exception, '__str__'):
-		message = exception.__str__()
-	else:
-		message = "Something went wrong while syncing"
-	return message
-
-def dump_request_data(data, event="create/order"):
-	event_mapper = {
-		"orders/create": get_webhook_address(connector_name='shopify_connection', method="sync_sales_order", exclude_uri=True),
-		"orders/paid" : get_webhook_address(connector_name='shopify_connection', method="prepare_sales_invoice", exclude_uri=True),
-		"orders/fulfilled": get_webhook_address(connector_name='shopify_connection', method="prepare_delivery_note", exclude_uri=True)
-	}
-
-	log = frappe.get_doc({
-		"doctype": "Shopify Log",
-		"request_data": json.dumps(data, indent=1),
-		"method": event_mapper[event]
-	}).insert(ignore_permissions=True)
-
-	frappe.db.commit()
-	frappe.enqueue(method=event_mapper[event], queue='short', timeout=300, is_async=True,
-		**{"order": data, "request_id": log.name})
-
-@frappe.whitelist()
-def resync(method, name, request_data):
-	frappe.db.set_value("Shopify Log", name, "status", "Queued", update_modified=False)
-	frappe.enqueue(method=method, queue='short', timeout=300, is_async=True,
-		**{"order": json.loads(request_data), "request_id": name})
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js b/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js
deleted file mode 100644
index 0913ce4..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/shopify_log_list.js
+++ /dev/null
@@ -1,12 +0,0 @@
-frappe.listview_settings['Shopify Log'] = {
-	add_fields: ["status"],
-	get_indicator: function(doc) {
-		if(doc.status==="Success"){
-			return [__("Success"), "green", "status,=,Success"];
-        } else if(doc.status ==="Error"){
-			return [__("Error"), "red", "status,=,Error"];
-        } else if(doc.status ==="Queued"){
-			return [__("Queued"), "orange", "status,=,Queued"];
-        }
-	}
-}
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js b/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js
deleted file mode 100644
index d22b6d5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shopify Log", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shopify Log
-		() => frappe.tests.make('Shopify Log', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py b/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py
deleted file mode 100644
index 5892e1d..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_log/test_shopify_log.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import frappe
-import unittest
-
-# test_records = frappe.get_test_records('Shopify Log')
-
-class TestShopifyLog(unittest.TestCase):
-	pass
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
deleted file mode 100644
index 1574795..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.js
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.provide("erpnext_integrations.shopify_settings");
-
-frappe.ui.form.on("Shopify Settings", "onload", function(frm){
-	frappe.call({
-		method:"erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings.get_series",
-		callback:function(r){
-			$.each(r.message, function(key, value){
-				set_field_options(key, value);
-			});
-		}
-	});
-	erpnext_integrations.shopify_settings.setup_queries(frm);
-})
-
-frappe.ui.form.on("Shopify Settings", "app_type", function(frm) {
-	frm.toggle_reqd("api_key", (frm.doc.app_type == "Private"));
-	frm.toggle_reqd("password", (frm.doc.app_type == "Private"));
-})
-
-frappe.ui.form.on("Shopify Settings", "refresh", function(frm){
-	if(!frm.doc.__islocal && frm.doc.enable_shopify === 1){
-		frm.toggle_reqd("price_list", true);
-		frm.toggle_reqd("warehouse", true);
-		frm.toggle_reqd("taxes", true);
-		frm.toggle_reqd("company", true);
-		frm.toggle_reqd("cost_center", true);
-		frm.toggle_reqd("cash_bank_account", true);
-		frm.toggle_reqd("sales_order_series", true);
-		frm.toggle_reqd("customer_group", true);
-		frm.toggle_reqd("shared_secret", true);
-
-		frm.toggle_reqd("sales_invoice_series", frm.doc.sync_sales_invoice);
-		frm.toggle_reqd("delivery_note_series", frm.doc.sync_delivery_note);
-
-	}
-})
-
-$.extend(erpnext_integrations.shopify_settings, {
-	setup_queries: function(frm) {
-		frm.fields_dict["warehouse"].get_query = function(doc) {
-			return {
-				filters:{
-					"company": doc.company,
-					"is_group": "No"
-				}
-			}
-		}
-
-		frm.fields_dict["taxes"].grid.get_field("tax_account").get_query = function(doc){
-			return {
-				"query": "erpnext.controllers.queries.tax_account_query",
-				"filters": {
-					"account_type": ["Tax", "Chargeable", "Expense Account"],
-					"company": doc.company
-				}
-			}
-		}
-
-		frm.fields_dict["cash_bank_account"].get_query = function(doc) {
-			return {
-				filters: [
-					["Account", "account_type", "in", ["Cash", "Bank"]],
-					["Account", "root_type", "=", "Asset"],
-					["Account", "is_group", "=",0],
-					["Account", "company", "=", doc.company]
-				]
-			}
-		}
-
-		frm.fields_dict["cost_center"].get_query = function(doc) {
-			return {
-				filters:{
-					"company": doc.company,
-					"is_group": "No"
-				}
-			}
-		}
-
-		frm.fields_dict["price_list"].get_query = function() {
-			return {
-				filters:{
-					"selling": 1
-				}
-			}
-		}
-	}
-})
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
deleted file mode 100644
index 308e7d1..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.json
+++ /dev/null
@@ -1,353 +0,0 @@
-{
- "actions": [],
- "creation": "2015-05-18 05:21:07.270859",
- "doctype": "DocType",
- "document_type": "System",
- "engine": "InnoDB",
- "field_order": [
-  "status_html",
-  "enable_shopify",
-  "app_type",
-  "column_break_4",
-  "last_sync_datetime",
-  "section_break_2",
-  "shopify_url",
-  "api_key",
-  "column_break_3",
-  "password",
-  "shared_secret",
-  "access_token",
-  "section_break_38",
-  "webhooks",
-  "section_break_15",
-  "default_customer",
-  "column_break_19",
-  "customer_group",
-  "company_dependent_settings",
-  "company",
-  "cash_bank_account",
-  "column_break_20",
-  "cost_center",
-  "erp_settings",
-  "price_list",
-  "update_price_in_erpnext_price_list",
-  "column_break_26",
-  "warehouse",
-  "section_break_25",
-  "sales_order_series",
-  "column_break_27",
-  "sync_delivery_note",
-  "delivery_note_series",
-  "sync_sales_invoice",
-  "sales_invoice_series",
-  "section_break_22",
-  "html_16",
-  "taxes",
-  "syncing_details_section",
-  "sync_missing_orders",
-  "sync_based_on",
-  "column_break_41",
-  "from_date",
-  "to_date",
-  "from_order_id",
-  "to_order_id",
-  "last_order_id"
- ],
- "fields": [
-  {
-   "fieldname": "status_html",
-   "fieldtype": "HTML",
-   "label": "status html",
-   "read_only": 1
-  },
-  {
-   "default": "0",
-   "fieldname": "enable_shopify",
-   "fieldtype": "Check",
-   "label": "Enable Shopify"
-  },
-  {
-   "default": "Private",
-   "fieldname": "app_type",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "App Type",
-   "read_only": 1,
-   "reqd": 1
-  },
-  {
-   "fieldname": "column_break_4",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "last_sync_datetime",
-   "fieldtype": "Datetime",
-   "label": "Last Sync Datetime",
-   "read_only": 1
-  },
-  {
-   "fieldname": "section_break_2",
-   "fieldtype": "Section Break"
-  },
-  {
-   "description": "eg: frappe.myshopify.com",
-   "fieldname": "shopify_url",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Shop URL",
-   "reqd": 1
-  },
-  {
-   "depends_on": "eval:doc.app_type==\"Private\"",
-   "fieldname": "api_key",
-   "fieldtype": "Data",
-   "label": "API Key"
-  },
-  {
-   "fieldname": "column_break_3",
-   "fieldtype": "Column Break"
-  },
-  {
-   "depends_on": "eval:doc.app_type==\"Private\"",
-   "fieldname": "password",
-   "fieldtype": "Password",
-   "label": "Password"
-  },
-  {
-   "fieldname": "shared_secret",
-   "fieldtype": "Data",
-   "label": "Shared secret"
-  },
-  {
-   "fieldname": "access_token",
-   "fieldtype": "Data",
-   "hidden": 1,
-   "label": "Access Token",
-   "read_only": 1
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "section_break_38",
-   "fieldtype": "Section Break",
-   "label": "Webhooks Details"
-  },
-  {
-   "fieldname": "webhooks",
-   "fieldtype": "Table",
-   "label": "Webhooks",
-   "options": "Shopify Webhook Detail",
-   "read_only": 1
-  },
-  {
-   "fieldname": "section_break_15",
-   "fieldtype": "Section Break",
-   "label": "Customer Settings"
-  },
-  {
-   "description": "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order",
-   "fieldname": "default_customer",
-   "fieldtype": "Link",
-   "label": "Default Customer",
-   "options": "Customer"
-  },
-  {
-   "fieldname": "column_break_19",
-   "fieldtype": "Column Break"
-  },
-  {
-   "description": "Customer Group will set to selected group while syncing customers from Shopify",
-   "fieldname": "customer_group",
-   "fieldtype": "Link",
-   "label": "Customer Group",
-   "options": "Customer Group"
-  },
-  {
-   "fieldname": "company_dependent_settings",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "company",
-   "fieldtype": "Link",
-   "label": "For Company",
-   "options": "Company"
-  },
-  {
-   "description": "Cash Account will used for Sales Invoice creation",
-   "fieldname": "cash_bank_account",
-   "fieldtype": "Link",
-   "label": "Cash/Bank Account",
-   "options": "Account"
-  },
-  {
-   "fieldname": "column_break_20",
-   "fieldtype": "Column Break"
-  },
-  {
-   "fieldname": "cost_center",
-   "fieldtype": "Link",
-   "label": "Cost Center",
-   "options": "Cost Center"
-  },
-  {
-   "fieldname": "erp_settings",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "price_list",
-   "fieldtype": "Link",
-   "label": "Price List",
-   "options": "Price List"
-  },
-  {
-   "default": "0",
-   "fieldname": "update_price_in_erpnext_price_list",
-   "fieldtype": "Check",
-   "label": "Update Price from Shopify To ERPNext Price List"
-  },
-  {
-   "fieldname": "column_break_26",
-   "fieldtype": "Column Break"
-  },
-  {
-   "description": "Default Warehouse to to create Sales Order and Delivery Note",
-   "fieldname": "warehouse",
-   "fieldtype": "Link",
-   "label": "Warehouse",
-   "options": "Warehouse"
-  },
-  {
-   "fieldname": "section_break_25",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "sales_order_series",
-   "fieldtype": "Select",
-   "label": "Sales Order Series"
-  },
-  {
-   "fieldname": "column_break_27",
-   "fieldtype": "Column Break"
-  },
-  {
-   "default": "0",
-   "fieldname": "sync_delivery_note",
-   "fieldtype": "Check",
-   "label": "Import Delivery Notes from Shopify on Shipment"
-  },
-  {
-   "depends_on": "eval:doc.sync_delivery_note==1",
-   "fieldname": "delivery_note_series",
-   "fieldtype": "Select",
-   "label": "Delivery Note Series"
-  },
-  {
-   "default": "0",
-   "fieldname": "sync_sales_invoice",
-   "fieldtype": "Check",
-   "label": "Import Sales Invoice from Shopify if Payment is marked"
-  },
-  {
-   "depends_on": "eval:doc.sync_sales_invoice==1",
-   "fieldname": "sales_invoice_series",
-   "fieldtype": "Select",
-   "label": "Sales Invoice Series"
-  },
-  {
-   "fieldname": "section_break_22",
-   "fieldtype": "Section Break"
-  },
-  {
-   "fieldname": "html_16",
-   "fieldtype": "HTML",
-   "options": "Map Shopify Taxes / Shipping Charges to ERPNext Account"
-  },
-  {
-   "fieldname": "taxes",
-   "fieldtype": "Table",
-   "label": "Shopify Tax Account",
-   "options": "Shopify Tax Account"
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "syncing_details_section",
-   "fieldtype": "Section Break",
-   "label": "Syncing Missing Orders"
-  },
-  {
-   "depends_on": "eval:doc.sync_missing_orders",
-   "fieldname": "last_order_id",
-   "fieldtype": "Data",
-   "label": "Last Order Id",
-   "read_only": 1
-  },
-  {
-   "fieldname": "column_break_41",
-   "fieldtype": "Column Break"
-  },
-  {
-   "default": "0",
-   "description": "On checking this Order from the ",
-   "fieldname": "sync_missing_orders",
-   "fieldtype": "Check",
-   "label": "Sync Missing Old Shopify Orders"
-  },
-  {
-   "depends_on": "eval:doc.sync_missing_orders",
-   "fieldname": "sync_based_on",
-   "fieldtype": "Select",
-   "label": "Sync Based On",
-   "mandatory_depends_on": "eval:doc.sync_missing_orders",
-   "options": "\nDate\nShopify Order Id"
-  },
-  {
-   "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
-   "fieldname": "from_date",
-   "fieldtype": "Date",
-   "label": "From Date",
-   "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
-  },
-  {
-   "depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders",
-   "fieldname": "to_date",
-   "fieldtype": "Date",
-   "label": "To Date",
-   "mandatory_depends_on": "eval:doc.sync_based_on == 'Date' && doc.sync_missing_orders"
-  },
-  {
-   "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
-   "fieldname": "from_order_id",
-   "fieldtype": "Data",
-   "label": "From Order Id",
-   "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
-  },
-  {
-   "depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders",
-   "fieldname": "to_order_id",
-   "fieldtype": "Data",
-   "label": "To Order Id",
-   "mandatory_depends_on": "eval:doc.sync_based_on == 'Shopify Order Id' && doc.sync_missing_orders"
-  }
- ],
- "issingle": 1,
- "links": [],
- "modified": "2021-03-02 17:35:41.953317",
- "modified_by": "Administrator",
- "module": "ERPNext Integrations",
- "name": "Shopify 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/erpnext_integrations/doctype/shopify_settings/shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
deleted file mode 100644
index 381c5e5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/shopify_settings.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-import json
-from frappe import _
-from frappe.model.document import Document
-from frappe.utils import get_request_session
-from requests.exceptions import HTTPError
-from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
-from erpnext.erpnext_integrations.utils import get_webhook_address
-from erpnext.erpnext_integrations.doctype.shopify_log.shopify_log import make_shopify_log
-
-class ShopifySettings(Document):
-	def validate(self):
-		if self.enable_shopify == 1:
-			setup_custom_fields()
-			self.validate_access_credentials()
-			self.register_webhooks()
-		else:
-			self.unregister_webhooks()
-
-	def validate_access_credentials(self):
-		if not (self.get_password(raise_exception=False) and self.api_key and self.shopify_url):
-			frappe.msgprint(_("Missing value for Password, API Key or Shopify URL"), raise_exception=frappe.ValidationError)
-
-	def register_webhooks(self):
-		webhooks = ["orders/create", "orders/paid", "orders/fulfilled"]
-		# url = get_shopify_url('admin/webhooks.json', self)
-		created_webhooks = [d.method for d in self.webhooks]
-		url = get_shopify_url('admin/api/2021-04/webhooks.json', self)
-		for method in webhooks:
-			session = get_request_session()
-			try:
-				res = session.post(url, data=json.dumps({
-					"webhook": {
-						"topic": method,
-						"address": get_webhook_address(connector_name='shopify_connection', method='store_request_data', force_https=True),
-						"format": "json"
-						}
-					}), headers=get_header(self))
-				res.raise_for_status()
-				self.update_webhook_table(method, res.json())
-
-			except HTTPError as e:
-				error_message = res.json().get('errors', e)
-				make_shopify_log(status="Warning", exception=error_message, rollback=True)
-
-			except Exception as e:
-				make_shopify_log(status="Warning", exception=e, rollback=True)
-
-	def unregister_webhooks(self):
-		session = get_request_session()
-		deleted_webhooks = []
-
-		for d in self.webhooks:
-			url = get_shopify_url('admin/api/2021-04/webhooks/{0}.json'.format(d.webhook_id), self)
-			try:
-				res = session.delete(url, headers=get_header(self))
-				res.raise_for_status()
-				deleted_webhooks.append(d)
-
-			except HTTPError as e:
-				error_message = res.json().get('errors', e)
-				make_shopify_log(status="Warning", exception=error_message, rollback=True)
-
-			except Exception as e:
-				frappe.log_error(message=e, title='Shopify Webhooks Issue')
-
-		for d in deleted_webhooks:
-			self.remove(d)
-
-	def update_webhook_table(self, method, res):
-		self.append("webhooks", {
-			"webhook_id": res['webhook']['id'],
-			"method": method
-		})
-
-def get_shopify_url(path, settings):
-	if settings.app_type == "Private":
-		return 'https://{}:{}@{}/{}'.format(settings.api_key, settings.get_password('password'), settings.shopify_url, path)
-	else:
-		return 'https://{}/{}'.format(settings.shopify_url, path)
-
-def get_header(settings):
-	header = {'Content-Type': 'application/json'}
-
-	return header
-
-@frappe.whitelist()
-def get_series():
-	return {
-		"sales_order_series" : frappe.get_meta("Sales Order").get_options("naming_series") or "SO-Shopify-",
-		"sales_invoice_series" : frappe.get_meta("Sales Invoice").get_options("naming_series")  or "SI-Shopify-",
-		"delivery_note_series" : frappe.get_meta("Delivery Note").get_options("naming_series")  or "DN-Shopify-"
-	}
-
-def setup_custom_fields():
-	custom_fields = {
-		"Customer": [
-			dict(fieldname='shopify_customer_id', label='Shopify Customer Id',
-				fieldtype='Data', insert_after='series', read_only=1, print_hide=1)
-		],
-		"Supplier": [
-			dict(fieldname='shopify_supplier_id', label='Shopify Supplier Id',
-				fieldtype='Data', insert_after='supplier_name', read_only=1, print_hide=1)
-		],
-		"Address": [
-			dict(fieldname='shopify_address_id', label='Shopify Address Id',
-				fieldtype='Data', insert_after='fax', read_only=1, print_hide=1)
-		],
-		"Item": [
-			dict(fieldname='shopify_variant_id', label='Shopify Variant Id',
-				fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
-			dict(fieldname='shopify_product_id', label='Shopify Product Id',
-				fieldtype='Data', insert_after='item_code', read_only=1, print_hide=1),
-			dict(fieldname='shopify_description', label='Shopify Description',
-				fieldtype='Text Editor', insert_after='description', read_only=1, print_hide=1)
-		],
-		"Sales Order": [
-			dict(fieldname='shopify_order_id', label='Shopify Order Id',
-				fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
-			dict(fieldname='shopify_order_number', label='Shopify Order Number',
-				fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
-		],
-		"Delivery Note":[
-			dict(fieldname='shopify_order_id', label='Shopify Order Id',
-				fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
-			dict(fieldname='shopify_order_number', label='Shopify Order Number',
-				fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1),
-			dict(fieldname='shopify_fulfillment_id', label='Shopify Fulfillment Id',
-				fieldtype='Data', insert_after='title', read_only=1, print_hide=1)
-		],
-		"Sales Invoice": [
-			dict(fieldname='shopify_order_id', label='Shopify Order Id',
-				fieldtype='Data', insert_after='title', read_only=1, print_hide=1),
-			dict(fieldname='shopify_order_number', label='Shopify Order Number',
-				fieldtype='Data', insert_after='shopify_order_id', read_only=1, print_hide=1)
-		]
-	}
-
-	create_custom_fields(custom_fields)
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
deleted file mode 100644
index 2af57f4..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_customer.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-
-def create_customer(shopify_customer, shopify_settings):
-	import frappe.utils.nestedset
-
-	cust_name = (shopify_customer.get("first_name") + " " + (shopify_customer.get("last_name") \
-		and  shopify_customer.get("last_name") or "")) if shopify_customer.get("first_name")\
-		else shopify_customer.get("email")
-
-	try:
-		customer = frappe.get_doc({
-			"doctype": "Customer",
-			"name": shopify_customer.get("id"),
-			"customer_name" : cust_name,
-			"shopify_customer_id": shopify_customer.get("id"),
-			"sync_with_shopify": 1,
-			"customer_group": shopify_settings.customer_group,
-			"territory": frappe.utils.nestedset.get_root_of("Territory"),
-			"customer_type": _("Individual")
-		})
-		customer.flags.ignore_mandatory = True
-		customer.insert(ignore_permissions=True)
-
-		if customer:
-			create_customer_address(customer, shopify_customer)
-
-		frappe.db.commit()
-
-	except Exception as e:
-		raise e
-
-def create_customer_address(customer, shopify_customer):
-	addresses = shopify_customer.get("addresses", [])
-
-	if not addresses and "default_address" in shopify_customer:
-		addresses.append(shopify_customer["default_address"])
-
-	for i, address in enumerate(addresses):
-		address_title, address_type = get_address_title_and_type(customer.customer_name, i)
-		try :
-			frappe.get_doc({
-				"doctype": "Address",
-				"shopify_address_id": address.get("id"),
-				"address_title": address_title,
-				"address_type": address_type,
-				"address_line1": address.get("address1") or "Address 1",
-				"address_line2": address.get("address2"),
-				"city": address.get("city") or "City",
-				"state": address.get("province"),
-				"pincode": address.get("zip"),
-				"country": address.get("country"),
-				"phone": address.get("phone"),
-				"email_id": shopify_customer.get("email"),
-				"links": [{
-					"link_doctype": "Customer",
-					"link_name": customer.name
-				}]
-			}).insert(ignore_mandatory=True)
-
-		except Exception as e:
-			raise e
-
-def get_address_title_and_type(customer_name, index):
-	address_type = _("Billing")
-	address_title = customer_name
-	if frappe.db.get_value("Address", "{0}-{1}".format(customer_name.strip(), address_type)):
-		address_title = "{0}-{1}".format(customer_name.strip(), index)
-
-	return address_title, address_type
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py b/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
deleted file mode 100644
index 16efb6c..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/sync_product.py
+++ /dev/null
@@ -1,309 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe import _
-from erpnext import get_default_company
-from frappe.utils import cstr, cint, get_request_session
-from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import get_shopify_url, get_header
-
-shopify_variants_attr_list = ["option1", "option2", "option3"]
-
-def sync_item_from_shopify(shopify_settings, item):
-	url = get_shopify_url("admin/api/2021-04/products/{0}.json".format(item.get("product_id")), shopify_settings)
-	session = get_request_session()
-
-	try:
-		res = session.get(url, headers=get_header(shopify_settings))
-		res.raise_for_status()
-
-		shopify_item = res.json()["product"]
-		make_item(shopify_settings.warehouse, shopify_item)
-	except Exception as e:
-		raise e
-
-def make_item(warehouse, shopify_item):
-	add_item_weight(shopify_item)
-
-	if has_variants(shopify_item):
-		attributes = create_attribute(shopify_item)
-		create_item(shopify_item, warehouse, 1, attributes)
-		create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list)
-
-	else:
-		shopify_item["variant_id"] = shopify_item['variants'][0]["id"]
-		create_item(shopify_item, warehouse)
-
-def add_item_weight(shopify_item):
-	shopify_item["weight"] = shopify_item['variants'][0]["weight"]
-	shopify_item["weight_unit"] = shopify_item['variants'][0]["weight_unit"]
-
-def has_variants(shopify_item):
-	if len(shopify_item.get("options")) >= 1 and "Default Title" not in shopify_item.get("options")[0]["values"]:
-		return True
-	return False
-
-def create_attribute(shopify_item):
-	attribute = []
-	# shopify item dict
-	for attr in shopify_item.get('options'):
-		if not frappe.db.get_value("Item Attribute", attr.get("name"), "name"):
-			frappe.get_doc({
-				"doctype": "Item Attribute",
-				"attribute_name": attr.get("name"),
-				"item_attribute_values": [
-					{
-						"attribute_value": attr_value,
-						"abbr":attr_value
-					}
-					for attr_value in attr.get("values")
-				]
-			}).insert()
-			attribute.append({"attribute": attr.get("name")})
-
-		else:
-			# check for attribute values
-			item_attr = frappe.get_doc("Item Attribute", attr.get("name"))
-			if not item_attr.numeric_values:
-				set_new_attribute_values(item_attr, attr.get("values"))
-				item_attr.save()
-				attribute.append({"attribute": attr.get("name")})
-
-			else:
-				attribute.append({
-					"attribute": attr.get("name"),
-					"from_range": item_attr.get("from_range"),
-					"to_range": item_attr.get("to_range"),
-					"increment": item_attr.get("increment"),
-					"numeric_values": item_attr.get("numeric_values")
-				})
-
-	return attribute
-
-def set_new_attribute_values(item_attr, values):
-	for attr_value in values:
-		if not any((d.abbr.lower() == attr_value.lower() or d.attribute_value.lower() == attr_value.lower())\
-		for d in item_attr.item_attribute_values):
-			item_attr.append("item_attribute_values", {
-				"attribute_value": attr_value,
-				"abbr": attr_value
-			})
-
-def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_of=None):
-	item_dict = {
-		"doctype": "Item",
-		"shopify_product_id": shopify_item.get("id"),
-		"shopify_variant_id": shopify_item.get("variant_id"),
-		"variant_of": variant_of,
-		"sync_with_shopify": 1,
-		"is_stock_item": 1,
-		"item_code": cstr(shopify_item.get("item_code")) or cstr(shopify_item.get("id")),
-		"item_name": shopify_item.get("title", '').strip(),
-		"description": shopify_item.get("body_html") or shopify_item.get("title"),
-		"shopify_description": shopify_item.get("body_html") or shopify_item.get("title"),
-		"item_group": get_item_group(shopify_item.get("product_type")),
-		"has_variants": has_variant,
-		"attributes":attributes or [],
-		"stock_uom": shopify_item.get("uom") or _("Nos"),
-		"stock_keeping_unit": shopify_item.get("sku") or get_sku(shopify_item),
-		"default_warehouse": warehouse,
-		"image": get_item_image(shopify_item),
-		"weight_uom": shopify_item.get("weight_unit"),
-		"weight_per_unit": shopify_item.get("weight"),
-		"default_supplier": get_supplier(shopify_item),
-		"item_defaults": [
-			{
-				"company": get_default_company()
-			}
-		]
-	}
-
-	if not is_item_exists(item_dict, attributes, variant_of=variant_of):
-		item_details = get_item_details(shopify_item)
-		name = ''
-
-		if not item_details:
-			new_item = frappe.get_doc(item_dict)
-			new_item.insert(ignore_permissions=True, ignore_mandatory=True)
-			name = new_item.name
-
-		if not name:
-			name = item_details.name
-
-		if not has_variant:
-			add_to_price_list(shopify_item, name)
-
-		frappe.db.commit()
-
-def create_item_variants(shopify_item, warehouse, attributes, shopify_variants_attr_list):
-	template_item = frappe.db.get_value("Item", filters={"shopify_product_id": shopify_item.get("id")},
-		fieldname=["name", "stock_uom"], as_dict=True)
-
-	if template_item:
-		for variant in shopify_item.get("variants"):
-			shopify_item_variant = {
-				"id" : variant.get("id"),
-				"item_code": variant.get("id"),
-				"title": variant.get("title"),
-				"product_type": shopify_item.get("product_type"),
-				"sku": variant.get("sku"),
-				"uom": template_item.stock_uom or _("Nos"),
-				"item_price": variant.get("price"),
-				"variant_id": variant.get("id"),
-				"weight_unit": variant.get("weight_unit"),
-				"weight": variant.get("weight")
-			}
-
-			for i, variant_attr in enumerate(shopify_variants_attr_list):
-				if variant.get(variant_attr):
-					attributes[i].update({"attribute_value": get_attribute_value(variant.get(variant_attr), attributes[i])})
-			create_item(shopify_item_variant, warehouse, 0, attributes, template_item.name)
-
-def get_attribute_value(variant_attr_val, attribute):
-	attribute_value = frappe.db.sql("""select attribute_value from `tabItem Attribute Value`
-		where parent = %s and (abbr = %s or attribute_value = %s)""", (attribute["attribute"], variant_attr_val,
-		variant_attr_val), as_list=1)
-	return attribute_value[0][0] if len(attribute_value)>0 else cint(variant_attr_val)
-
-def get_item_group(product_type=None):
-	import frappe.utils.nestedset
-	parent_item_group = frappe.utils.nestedset.get_root_of("Item Group")
-
-	if product_type:
-		if not frappe.db.get_value("Item Group", product_type, "name"):
-			item_group = frappe.get_doc({
-				"doctype": "Item Group",
-				"item_group_name": product_type,
-				"parent_item_group": parent_item_group,
-				"is_group": "No"
-			}).insert()
-			return item_group.name
-		else:
-			return product_type
-	else:
-		return parent_item_group
-
-
-def get_sku(item):
-	if item.get("variants"):
-		return item.get("variants")[0].get("sku")
-	return ""
-
-def add_to_price_list(item, name):
-	shopify_settings = frappe.db.get_value("Shopify Settings", None, ["price_list", "update_price_in_erpnext_price_list"], as_dict=1)
-	if not shopify_settings.update_price_in_erpnext_price_list:
-		return
-
-	item_price_name = frappe.db.get_value("Item Price",
-		{"item_code": name, "price_list": shopify_settings.price_list}, "name")
-
-	if not item_price_name:
-		frappe.get_doc({
-			"doctype": "Item Price",
-			"price_list": shopify_settings.price_list,
-			"item_code": name,
-			"price_list_rate": item.get("item_price") or item.get("variants")[0].get("price")
-		}).insert()
-	else:
-		item_rate = frappe.get_doc("Item Price", item_price_name)
-		item_rate.price_list_rate = item.get("item_price") or item.get("variants")[0].get("price")
-		item_rate.save()
-
-def get_item_image(shopify_item):
-	if shopify_item.get("image"):
-		return shopify_item.get("image").get("src")
-	return None
-
-def get_supplier(shopify_item):
-	if shopify_item.get("vendor"):
-		supplier = frappe.db.sql("""select name from tabSupplier
-			where name = %s or shopify_supplier_id = %s """, (shopify_item.get("vendor"),
-			shopify_item.get("vendor").lower()), as_list=1)
-
-		if not supplier:
-			supplier = frappe.get_doc({
-				"doctype": "Supplier",
-				"supplier_name": shopify_item.get("vendor"),
-				"shopify_supplier_id": shopify_item.get("vendor").lower(),
-				"supplier_group": get_supplier_group()
-			}).insert()
-			return supplier.name
-		else:
-			return shopify_item.get("vendor")
-	else:
-		return ""
-
-def get_supplier_group():
-	supplier_group = frappe.db.get_value("Supplier Group", _("Shopify Supplier"))
-	if not supplier_group:
-		supplier_group = frappe.get_doc({
-			"doctype": "Supplier Group",
-			"supplier_group_name": _("Shopify Supplier")
-		}).insert()
-		return supplier_group.name
-	return supplier_group
-
-def get_item_details(shopify_item):
-	item_details = {}
-
-	item_details = frappe.db.get_value("Item", {"shopify_product_id": shopify_item.get("id")},
-		["name", "stock_uom", "item_name"], as_dict=1)
-
-	if item_details:
-		return item_details
-
-	else:
-		item_details = frappe.db.get_value("Item", {"shopify_variant_id": shopify_item.get("id")},
-			["name", "stock_uom", "item_name"], as_dict=1)
-		return item_details
-
-def is_item_exists(shopify_item, attributes=None, variant_of=None):
-	if variant_of:
-		name = variant_of
-	else:
-		name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")})
-
-	if name:
-		item = frappe.get_doc("Item", name)
-		item.flags.ignore_mandatory=True
-
-		if not variant_of and not item.shopify_product_id:
-			item.shopify_product_id = shopify_item.get("shopify_product_id")
-			item.shopify_variant_id = shopify_item.get("shopify_variant_id")
-			item.save()
-			return True
-
-		if item.shopify_product_id and attributes and attributes[0].get("attribute_value"):
-			if not variant_of:
-				variant_of = frappe.db.get_value("Item",
-					{"shopify_product_id": item.shopify_product_id}, "variant_of")
-
-			# create conditions for all item attributes,
-			# as we are putting condition basis on OR it will fetch all items matching either of conditions
-			# thus comparing matching conditions with len(attributes)
-			# which will give exact matching variant item.
-
-			conditions = ["(iv.attribute='{0}' and iv.attribute_value = '{1}')"\
-				.format(attr.get("attribute"), attr.get("attribute_value")) for attr in attributes]
-
-			conditions = "( {0} ) and iv.parent = it.name ) = {1}".format(" or ".join(conditions), len(attributes))
-
-			parent = frappe.db.sql(""" select * from tabItem it where
-				( select count(*) from `tabItem Variant Attribute` iv
-					where {conditions} and it.variant_of = %s """.format(conditions=conditions) ,
-				variant_of, as_list=1)
-
-			if parent:
-				variant = frappe.get_doc("Item", parent[0][0])
-				variant.flags.ignore_mandatory = True
-
-				variant.shopify_product_id = shopify_item.get("shopify_product_id")
-				variant.shopify_variant_id = shopify_item.get("shopify_variant_id")
-				variant.save()
-			return False
-
-		if item.shopify_product_id and item.shopify_product_id != shopify_item.get("shopify_product_id"):
-			return False
-
-		return True
-
-	else:
-		return False
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json
deleted file mode 100644
index db6c3d5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json
+++ /dev/null
@@ -1,527 +0,0 @@
-[
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Print Settings", 
-  "fieldname": "compact_item_print", 
-  "fieldtype": "Check", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "with_letterhead", 
-  "label": "Compact Item Print", 
-  "modified": "2016-06-06 15:18:17.025602", 
-  "name": "Print Settings-compact_item_print", 
-  "no_copy": 0, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 0, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 0, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Customer", 
-  "fieldname": "shopify_customer_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "naming_series", 
-  "label": "Shopify Customer Id", 
-  "modified": "2016-01-15 17:25:28.991818", 
-  "name": "Customer-shopify_customer_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Address", 
-  "fieldname": "shopify_address_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "fax", 
-  "label": "Shopify Address Id", 
-  "modified": "2016-01-15 17:50:52.213743", 
-  "name": "Address-shopify_address_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Sales Order", 
-  "fieldname": "shopify_order_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "title", 
-  "label": "Shopify Order Id", 
-  "modified": "2016-01-18 09:55:50.764524", 
-  "name": "Sales Order-shopify_order_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Item", 
-  "fieldname": "shopify_product_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "item_code", 
-  "label": "Shopify Product Id", 
-  "modified": "2016-01-19 15:44:16.132952", 
-  "name": "Item-shopify_product_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Sales Invoice", 
-  "fieldname": "shopify_order_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "naming_series", 
-  "label": "Shopify Order Id", 
-  "modified": "2016-01-19 16:30:12.261797", 
-  "name": "Sales Invoice-shopify_order_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Delivery Note", 
-  "fieldname": "shopify_order_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "title", 
-  "label": "Shopify Order Id", 
-  "modified": "2016-01-19 16:30:31.201198", 
-  "name": "Delivery Note-shopify_order_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Item", 
-  "fieldname": "stock_keeping_unit", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "stock_uom", 
-  "label": "Stock Keeping Unit", 
-  "modified": "2015-11-10 09:29:10.854943", 
-  "name": "Item-stock_keeping_unit", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 0, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": "0", 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Item", 
-  "fieldname": "sync_with_shopify", 
-  "fieldtype": "Check", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "is_stock_item", 
-  "label": "Sync With Shopify", 
-  "modified": "2015-10-12 15:54:31.997714", 
-  "name": "Item-sync_with_shopify", 
-  "no_copy": 0, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 0, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 0, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Customer", 
-  "fieldname": "sync_with_shopify", 
-  "fieldtype": "Check", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "is_frozen", 
-  "label": "Sync With Shopify", 
-  "modified": "2015-10-01 17:31:55.758826", 
-  "name": "Customer-sync_with_shopify", 
-  "no_copy": 0, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 0, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 0, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Item", 
-  "fieldname": "shopify_variant_id", 
-  "fieldtype": "Data", 
-  "hidden": 1, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "item_code", 
-  "label": "Variant Id", 
-  "modified": "2015-11-09 18:26:50.825858", 
-  "name": "Item-shopify_variant_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Item", 
-  "fieldname": "sync_qty_with_shopify", 
-  "fieldtype": "Check", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "item_code", 
-  "label": "Sync Quantity With Shopify", 
-  "modified": "2015-12-29 08:37:46.183295", 
-  "name": "Item-sync_qty_with_shopify", 
-  "no_copy": 0, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 0, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 0, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Delivery Note", 
-  "fieldname": "shopify_fulfillment_id", 
-  "fieldtype": "Data", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "title", 
-  "label": "Shopify Fulfillment Id", 
-  "modified": "2016-01-20 23:50:35.609543", 
-  "name": "Delivery Note-shopify_fulfillment_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Supplier", 
-  "fieldname": "shopify_supplier_id", 
-  "fieldtype": "Data", 
-  "hidden": 1, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "supplier_name", 
-  "label": "Shopify Supplier Id", 
-  "modified": "2016-02-01 15:41:25.818306", 
-  "name": "Supplier-shopify_supplier_id", 
-  "no_copy": 1, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 1, 
-  "report_hide": 0, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }, 
- {
-  "allow_on_submit": 0, 
-  "collapsible": 0, 
-  "collapsible_depends_on": null, 
-  "default": null, 
-  "depends_on": null, 
-  "description": null, 
-  "docstatus": 0, 
-  "doctype": "Custom Field", 
-  "dt": "Item", 
-  "fieldname": "shopify_description", 
-  "fieldtype": "Text Editor", 
-  "hidden": 0, 
-  "ignore_user_permissions": 0, 
-  "ignore_xss_filter": 0, 
-  "in_filter": 0, 
-  "in_list_view": 0, 
-  "insert_after": "section_break_11", 
-  "label": "shopify_description", 
-  "modified": "2016-06-15 12:15:36.325581", 
-  "name": "Item-shopify_description", 
-  "no_copy": 0, 
-  "options": null, 
-  "permlevel": 0, 
-  "precision": "", 
-  "print_hide": 1, 
-  "print_hide_if_no_value": 0, 
-  "print_width": null, 
-  "read_only": 0, 
-  "report_hide": 1, 
-  "reqd": 0, 
-  "search_index": 0, 
-  "unique": 0, 
-  "width": null
- }
-]
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json
deleted file mode 100644
index e91ce9a..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_customer.json
+++ /dev/null
@@ -1,59 +0,0 @@
-{
-    "customer": {
-        "id": 2324518599,
-        "email": "andrew@wyatt.co.in",
-        "accepts_marketing": false,
-        "created_at": "2016-01-20T17:18:35+05:30",
-        "updated_at": "2016-01-20T17:22:23+05:30",
-        "first_name": "Andrew",
-        "last_name": "Wyatt",
-        "orders_count": 0,
-        "state": "disabled",
-        "total_spent": "0.00",
-        "last_order_id": null,
-        "note": "",
-        "verified_email": true,
-        "multipass_identifier": null,
-        "tax_exempt": false,
-        "tags": "",
-        "last_order_name": null,
-        "default_address": {
-            "id": 2476804295,
-            "first_name": "Andrew",
-            "last_name": "Wyatt",
-            "company": "Wyatt Inc.",
-            "address1": "B-11, Betahouse",
-            "address2": "Street 11, Sector 52",
-            "city": "Manhattan",
-            "province": "New York",
-            "country": "United States",
-            "zip": "10027",
-            "phone": "145-112211",
-            "name": "Andrew Wyatt",
-            "province_code": "NY",
-            "country_code": "US",
-            "country_name": "United States",
-            "default": true
-        },
-        "addresses": [
-            {
-                "id": 2476804295,
-                "first_name": "Andrew",
-                "last_name": "Wyatt",
-                "company": "Wyatt Inc.",
-                "address1": "B-11, Betahouse",
-                "address2": "Street 11, Sector 52",
-                "city": "Manhattan",
-                "province": "New York",
-                "country": "United States",
-                "zip": "10027",
-                "phone": "145-112211",
-                "name": "Andrew Wyatt",
-                "province_code": "NY",
-                "country_code": "US",
-                "country_name": "United States",
-                "default": true
-            }
-        ]
-    }
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json
deleted file mode 100644
index 296dede..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_item.json
+++ /dev/null
@@ -1,125 +0,0 @@
-{
-	"product": {
-		"id": 4059739520,
-		"title": "Shopify Test Item",
-		"body_html": "<div>Hold back Spin Medallion-Set of 2</div>\n<div></div>\n<div>Finish: Plated/ Powder Coated</div>\n<div>Material: Iron</div>\n<div>Color Finish: Satin Silver, Brown Oil Rubbed, Roman Bronze</div>\n<div>Qty: 1 Set</div>",
-		"vendor": "Boa casa",
-		"product_type": "Curtain Accessories",
-		"created_at": "2016-01-18T17:16:37+05:30",
-		"handle": "1001624-01",
-		"updated_at": "2016-01-20T17:26:44+05:30",
-		"published_at": "2016-01-18T17:16:37+05:30",
-		"template_suffix": null,
-		"published_scope": "global",
-		"tags": "Category_Curtain Accessories, Type_Holdback",
-		"variants": [{
-			"id": 13917612359,
-			"product_id": 4059739520,
-			"title": "Test BALCK Item",
-			"price": "499.00",
-			"sku": "",
-			"position": 1,
-			"grams": 0,
-			"inventory_policy": "continue",
-			"compare_at_price": null,
-			"fulfillment_service": "manual",
-			"inventory_management": "shopify",
-			"option1": "BLACK",
-			"option2": null,
-			"option3": null,
-			"created_at": "2016-01-18T17:16:37+05:30",
-			"updated_at": "2016-01-20T17:26:44+05:30",
-			"requires_shipping": true,
-			"taxable": true,
-			"barcode": "",
-			"inventory_quantity": -1,
-			"old_inventory_quantity": -1,
-			"image_id": 8539321735,
-			"weight": 0,
-			"weight_unit": "kg"
-		}, {
-			"id": 13917612423,
-			"product_id": 4059739520,
-			"title": "Test BLUE Item",
-			"price": "499.00",
-			"sku": "",
-			"position": 2,
-			"grams": 0,
-			"inventory_policy": "continue",
-			"compare_at_price": null,
-			"fulfillment_service": "manual",
-			"inventory_management": "shopify",
-			"option1": "BLUE",
-			"option2": null,
-			"option3": null,
-			"created_at": "2016-01-18T17:16:37+05:30",
-			"updated_at": "2016-01-20T17:26:44+05:30",
-			"requires_shipping": true,
-			"taxable": true,
-			"barcode": "",
-			"inventory_quantity": -1,
-			"old_inventory_quantity": -1,
-			"image_id": null,
-			"weight": 0,
-			"weight_unit": "kg"
-		}, {
-			"id": 13917612487,
-			"product_id": 4059739520,
-			"title": "Test White Item",
-			"price": "499.00",
-			"sku": "",
-			"position": 3,
-			"grams": 0,
-			"inventory_policy": "continue",
-			"compare_at_price": null,
-			"fulfillment_service": "manual",
-			"inventory_management": "shopify",
-			"option1": "White",
-			"option2": null,
-			"option3": null,
-			"created_at": "2016-01-18T17:16:37+05:30",
-			"updated_at": "2016-01-18T17:16:37+05:30",
-			"requires_shipping": true,
-			"taxable": true,
-			"barcode": "",
-			"inventory_quantity": 0,
-			"old_inventory_quantity": 0,
-			"image_id": null,
-			"weight": 0,
-			"weight_unit": "kg"
-		}],
-		"options": [{
-			"id": 4985027399,
-			"product_id": 4059739520,
-			"name": "Colour",
-			"position": 1,
-			"values": [
-				"BLACK",
-				"BLUE",
-				"White"
-			]
-		}],
-		"images": [{
-			"id": 8539321735,
-			"product_id": 4059739520,
-			"position": 1,
-			"created_at": "2016-01-18T17:16:37+05:30",
-			"updated_at": "2016-01-18T17:16:37+05:30",
-			"src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
-			"variant_ids": [
-				13917612359
-			]
-		}],
-		"image": {
-			"id": 8539321735,
-			"product_id": 4059739520,
-			"position": 1,
-			"created_at": "2016-01-18T17:16:37+05:30",
-			"updated_at": "2016-01-18T17:16:37+05:30",
-			"src": "https://cdn.shopify.com/s/files/1/1123/0654/products/2015-12-17_6.png?v=1453117597",
-			"variant_ids": [
-				13917612359
-			]
-		}
-	}
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json b/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json
deleted file mode 100644
index 988a2f0..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_data/shopify_order.json
+++ /dev/null
@@ -1,270 +0,0 @@
-{
-    "order": {
-        "id": 2414345735,
-        "email": "andrew@wyatt.co.in",
-        "closed_at": null,
-        "created_at": "2016-01-20T17:26:39+05:30",
-        "updated_at": "2016-01-20T17:27:15+05:30",
-        "number": 5,
-        "note": "",
-        "token": "660fed25987517b733644a8c9ec7c8e0",
-        "gateway": "manual",
-        "test": false,
-        "total_price": "1018.00",
-        "subtotal_price": "998.00",
-        "total_weight": 0,
-        "total_tax": "0.00",
-        "taxes_included": false,
-        "currency": "INR",
-        "financial_status": "paid",
-        "confirmed": true,
-        "total_discounts": "0.00",
-        "total_line_items_price": "998.00",
-        "cart_token": null,
-        "buyer_accepts_marketing": false,
-        "name": "#1005",
-        "referring_site": null,
-        "landing_site": null,
-        "cancelled_at": null,
-        "cancel_reason": null,
-        "total_price_usd": "15.02",
-        "checkout_token": null,
-        "reference": null,
-        "user_id": 55391175,
-        "location_id": null,
-        "source_identifier": null,
-        "source_url": null,
-        "processed_at": "2016-01-20T17:26:39+05:30",
-        "device_id": null,
-        "browser_ip": null,
-        "landing_site_ref": null,
-        "order_number": 1005,
-        "discount_codes": [],
-        "note_attributes": [],
-        "payment_gateway_names": [
-            "manual"
-        ],
-        "processing_method": "manual",
-        "checkout_id": null,
-        "source_name": "shopify_draft_order",
-        "fulfillment_status": "fulfilled",
-        "tax_lines": [],
-        "tags": "",
-        "contact_email": "andrew@wyatt.co.in",
-        "line_items": [
-            {
-                "id": 4125768135,
-                "variant_id": 13917612359,
-                "title": "Shopify Test Item",
-                "quantity": 1,
-                "price": "499.00",
-                "grams": 0,
-                "sku": "",
-                "variant_title": "Roman BALCK 1",
-                "vendor": "Boa casa",
-                "fulfillment_service": "manual",
-                "product_id": 4059739527,
-                "requires_shipping": true,
-                "taxable": true,
-                "gift_card": false,
-                "name": "Roman BALCK 1",
-                "variant_inventory_management": "shopify",
-                "properties": [],
-                "product_exists": true,
-                "fulfillable_quantity": 0,
-                "total_discount": "0.00",
-                "fulfillment_status": "fulfilled",
-                "tax_lines": []
-            },
-            {
-                "id": 4125768199,
-                "variant_id": 13917612423,
-                "title": "Shopify Test Item",
-                "quantity": 1,
-                "price": "499.00",
-                "grams": 0,
-                "sku": "",
-                "variant_title": "Satin BLUE 1",
-                "vendor": "Boa casa",
-                "fulfillment_service": "manual",
-                "product_id": 4059739527,
-                "requires_shipping": true,
-                "taxable": true,
-                "gift_card": false,
-                "name": "Satin BLUE 1",
-                "variant_inventory_management": "shopify",
-                "properties": [],
-                "product_exists": true,
-                "fulfillable_quantity": 0,
-                "total_discount": "0.00",
-                "fulfillment_status": "fulfilled",
-                "tax_lines": []
-            }
-        ],
-        "shipping_lines": [
-            {
-                "id": 2108906247,
-                "title": "International Shipping",
-                "price": "20.00",
-                "code": "International Shipping",
-                "source": "shopify",
-                "phone": null,
-                "tax_lines": []
-            }
-        ],
-        "billing_address": {
-            "first_name": "Andrew",
-            "address1": "B-11, Betahouse",
-            "phone": "145-112211",
-            "city": "Manhattan",
-            "zip": "10027",
-            "province": "New York",
-            "country": "United States",
-            "last_name": "Wyatt",
-            "address2": "Street 11, Sector 52",
-            "company": "Wyatt Inc.",
-            "latitude": 40.8138912,
-            "longitude": -73.96243270000001,
-            "name": "Andrew Wyatt",
-            "country_code": "US",
-            "province_code": "NY"
-        },
-        "shipping_address": {
-            "first_name": "Andrew",
-            "address1": "B-11, Betahouse",
-            "phone": "145-112211",
-            "city": "Manhattan",
-            "zip": "10027",
-            "province": "New York",
-            "country": "United States",
-            "last_name": "Wyatt",
-            "address2": "Street 11, Sector 52",
-            "company": "Wyatt Inc.",
-            "latitude": 40.8138912,
-            "longitude": -73.96243270000001,
-            "name": "Andrew Wyatt",
-            "country_code": "US",
-            "province_code": "NY"
-        },
-        "fulfillments": [
-            {
-                "id": 1849629255,
-                "order_id": 2414345735,
-                "status": "success",
-                "created_at": "2016-01-20T17:27:15+05:30",
-                "service": "manual",
-                "updated_at": "2016-01-20T17:27:15+05:30",
-                "tracking_company": null,
-                "tracking_number": null,
-                "tracking_numbers": [],
-                "tracking_url": null,
-                "tracking_urls": [],
-                "receipt": {},
-                "line_items": [
-                    {
-                        "id": 4125768199,
-                        "variant_id": 13917612423,
-                        "title": "1001624/01",
-                        "quantity": 1,
-                        "price": "499.00",
-                        "grams": 0,
-                        "sku": "",
-                        "variant_title": "Satin Silver",
-                        "vendor": "Boa casa",
-                        "fulfillment_service": "manual",
-                        "product_id": 4059739527,
-                        "requires_shipping": true,
-                        "taxable": true,
-                        "gift_card": false,
-                        "name": "1001624/01 - Satin Silver",
-                        "variant_inventory_management": "shopify",
-                        "properties": [],
-                        "product_exists": true,
-                        "fulfillable_quantity": 0,
-                        "total_discount": "0.00",
-                        "fulfillment_status": "fulfilled",
-                        "tax_lines": []
-                    }
-                ]
-            },
-            {
-                "id": 1849628167,
-                "order_id": 2414345735,
-                "status": "success",
-                "created_at": "2016-01-20T17:26:58+05:30",
-                "service": "manual",
-                "updated_at": "2016-01-20T17:26:58+05:30",
-                "tracking_company": null,
-                "tracking_number": null,
-                "tracking_numbers": [],
-                "tracking_url": null,
-                "tracking_urls": [],
-                "receipt": {},
-                "line_items": [
-                    {
-                        "id": 4125768135,
-                        "variant_id": 13917612359,
-                        "title": "1001624/01",
-                        "quantity": 1,
-                        "price": "499.00",
-                        "grams": 0,
-                        "sku": "",
-                        "variant_title": "Roman Bronze",
-                        "vendor": "Boa casa",
-                        "fulfillment_service": "manual",
-                        "product_id": 4059739527,
-                        "requires_shipping": true,
-                        "taxable": true,
-                        "gift_card": false,
-                        "name": "1001624/01 - Roman Bronze",
-                        "variant_inventory_management": "shopify",
-                        "properties": [],
-                        "product_exists": true,
-                        "fulfillable_quantity": 0,
-                        "total_discount": "0.00",
-                        "fulfillment_status": "fulfilled",
-                        "tax_lines": []
-                    }
-                ]
-            }
-        ],
-        "refunds": [],
-        "customer": {
-            "id": 2324518599,
-            "email": "andrew@wyatt.co.in",
-            "accepts_marketing": false,
-            "created_at": "2016-01-20T17:18:35+05:30",
-            "updated_at": "2016-01-20T17:26:39+05:30",
-            "first_name": "Andrew",
-            "last_name": "Wyatt",
-            "orders_count": 1,
-            "state": "disabled",
-            "total_spent": "1018.00",
-            "last_order_id": 2414345735,
-            "note": "",
-            "verified_email": true,
-            "multipass_identifier": null,
-            "tax_exempt": false,
-            "tags": "",
-            "last_order_name": "#1005",
-            "default_address": {
-                "id": 2476804295,
-                "first_name": "Andrew",
-                "last_name": "Wyatt",
-                "company": "Wyatt Inc.",
-                "address1": "B-11, Betahouse",
-                "address2": "Street 11, Sector 52",
-                "city": "Manhattan",
-                "province": "New York",
-                "country": "United States",
-                "zip": "10027",
-                "phone": "145-112211",
-                "name": "Andrew Wyatt",
-                "province_code": "NY",
-                "country_code": "US",
-                "country_name": "United States",
-                "default": true
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js
deleted file mode 100644
index b2f82d5..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/* eslint-disable */
-// rename this file from _test_[name] to test_[name] to activate
-// and remove above this line
-
-QUnit.test("test: Shopify Settings", function (assert) {
-	let done = assert.async();
-
-	// number of asserts
-	assert.expect(1);
-
-	frappe.run_serially([
-		// insert a new Shopify Settings
-		() => frappe.tests.make('Shopify Settings', [
-			// values to be set
-			{key: 'value'}
-		]),
-		() => {
-			assert.equal(cur_frm.doc.key, 'value');
-		},
-		() => done()
-	]);
-
-});
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
deleted file mode 100644
index 6bec301..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
+++ /dev/null
@@ -1,107 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-import frappe
-
-import unittest, os, json
-from frappe.utils import cstr, cint
-from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
-from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
-from frappe.core.doctype.data_import.data_import import import_doc
-
-
-class ShopifySettings(unittest.TestCase):
-	@classmethod
-	def setUpClass(cls):
-		frappe.set_user("Administrator")
-
-		cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
-		if not cls.allow_negative_stock:
-			frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
-
-		# use the fixture data
-		import_doc(path=frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
-
-		frappe.reload_doctype("Customer")
-		frappe.reload_doctype("Sales Order")
-		frappe.reload_doctype("Delivery Note")
-		frappe.reload_doctype("Sales Invoice")
-
-		cls.setup_shopify()
-
-	@classmethod
-	def tearDownClass(cls):
-		if not cls.allow_negative_stock:
-			frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
-
-	@classmethod
-	def setup_shopify(cls):
-		shopify_settings = frappe.get_doc("Shopify Settings")
-		shopify_settings.taxes = []
-
-		shopify_settings.update({
-			"app_type": "Private",
-			"shopify_url": "test.myshopify.com",
-			"api_key": "17702c7c4452b9c5d235240b6e7a39da",
-			"password": "17702c7c4452b9c5d235240b6e7a39da",
-			"shared_secret": "17702c7c4452b9c5d235240b6e7a39da",
-			"price_list": "_Test Price List",
-			"warehouse": "_Test Warehouse - _TC",
-			"cash_bank_account": "Cash - _TC",
-			"account": "Cash - _TC",
-			"customer_group": "_Test Customer Group",
-			"cost_center": "Main - _TC",
-			"taxes": [
-				{
-					"shopify_tax": "International Shipping",
-					"tax_account":"Legal Expenses - _TC"
-				}
-			],
-			"enable_shopify": 0,
-			"sales_order_series": "SO-",
-			"sync_sales_invoice": 1,
-			"sales_invoice_series": "SINV-",
-			"sync_delivery_note": 1,
-			"delivery_note_series": "DN-"
-		}).save(ignore_permissions=True)
-
-		cls.shopify_settings = shopify_settings
-
-	def test_order(self):
-		# Create Customer
-		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
-			shopify_customer = json.load(shopify_customer)
-		create_customer(shopify_customer.get("customer"), self.shopify_settings)
-
-		# Create Item
-		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
-			shopify_item = json.load(shopify_item)
-		make_item("_Test Warehouse - _TC", shopify_item.get("product"))
-
-		# Create Order
-		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
-			shopify_order = json.load(shopify_order)
-
-		create_order(shopify_order.get("order"), self.shopify_settings, False, company="_Test Company")
-
-		sales_order = frappe.get_doc("Sales Order", {"shopify_order_id": cstr(shopify_order.get("order").get("id"))})
-
-		self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
-
-		# Check for customer
-		shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
-		sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
-
-		self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
-
-		# Check sales invoice
-		sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
-		self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
-
-		# Check delivery note
-		delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
-			where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
-
-		self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments")))
diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json b/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json
deleted file mode 100644
index 63c674c..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.json
+++ /dev/null
@@ -1,133 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2015-10-05 16:55:20.455371", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 0, 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "shopify_tax", 
-   "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": "Shopify Tax/Shipping 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": 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": "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "tax_account", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "ERPNext Account", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Account", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }
- ], 
- "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-04-09 11:36:49.272815", 
- "modified_by": "Administrator", 
- "module": "ERPNext Integrations", 
- "name": "Shopify Tax Account", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 0, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py b/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py
deleted file mode 100644
index 74c13c0..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_tax_account/shopify_tax_account.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.model.document import Document
-
-class ShopifyTaxAccount(Document):
-	pass
diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/__init__.py
+++ /dev/null
diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json
deleted file mode 100644
index e47ecdc..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.json
+++ /dev/null
@@ -1,103 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2018-04-10 17:06:22.697427", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 0, 
- "engine": "InnoDB", 
- "fields": [
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "webhook_id", 
-   "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": "Webhook ID", 
-   "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
-  }, 
-  {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "method", 
-   "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": "Method", 
-   "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
-  }
- ], 
- "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-04-11 12:43:09.456449", 
- "modified_by": "Administrator", 
- "module": "ERPNext Integrations", 
- "name": "Shopify Webhook Detail", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
-}
\ No newline at end of file
diff --git a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py b/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py
deleted file mode 100644
index e127989..0000000
--- a/erpnext/erpnext_integrations/doctype/shopify_webhook_detail/shopify_webhook_detail.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-
-from __future__ import unicode_literals
-import frappe
-from frappe.model.document import Document
-
-class ShopifyWebhookDetail(Document):
-	pass
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
index 4a5e54e..24b8e48 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations/erpnext_integrations.json
@@ -41,16 +41,6 @@
    "type": "Link"
   },
   {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Shopify Settings",
-   "link_to": "Shopify Settings",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Payments",
@@ -113,4 +103,4 @@
  "pin_to_bottom": 0,
  "pin_to_top": 0,
  "shortcuts": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json
index d258d57..d656b3c 100644
--- a/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json
+++ b/erpnext/erpnext_integrations/workspace/erpnext_integrations_settings/erpnext_integrations_settings.json
@@ -34,16 +34,6 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Shopify Settings",
-   "link_to": "Shopify Settings",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
    "label": "Amazon MWS Settings",
    "link_to": "Amazon MWS Settings",
    "link_type": "DocType",
@@ -79,4 +69,4 @@
  "pin_to_bottom": 0,
  "pin_to_top": 0,
  "shortcuts": []
-}
\ No newline at end of file
+}
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 9717bb9..8f7c7db 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -25,7 +25,8 @@
 	"Address": "public/js/address.js",
 	"Communication": "public/js/communication.js",
 	"Event": "public/js/event.js",
-	"Newsletter": "public/js/newsletter.js"
+	"Newsletter": "public/js/newsletter.js",
+	"Contact": "public/js/contact.js"
 }
 
 override_doctype_class = {
@@ -340,7 +341,6 @@
 		"erpnext.projects.doctype.project.project.hourly_reminder",
 		"erpnext.projects.doctype.project.project.collect_project_status",
 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
-		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
 		"erpnext.support.doctype.service_level_agreement.service_level_agreement.set_service_level_agreement_variance"
 	],
 	"hourly_long": [
@@ -437,7 +437,8 @@
 		'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.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'
 	},
 	'United Arab Emirates': {
 		'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data',
diff --git a/erpnext/hr/doctype/appraisal/appraisal.py b/erpnext/hr/doctype/appraisal/appraisal.py
index f760187..c2ed457 100644
--- a/erpnext/hr/doctype/appraisal/appraisal.py
+++ b/erpnext/hr/doctype/appraisal/appraisal.py
@@ -9,7 +9,7 @@
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, validate_active_employee
 
 class Appraisal(Document):
 	def validate(self):
@@ -19,6 +19,7 @@
 		if not self.goals:
 			frappe.throw(_("Goals cannot be empty"))
 
+		validate_active_employee(self.employee)
 		set_employee_name(self)
 		self.validate_dates()
 		self.validate_existing_appraisal()
diff --git a/erpnext/hr/doctype/attendance/attendance.py b/erpnext/hr/doctype/attendance/attendance.py
index 3412675..f79f0fe 100644
--- a/erpnext/hr/doctype/attendance/attendance.py
+++ b/erpnext/hr/doctype/attendance/attendance.py
@@ -8,11 +8,13 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import cstr, get_datetime, formatdate
+from erpnext.hr.utils import validate_active_employee
 
 class Attendance(Document):
 	def validate(self):
 		from erpnext.controllers.status_updater import validate_status
 		validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
+		validate_active_employee(self.employee)
 		self.validate_attendance_date()
 		self.validate_duplicate_record()
 		self.validate_employee_status()
diff --git a/erpnext/hr/doctype/attendance_request/attendance_request.py b/erpnext/hr/doctype/attendance_request/attendance_request.py
index 090d532..7f88fed 100644
--- a/erpnext/hr/doctype/attendance_request/attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/attendance_request.py
@@ -8,10 +8,11 @@
 from frappe.model.document import Document
 from frappe.utils import date_diff, add_days, getdate
 from erpnext.hr.doctype.employee.employee import is_holiday
-from erpnext.hr.utils import validate_dates
+from erpnext.hr.utils import validate_dates, validate_active_employee
 
 class AttendanceRequest(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		validate_dates(self, self.from_date, self.to_date)
 		if self.half_day:
 			if not getdate(self.from_date)<=getdate(self.half_day_date)<=getdate(self.to_date):
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index a6fe429..0d7fded 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -7,12 +7,13 @@
 from frappe import _
 from frappe.utils import date_diff, add_days, getdate, cint, format_date
 from frappe.model.document import Document
-from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
+from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, validate_active_employee, \
 	get_holidays_for_employee, create_additional_leave_ledger_entry
 
 class CompensatoryLeaveRequest(Document):
 
 	def validate(self):
+		validate_active_employee(self.employee)
 		validate_dates(self, self.work_from_date, self.work_end_date)
 		if self.half_day:
 			if not self.half_day_date:
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index fa017d9..5ca4756 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -13,8 +13,10 @@
 from erpnext.utilities.transaction_base import delete_events
 from frappe.utils.nestedset import NestedSet
 
-class EmployeeUserDisabledError(frappe.ValidationError): pass
-class EmployeeLeftValidationError(frappe.ValidationError): pass
+class EmployeeUserDisabledError(frappe.ValidationError):
+	pass
+class InactiveEmployeeStatusError(frappe.ValidationError):
+	pass
 
 class Employee(NestedSet):
 	nsm_parent_field = 'reports_to'
@@ -196,7 +198,7 @@
 				message += "<br><br><ul><li>" + "</li><li>".join(link_to_employees)
 				message += "</li></ul><br>"
 				message += _("Please make sure the employees above report to another Active employee.")
-				throw(message, EmployeeLeftValidationError, _("Cannot Relieve Employee"))
+				throw(message, InactiveEmployeeStatusError, _("Cannot Relieve Employee"))
 			if not self.relieving_date:
 				throw(_("Please enter relieving date."))
 
diff --git a/erpnext/hr/doctype/employee/test_employee.py b/erpnext/hr/doctype/employee/test_employee.py
index 7d652a7..8fc7cf1 100644
--- a/erpnext/hr/doctype/employee/test_employee.py
+++ b/erpnext/hr/doctype/employee/test_employee.py
@@ -7,7 +7,7 @@
 import erpnext
 import unittest
 import frappe.utils
-from erpnext.hr.doctype.employee.employee import EmployeeLeftValidationError
+from erpnext.hr.doctype.employee.employee import InactiveEmployeeStatusError
 
 test_records = frappe.get_test_records('Employee')
 
@@ -45,10 +45,33 @@
 		employee2_doc.save()
 		employee1_doc.reload()
 		employee1_doc.status = 'Left'
-		self.assertRaises(EmployeeLeftValidationError, employee1_doc.save)
+		self.assertRaises(InactiveEmployeeStatusError, employee1_doc.save)
+
+	def test_employee_status_inactive(self):
+		from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure
+		from erpnext.payroll.doctype.salary_structure.salary_structure import make_salary_slip
+		from erpnext.payroll.doctype.salary_slip.test_salary_slip import make_holiday_list
+
+		employee = make_employee("test_employee_status@company.com")
+		employee_doc = frappe.get_doc("Employee", employee)
+		employee_doc.status = "Inactive"
+		employee_doc.save()
+		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.sql("""delete from `tabSalary Structure` where name='Test Inactive Employee Salary Slip'""")
+		salary_structure = make_salary_structure("Test Inactive Employee Salary Slip", "Monthly",
+			employee=employee_doc.name, company=employee_doc.company)
+		salary_slip = make_salary_slip(salary_structure.name, employee=employee_doc.name)
+
+		self.assertRaises(InactiveEmployeeStatusError, salary_slip.save)
+
+	def tearDown(self):
+		frappe.db.rollback()
 
 def make_employee(user, company=None, **kwargs):
-	""
 	if not frappe.db.get_value("User", user):
 		frappe.get_doc({
 			"doctype": "User",
@@ -80,4 +103,5 @@
 		employee.insert()
 		return employee.name
 	else:
+		frappe.db.set_value("Employee", {"employee_name":user}, "status", "Active")
 		return frappe.get_value("Employee", {"employee_name":user}, "name")
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.py b/erpnext/hr/doctype/employee_advance/employee_advance.py
index cb72f6b..cbb3cc8 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.py
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.py
@@ -8,6 +8,7 @@
 from frappe.model.document import Document
 from frappe.utils import flt, nowdate
 from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
+from erpnext.hr.utils import validate_active_employee
 
 class EmployeeAdvanceOverPayment(frappe.ValidationError):
 	pass
@@ -18,11 +19,11 @@
 			'make_payment_via_journal_entry')
 
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.set_status()
 
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ('GL Entry')
-		self.set_status()
 
 	def set_status(self):
 		if self.docstatus == 0:
@@ -183,9 +184,9 @@
 	bank_cash_account = get_default_bank_cash_account(company, account_type='Cash', mode_of_payment = mode_of_payment)
 	if not bank_cash_account:
 		frappe.throw(_("Please set a Default Cash Account in Company defaults"))
-	
+
 	advance_account_currency = frappe.db.get_value('Account', advance_account, 'account_currency')
-	
+
 	je = frappe.new_doc('Journal Entry')
 	je.posting_date = nowdate()
 	je.voucher_type = get_voucher_type(mode_of_payment)
@@ -229,4 +230,4 @@
 		if mode_of_payment_type == "Bank":
 			voucher_type = "Bank Entry"
 
-	return voucher_type
\ No newline at end of file
+	return voucher_type
diff --git a/erpnext/hr/doctype/employee_checkin/employee_checkin.py b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
index 15fbd4e..60ea0f9 100644
--- a/erpnext/hr/doctype/employee_checkin/employee_checkin.py
+++ b/erpnext/hr/doctype/employee_checkin/employee_checkin.py
@@ -9,9 +9,11 @@
 from frappe import _
 
 from erpnext.hr.doctype.shift_assignment.shift_assignment import get_actual_start_end_datetime_of_shift
+from erpnext.hr.utils import validate_active_employee
 
 class EmployeeCheckin(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_duplicate_log()
 		self.fetch_shift()
 
@@ -122,7 +124,7 @@
 def calculate_working_hours(logs, check_in_out_type, working_hours_calc_type):
 	"""Given a set of logs in chronological order calculates the total working hours based on the parameters.
 	Zero is returned for all invalid cases.
-	
+
 	:param logs: The List of 'Employee Checkin'.
 	:param check_in_out_type: One of: 'Alternating entries as IN and OUT during the same shift', 'Strictly based on Log Type in Employee Checkin'
 	:param working_hours_calc_type: One of: 'First Check-in and Last Check-out', 'Every Valid Check-in and Check-out'
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
index d6047e1..5d1a024 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.js
@@ -50,28 +50,13 @@
 			}, __('Create'));
 			frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
-		if (frm.doc.docstatus === 1 && frm.doc.project) {
-			frappe.call({
-				method: "erpnext.hr.utils.get_boarding_status",
-				args: {
-					"project": frm.doc.project
-				},
-				callback: function(r) {
-					if (r.message) {
-						frm.set_value('boarding_status', r.message);
-					}
-					refresh_field("boarding_status");
-				}
-			});
-		}
-
 	},
 
 	employee_onboarding_template: function(frm) {
 		frm.set_value("activities" ,"");
 		if (frm.doc.employee_onboarding_template) {
 			frappe.call({
-				method: "erpnext.hr.utils.get_onboarding_details",
+				method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
 				args: {
 					"parent": frm.doc.employee_onboarding_template,
 					"parenttype": "Employee Onboarding Template"
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
index 783c757..673e228 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.json
@@ -30,18 +30,14 @@
    "fieldtype": "Link",
    "label": "Job Applicant",
    "options": "Job Applicant",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "job_offer",
    "fieldtype": "Link",
    "label": "Job Offer",
    "options": "Job Offer",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fetch_from": "job_applicant.applicant_name",
@@ -49,116 +45,90 @@
    "fieldtype": "Data",
    "in_list_view": 1,
    "label": "Employee Name",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "employee",
    "fieldtype": "Link",
    "label": "Employee",
    "options": "Employee",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "date_of_joining",
    "fieldtype": "Date",
    "in_list_view": 1,
-   "label": "Date of Joining",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Date of Joining"
   },
   {
    "allow_on_submit": 1,
+   "default": "Pending",
    "fieldname": "boarding_status",
    "fieldtype": "Select",
    "label": "Status",
-   "options": "\nPending\nIn Process\nCompleted",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Pending\nIn Process\nCompleted",
+   "read_only": 1
   },
   {
    "allow_on_submit": 1,
    "default": "0",
    "fieldname": "notify_users_by_email",
    "fieldtype": "Check",
-   "label": "Notify users by email",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Notify users by email"
   },
   {
    "fieldname": "column_break_7",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "employee_onboarding_template",
    "fieldtype": "Link",
    "label": "Employee Onboarding Template",
-   "options": "Employee Onboarding Template",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Employee Onboarding Template"
   },
   {
    "fieldname": "company",
    "fieldtype": "Link",
    "label": "Company",
-   "options": "Company",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Company"
   },
   {
    "fieldname": "department",
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Department",
-   "options": "Department",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Department"
   },
   {
    "fieldname": "designation",
    "fieldtype": "Link",
    "in_list_view": 1,
    "label": "Designation",
-   "options": "Designation",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Designation"
   },
   {
    "fieldname": "employee_grade",
    "fieldtype": "Link",
    "label": "Employee Grade",
-   "options": "Employee Grade",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Employee Grade"
   },
   {
    "fieldname": "project",
    "fieldtype": "Link",
    "label": "Project",
    "options": "Project",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "table_for_activity",
-   "fieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Section Break"
   },
   {
    "allow_on_submit": 1,
    "fieldname": "activities",
    "fieldtype": "Table",
    "label": "Activities",
-   "options": "Employee Boarding Activity",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Employee Boarding Activity"
   },
   {
    "fieldname": "amended_from",
@@ -167,14 +137,12 @@
    "no_copy": 1,
    "options": "Employee Onboarding",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   }
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-06-25 15:22:24.923835",
+ "modified": "2021-06-03 18:01:51.097927",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Onboarding",
diff --git a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
index 6cc2bf5..55fe317 100644
--- a/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/employee_onboarding.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from erpnext.hr.utils import EmployeeBoardingController
+from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
 from frappe.model.mapper import get_mapped_doc
 
 class IncompleteTaskError(frappe.ValidationError): pass
@@ -16,9 +16,9 @@
 		self.validate_duplicate_employee_onboarding()
 
 	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})
 		if emp_onboarding and emp_onboarding != self.name:
-			frappe.throw(_("Employee Onboarding: {0} is already for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
+			frappe.throw(_("Employee Onboarding: {0} already exists for Job Applicant: {1}").format(frappe.bold(emp_onboarding), frappe.bold(self.job_applicant)))
 
 	def validate_employee_creation(self):
 		if self.docstatus != 1:
@@ -30,7 +30,7 @@
 				else:
 					task_status = frappe.db.get_value("Task", activity.task, "status")
 					if task_status not in ["Completed", "Cancelled"]:
-						frappe.throw(_("All the mandatory Task for employee creation hasn't been done yet."), IncompleteTaskError)
+						frappe.throw(_("All the mandatory tasks for employee creation are not completed yet."), IncompleteTaskError)
 
 	def on_submit(self):
 		super(EmployeeOnboarding, self).on_submit()
diff --git a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
index 336e13c..5f7756b 100644
--- a/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
+++ b/erpnext/hr/doctype/employee_onboarding/test_employee_onboarding.py
@@ -11,39 +11,26 @@
 from erpnext.hr.doctype.job_offer.test_job_offer import create_job_offer
 
 class TestEmployeeOnboarding(unittest.TestCase):
-	def test_employee_onboarding_incomplete_task(self):
+	def setUp(self):
 		if frappe.db.exists('Employee Onboarding', {'employee_name': 'Test Researcher'}):
 			frappe.delete_doc('Employee Onboarding', {'employee_name': 'Test Researcher'})
-		_set_up()
-		applicant = get_job_applicant()
 
-		job_offer = create_job_offer(job_applicant=applicant.name)
-		job_offer.submit()
+		project = "Employee Onboarding : Test Researcher - test@researcher.com"
+		frappe.db.sql("delete from tabProject where name=%s", project)
+		frappe.db.sql("delete from tabTask where project=%s", project)
 
-		onboarding = frappe.new_doc('Employee Onboarding')
-		onboarding.job_applicant = applicant.name
-		onboarding.job_offer = job_offer.name
-		onboarding.company = '_Test Company'
-		onboarding.designation = 'Researcher'
-		onboarding.append('activities', {
-			'activity_name': 'Assign ID Card',
-			'role': 'HR User',
-			'required_for_employee_creation': 1
-		})
-		onboarding.append('activities', {
-			'activity_name': 'Assign a laptop',
-			'role': 'HR User'
-		})
-		onboarding.status = 'Pending'
-		onboarding.insert()
-		onboarding.submit()
+	def test_employee_onboarding_incomplete_task(self):
+		onboarding = create_employee_onboarding()
 
-		project_name = frappe.db.get_value("Project", onboarding.project, "project_name")
+		project_name = frappe.db.get_value('Project', onboarding.project, 'project_name')
 		self.assertEqual(project_name, 'Employee Onboarding : Test Researcher - test@researcher.com')
 
 		# don't allow making employee if onboarding is not complete
 		self.assertRaises(IncompleteTaskError, make_employee, onboarding.name)
 
+		# boarding status
+		self.assertEqual(onboarding.boarding_status, 'Pending')
+
 		# complete the task
 		project = frappe.get_doc('Project', onboarding.project)
 		for task in frappe.get_all('Task', dict(project=project.name)):
@@ -51,6 +38,10 @@
 			task.status = 'Completed'
 			task.save()
 
+		# boarding status
+		onboarding.reload()
+		self.assertEqual(onboarding.boarding_status, 'Completed')
+
 		# make employee
 		onboarding.reload()
 		employee = make_employee(onboarding.name)
@@ -61,6 +52,13 @@
 		employee.insert()
 		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()
+
+
 def get_job_applicant():
 	if frappe.db.exists('Job Applicant', 'Test Researcher - test@researcher.com'):
 		return frappe.get_doc('Job Applicant', 'Test Researcher - test@researcher.com')
@@ -72,10 +70,35 @@
 	applicant.insert()
 	return applicant
 
-def _set_up():
-	for doctype in ["Employee Onboarding"]:
-		frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
+def get_job_offer(applicant_name):
+	job_offer = frappe.db.exists('Job Offer', {'job_applicant': applicant_name})
+	if job_offer:
+		return frappe.get_doc('Job Offer', job_offer)
 
-	project = "Employee Onboarding : Test Researcher - test@researcher.com"
-	frappe.db.sql("delete from tabProject where name=%s", project)
-	frappe.db.sql("delete from tabTask where project=%s", project)
+	job_offer = create_job_offer(job_applicant=applicant_name)
+	job_offer.submit()
+	return job_offer
+
+def create_employee_onboarding():
+	applicant = get_job_applicant()
+	job_offer = get_job_offer(applicant.name)
+
+	onboarding = frappe.new_doc('Employee Onboarding')
+	onboarding.job_applicant = applicant.name
+	onboarding.job_offer = job_offer.name
+	onboarding.company = '_Test Company'
+	onboarding.designation = 'Researcher'
+	onboarding.append('activities', {
+		'activity_name': 'Assign ID Card',
+		'role': 'HR User',
+		'required_for_employee_creation': 1
+	})
+	onboarding.append('activities', {
+		'activity_name': 'Assign a laptop',
+		'role': 'HR User'
+	})
+	onboarding.status = 'Pending'
+	onboarding.insert()
+	onboarding.submit()
+
+	return onboarding
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_promotion/employee_promotion.py b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
index 83fb235..a3a6183 100644
--- a/erpnext/hr/doctype/employee_promotion/employee_promotion.py
+++ b/erpnext/hr/doctype/employee_promotion/employee_promotion.py
@@ -7,12 +7,11 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import getdate
-from erpnext.hr.utils import update_employee
+from erpnext.hr.utils import update_employee, validate_active_employee
 
 class EmployeePromotion(Document):
 	def validate(self):
-		if frappe.get_value("Employee", self.employee, "status") != "Active":
-			frappe.throw(_("Cannot promote Employee with status Left or Inactive"))
+		validate_active_employee(self.employee)
 
 	def before_submit(self):
 		if getdate(self.promotion_date) > getdate():
diff --git a/erpnext/hr/doctype/employee_referral/employee_referral.py b/erpnext/hr/doctype/employee_referral/employee_referral.py
index 45d6872..0493306 100644
--- a/erpnext/hr/doctype/employee_referral/employee_referral.py
+++ b/erpnext/hr/doctype/employee_referral/employee_referral.py
@@ -7,9 +7,11 @@
 from frappe import _
 from frappe.utils import get_link_to_form
 from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
 
 class EmployeeReferral(Document):
 	def validate(self):
+		validate_active_employee(self.referrer)
 		self.set_full_name()
 		self.set_referral_bonus_payment_status()
 
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.js b/erpnext/hr/doctype/employee_separation/employee_separation.js
index 9a75c16..d9011b2 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.js
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.js
@@ -23,27 +23,13 @@
 				frappe.set_route('List', 'Task', {project: frm.doc.project});
 			},__("View"));
 		}
-		if (frm.doc.docstatus === 1 && frm.doc.project) {
-			frappe.call({
-				method: "erpnext.hr.utils.get_boarding_status",
-				args: {
-					"project": frm.doc.project
-				},
-				callback: function(r) {
-					if (r.message) {
-						frm.set_value('boarding_status', r.message);
-					}
-					refresh_field("boarding_status");
-				}
-			});
-		}
 	},
 
 	employee_separation_template: function(frm) {
 		frm.set_value("activities" ,"");
 		if (frm.doc.employee_separation_template) {
 			frappe.call({
-				method: "erpnext.hr.utils.get_onboarding_details",
+				method: "erpnext.controllers.employee_boarding_controller.get_onboarding_details",
 				args: {
 					"parent": frm.doc.employee_separation_template,
 					"parenttype": "Employee Separation Template"
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.json b/erpnext/hr/doctype/employee_separation/employee_separation.json
index 7af20988..c10da5c 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.json
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.json
@@ -50,11 +50,12 @@
   },
   {
    "allow_on_submit": 1,
+   "default": "Pending",
    "fieldname": "boarding_status",
    "fieldtype": "Select",
    "label": "Status",
-   "options": "\nPending\nIn Process\nCompleted",
-   "reqd": 1
+   "options": "Pending\nIn Process\nCompleted",
+   "read_only": 1
   },
   {
    "allow_on_submit": 1,
@@ -147,7 +148,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2021-04-28 15:58:36.020196",
+ "modified": "2021-06-03 18:02:54.007313",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Separation",
diff --git a/erpnext/hr/doctype/employee_separation/employee_separation.py b/erpnext/hr/doctype/employee_separation/employee_separation.py
index b646681..8afee25 100644
--- a/erpnext/hr/doctype/employee_separation/employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/employee_separation.py
@@ -3,7 +3,7 @@
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-from erpnext.hr.utils import EmployeeBoardingController
+from erpnext.controllers.employee_boarding_controller import EmployeeBoardingController
 
 class EmployeeSeparation(EmployeeBoardingController):
 	def validate(self):
diff --git a/erpnext/hr/doctype/employee_separation/test_employee_separation.py b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
index 713fcf5..f787d9c 100644
--- a/erpnext/hr/doctype/employee_separation/test_employee_separation.py
+++ b/erpnext/hr/doctype/employee_separation/test_employee_separation.py
@@ -6,21 +6,43 @@
 import frappe
 import unittest
 
-test_dependencies = ["Employee Onboarding"]
+test_dependencies = ['Employee Onboarding']
 
 class TestEmployeeSeparation(unittest.TestCase):
 	def test_employee_separation(self):
-		employee = frappe.db.get_value("Employee", {"status": "Active"})
-		separation = frappe.new_doc('Employee Separation')
-		separation.employee = employee
-		separation.company = '_Test Company'
-		separation.append('activities', {
-			'activity_name': 'Deactivate Employee',
-			'role': 'HR User'
-		})
-		separation.boarding_status = 'Pending'
-		separation.insert()
-		separation.submit()
+		separation = create_employee_separation()
+
 		self.assertEqual(separation.docstatus, 1)
+		self.assertEqual(separation.boarding_status, 'Pending')
+
+		project = frappe.get_doc('Project', separation.project)
+		project.percent_complete_method = 'Manual'
+		project.status = 'Completed'
+		project.save()
+
+		separation.reload()
+		self.assertEqual(separation.boarding_status, 'Completed')
+
 		separation.cancel()
-		self.assertEqual(separation.project, "")
\ No newline at end of file
+		self.assertEqual(separation.project, '')
+
+	def tearDown(self):
+		for entry in frappe.get_all('Employee Separation'):
+			doc = frappe.get_doc('Employee Separation', entry.name)
+			if doc.docstatus == 1:
+				doc.cancel()
+			doc.delete()
+
+def create_employee_separation():
+	employee = frappe.db.get_value('Employee', {'status': 'Active'})
+	separation = frappe.new_doc('Employee Separation')
+	separation.employee = employee
+	separation.company = '_Test Company'
+	separation.append('activities', {
+		'activity_name': 'Deactivate Employee',
+		'role': 'HR User'
+	})
+	separation.boarding_status = 'Pending'
+	separation.insert()
+	separation.submit()
+	return separation
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_transfer/employee_transfer.py b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
index 6eec9fa..c200774 100644
--- a/erpnext/hr/doctype/employee_transfer/employee_transfer.py
+++ b/erpnext/hr/doctype/employee_transfer/employee_transfer.py
@@ -10,10 +10,6 @@
 from erpnext.hr.utils import update_employee
 
 class EmployeeTransfer(Document):
-	def validate(self):
-		if frappe.get_value("Employee", self.employee, "status") != "Active":
-			frappe.throw(_("Cannot transfer Employee with status Left or Inactive"))
-
 	def before_submit(self):
 		if getdate(self.transfer_date) > getdate():
 			frappe.throw(_("Employee Transfer cannot be submitted before Transfer Date"),
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index 5010fc3..95e2806 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -6,7 +6,7 @@
 from frappe import _
 from frappe.utils import get_fullname, flt, cstr, get_link_to_form
 from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name, share_doc_with_approver
+from erpnext.hr.utils import set_employee_name, share_doc_with_approver, validate_active_employee
 from erpnext.accounts.party import get_party_account
 from erpnext.accounts.general_ledger import make_gl_entries
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
@@ -23,6 +23,7 @@
 			'make_payment_via_journal_entry')
 
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_advances()
 		self.validate_sanctioned_amount()
 		self.calculate_total_amount()
@@ -35,8 +36,8 @@
 		if self.task and not self.project:
 			self.project = frappe.db.get_value("Task", self.task, "project")
 
-	def set_status(self):
-		self.status = {
+	def set_status(self, update=False):
+		status = {
 			"0": "Draft",
 			"1": "Submitted",
 			"2": "Cancelled"
@@ -44,14 +45,18 @@
 
 		paid_amount = flt(self.total_amount_reimbursed) + flt(self.total_advance_amount)
 		precision = self.precision("grand_total")
-		if (self.is_paid or (flt(self.total_sanctioned_amount) > 0
-			and flt(self.grand_total, precision) ==  flt(paid_amount, precision))) \
-			and self.docstatus == 1 and self.approval_status == 'Approved':
-				self.status = "Paid"
+		if (self.is_paid or (flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1
+			and flt(self.grand_total, precision) == flt(paid_amount, precision))) and self.approval_status == 'Approved':
+			status = "Paid"
 		elif flt(self.total_sanctioned_amount) > 0 and self.docstatus == 1 and self.approval_status == 'Approved':
-			self.status = "Unpaid"
+			status = "Unpaid"
 		elif self.docstatus == 1 and self.approval_status == 'Rejected':
-			self.status = 'Rejected'
+			status = 'Rejected'
+
+		if update:
+			self.db_set("status", status)
+		else:
+			self.status = status
 
 	def on_update(self):
 		share_doc_with_approver(self, self.expense_approver)
@@ -74,7 +79,7 @@
 		if self.is_paid:
 			update_reimbursed_amount(self)
 
-		self.set_status()
+		self.set_status(update=True)
 		self.update_claimed_amount_in_employee_advance()
 
 	def on_cancel(self):
@@ -86,7 +91,6 @@
 		if self.is_paid:
 			update_reimbursed_amount(self)
 
-		self.set_status()
 		self.update_claimed_amount_in_employee_advance()
 
 	def update_claimed_amount_in_employee_advance(self):
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index cee6f37..93fb19f 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe import _
 from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, get_fullname, add_days, nowdate
-from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
+from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver, validate_active_employee
 from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
@@ -22,6 +22,7 @@
 		return _("{0}: From {0} of type {1}").format(self.employee_name, self.leave_type)
 
 	def validate(self):
+		validate_active_employee(self.employee)
 		set_employee_name(self)
 		self.validate_dates()
 		self.validate_balance_leaves()
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index e041b7f..912bd8a 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -7,7 +7,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import getdate, nowdate, flt
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, validate_active_employee
 from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import create_leave_ledger_entry
 from erpnext.hr.doctype.leave_allocation.leave_allocation import get_unused_leaves
@@ -15,6 +15,7 @@
 class LeaveEncashment(Document):
 	def validate(self):
 		set_employee_name(self)
+		validate_active_employee(self.employee)
 		self.get_leave_details_for_encashment()
 		self.validate_salary_structure()
 
diff --git a/erpnext/hr/doctype/shift_assignment/shift_assignment.py b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
index ab65260..89ae4d5 100644
--- a/erpnext/hr/doctype/shift_assignment/shift_assignment.py
+++ b/erpnext/hr/doctype/shift_assignment/shift_assignment.py
@@ -9,10 +9,12 @@
 from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, now_datetime, nowdate
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
+from erpnext.hr.utils import validate_active_employee
 from datetime import timedelta, datetime
 
 class ShiftAssignment(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_overlapping_dates()
 
 		if self.end_date and self.end_date <= self.start_date:
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 177c45e..6461f07 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -7,12 +7,13 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import formatdate, getdate
-from erpnext.hr.utils import share_doc_with_approver
+from erpnext.hr.utils import share_doc_with_approver, validate_active_employee
 
 class OverlapError(frappe.ValidationError): pass
 
 class ShiftRequest(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_dates()
 		self.validate_shift_request_overlap_dates()
 		self.validate_approver()
diff --git a/erpnext/hr/doctype/travel_request/travel_request.py b/erpnext/hr/doctype/travel_request/travel_request.py
index 01d3f34..60834d3 100644
--- a/erpnext/hr/doctype/travel_request/travel_request.py
+++ b/erpnext/hr/doctype/travel_request/travel_request.py
@@ -5,6 +5,8 @@
 from __future__ import unicode_literals
 import frappe
 from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
 
 class TravelRequest(Document):
-	pass
+	def validate(self):
+		validate_active_employee(self.employee)
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index ebb1734..992b18d 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -3,128 +3,15 @@
 
 import erpnext
 import frappe
-from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee, InactiveEmployeeStatusError
 from frappe import _
 from frappe.desk.form import assign_to
 from frappe.model.document import Document
 from frappe.utils import (add_days, cstr, flt, format_datetime, formatdate,
-	get_datetime, getdate, nowdate, today, unique)
-
+	get_datetime, getdate, nowdate, today, unique, get_link_to_form)
 
 class DuplicateDeclarationError(frappe.ValidationError): pass
 
-
-class EmployeeBoardingController(Document):
-	'''
-		Create the project and the task for the boarding process
-		Assign to the concerned person and roles as per the onboarding/separation template
-	'''
-	def validate(self):
-		# remove the task if linked before submitting the form
-		if self.amended_from:
-			for activity in self.activities:
-				activity.task = ''
-
-	def on_submit(self):
-		# create the project for the given employee onboarding
-		project_name = _(self.doctype) + " : "
-		if self.doctype == "Employee Onboarding":
-			project_name += self.job_applicant
-		else:
-			project_name += self.employee
-
-		project = frappe.get_doc({
-				"doctype": "Project",
-				"project_name": project_name,
-				"expected_start_date": self.date_of_joining if self.doctype == "Employee Onboarding" else self.resignation_letter_date,
-				"department": self.department,
-				"company": self.company
-			}).insert(ignore_permissions=True, ignore_mandatory=True)
-
-		self.db_set("project", project.name)
-		self.db_set("boarding_status", "Pending")
-		self.reload()
-		self.create_task_and_notify_user()
-
-	def create_task_and_notify_user(self):
-		# create the task for the given project and assign to the concerned person
-		for activity in self.activities:
-			if activity.task:
-				continue
-
-			task = frappe.get_doc({
-				"doctype": "Task",
-				"project": self.project,
-				"subject": activity.activity_name + " : " + self.employee_name,
-				"description": activity.description,
-				"department": self.department,
-				"company": self.company,
-				"task_weight": activity.task_weight
-			}).insert(ignore_permissions=True)
-			activity.db_set("task", task.name)
-
-			users = [activity.user] if activity.user else []
-			if activity.role:
-				user_list = frappe.db.sql_list('''
-					SELECT
-						DISTINCT(has_role.parent)
-					FROM
-						`tabHas Role` has_role
-							LEFT JOIN `tabUser` user
-								ON has_role.parent = user.name
-					WHERE
-						has_role.parenttype = 'User'
-							AND user.enabled = 1
-							AND has_role.role = %s
-				''', activity.role)
-				users = unique(users + user_list)
-
-				if "Administrator" in users:
-					users.remove("Administrator")
-
-			# assign the task the users
-			if users:
-				self.assign_task_to_users(task, users)
-
-	def assign_task_to_users(self, task, users):
-		for user in users:
-			args = {
-				'assign_to': [user],
-				'doctype': task.doctype,
-				'name': task.name,
-				'description': task.description or task.subject,
-				'notify': self.notify_users_by_email
-			}
-			assign_to.add(args)
-
-	def on_cancel(self):
-		# delete task project
-		for task in frappe.get_all("Task", filters={"project": self.project}):
-			frappe.delete_doc("Task", task.name, force=1)
-		frappe.delete_doc("Project", self.project, force=1)
-		self.db_set('project', '')
-		for activity in self.activities:
-			activity.db_set("task", "")
-
-
-@frappe.whitelist()
-def get_onboarding_details(parent, parenttype):
-	return frappe.get_all("Employee Boarding Activity",
-		fields=["activity_name", "role", "user", "required_for_employee_creation", "description", "task_weight"],
-		filters={"parent": parent, "parenttype": parenttype},
-		order_by= "idx")
-
-@frappe.whitelist()
-def get_boarding_status(project):
-	status = 'Pending'
-	if project:
-		doc = frappe.get_doc('Project', project)
-		if flt(doc.percent_complete) > 0.0 and flt(doc.percent_complete) < 100.0:
-			status = 'In Process'
-		elif flt(doc.percent_complete) == 100.0:
-			status = 'Completed'
-		return status
-
 def set_employee_name(doc):
 	if doc.employee and not doc.employee_name:
 		doc.employee_name = frappe.db.get_value("Employee", doc.employee, "employee_name")
@@ -522,3 +409,8 @@
 		approver = approvers.get(doc.doctype)
 		if doc_before_save.get(approver) != doc.get(approver):
 			frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
+
+def validate_active_employee(employee):
+	if frappe.db.get_value("Employee", employee, "status") == "Inactive":
+		frappe.throw(_("Transactions cannot be created for an Inactive Employee {0}.").format(
+			get_link_to_form("Employee", employee)), InactiveEmployeeStatusError)
\ No newline at end of file
diff --git a/erpnext/loan_management/doctype/loan_application/loan_application.js b/erpnext/loan_management/doctype/loan_application/loan_application.js
index 017026c..5142178 100644
--- a/erpnext/loan_management/doctype/loan_application/loan_application.js
+++ b/erpnext/loan_management/doctype/loan_application/loan_application.js
@@ -14,7 +14,7 @@
 	refresh: function(frm) {
 		frm.trigger("toggle_fields");
 		frm.trigger("add_toolbar_buttons");
-		frm.set_query("loan_type", () => {
+		frm.set_query('loan_type', () => {
 			return {
 				filters: {
 					company: frm.doc.company
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 8692f3d..4e93fc6 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -748,7 +748,7 @@
 	if valuation_rate <= 0:
 		last_valuation_rate = frappe.db.sql("""select valuation_rate
 			from `tabStock Ledger Entry`
-			where item_code = %s and valuation_rate > 0
+			where item_code = %s and valuation_rate > 0 and is_cancelled = 0
 			order by posting_date desc, posting_time desc, creation desc limit 1""", args['item_code'])
 
 		valuation_rate = flt(last_valuation_rate[0][0]) if last_valuation_rate else 0
@@ -774,7 +774,7 @@
 				item.image,
 				bom.project,
 				bom_item.rate,
-				bom_item.amount,
+				sum(bom_item.{qty_field}/ifnull(bom.quantity, 1)) * bom_item.rate * %(qty)s as amount,
 				item.stock_uom,
 				item.item_group,
 				item.allow_alternative_item,
@@ -1069,13 +1069,6 @@
 		if barcodes:
 			or_cond_filters["name"] = ("in", barcodes)
 
-	for cond in get_match_cond(doctype, as_condition=False):
-		for key, value in cond.items():
-			if key == doctype:
-				key = "name"
-
-			query_filters[key] = ("in", value)
-
 	if filters and filters.get("item_code"):
 		has_variants = frappe.get_cached_value("Item", filters.get("item_code"), "has_variants")
 		if not has_variants:
@@ -1084,7 +1077,7 @@
 	if filters and filters.get("is_stock_item"):
 		query_filters["is_stock_item"] = 1
 
-	return frappe.get_all("Item",
+	return frappe.get_list("Item",
 		fields = fields, filters=query_filters,
 		or_filters = or_cond_filters, order_by=order_by,
 		limit_start=start, limit_page_length=page_len, as_list=1)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 420bb00..69c7f5c 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -192,11 +192,11 @@
 						"completed_qty": args.get("completed_qty") or 0.0
 					})
 		elif args.get("start_time"):
-			new_args = {
+			new_args = frappe._dict({
 				"from_time": get_datetime(args.get("start_time")),
 				"operation": args.get("sub_operation"),
 				"completed_qty": 0.0
-			}
+			})
 
 			if employees:
 				for name in employees:
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 0a8e532..282b5d0 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -487,21 +487,20 @@
 			return
 
 		operations = []
-		if not self.use_multi_level_bom:
-			bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
-			operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
-		else:
+
+		if self.use_multi_level_bom:
 			bom_tree = frappe.get_doc("BOM", self.bom_no).get_tree_representation()
-			bom_traversal = list(reversed(bom_tree.level_order_traversal()))
-			bom_traversal.append(bom_tree) # add operation on top level item last
+			bom_traversal = reversed(bom_tree.level_order_traversal())
 
-			for d in bom_traversal:
-				if d.is_bom:
-					operations.extend(_get_operations(d.name, qty=d.exploded_qty))
+			for node in bom_traversal:
+				if node.is_bom:
+					operations.extend(_get_operations(node.name, qty=node.exploded_qty))
 
-			for correct_index, operation in enumerate(operations, start=1):
-				operation.idx = correct_index
+		bom_qty = frappe.db.get_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):
+			operation.idx = correct_index
 
 		self.set('operations', operations)
 		self.calculate_time()
@@ -656,7 +655,7 @@
 				for item in sorted(item_dict.values(), key=lambda d: d['idx'] or 9999):
 					self.append('required_items', {
 						'rate': item.rate,
-						'amount': item.amount,
+						'amount': item.rate * item.qty,
 						'operation': item.operation or operation,
 						'item_code': item.item_code,
 						'item_name': item.item_name,
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 8debf86..04f05ed 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -45,7 +45,6 @@
 erpnext.patches.v11_0.make_asset_finance_book_against_old_entries
 erpnext.patches.v11_0.check_buying_selling_in_currency_exchange
 erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany #02-07-2018 #19-06-2019
-erpnext.patches.v11_0.refactor_erpnext_shopify #2018-09-07
 erpnext.patches.v11_0.rename_overproduction_percent_field
 erpnext.patches.v11_0.update_backflush_subcontract_rm_based_on_bom
 erpnext.patches.v11_0.inter_state_field_for_gst
@@ -143,7 +142,6 @@
 erpnext.patches.v12_0.rename_bank_account_field_in_journal_entry_account
 erpnext.patches.v12_0.create_default_energy_point_rules
 erpnext.patches.v12_0.set_produced_qty_field_in_sales_order_for_work_order
-erpnext.patches.v12_0.set_default_shopify_app_type
 erpnext.patches.v12_0.set_cwip_and_delete_asset_settings
 erpnext.patches.v12_0.set_expense_account_in_landed_cost_voucher_taxes
 erpnext.patches.v12_0.replace_accounting_with_accounts_in_home_settings
@@ -244,7 +242,6 @@
 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.update_custom_fields_for_shopify
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.create_leave_policy_assignment_based_on_employee_current_leave_policy
 erpnext.patches.v13_0.update_pos_closing_entry_in_merge_log
@@ -296,3 +293,8 @@
 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.v13_0.update_export_type_for_gst
+erpnext.patches.v13_0.update_tds_check_field #3
+erpnext.patches.v13_0.add_custom_field_for_south_africa
+erpnext.patches.v13_0.shopify_deprecation_warning
diff --git a/erpnext/patches/v11_0/refactor_erpnext_shopify.py b/erpnext/patches/v11_0/refactor_erpnext_shopify.py
deleted file mode 100644
index 340e9fc..0000000
--- a/erpnext/patches/v11_0/refactor_erpnext_shopify.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-from frappe.installer import remove_from_installed_apps
-
-def execute():
-	frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
-	frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_tax_account')
-	frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_log')
-	frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_webhook_detail')
-
-	if 'erpnext_shopify' in frappe.get_installed_apps():
-		remove_from_installed_apps('erpnext_shopify')
-
-		frappe.delete_doc("Module Def", 'erpnext_shopify')
-
-		frappe.db.commit()
-
-		frappe.db.sql("truncate `tabShopify Log`")
-
-		setup_app_type()
-	else:
-		disable_shopify()
-
-def setup_app_type():
-	try:
-		shopify_settings = frappe.get_doc("Shopify Settings")
-		shopify_settings.app_type = 'Private'
-		shopify_settings.update_price_in_erpnext_price_list = 0 if getattr(shopify_settings, 'push_prices_to_shopify', None) else 1
-		shopify_settings.flags.ignore_mandatory = True
-		shopify_settings.ignore_permissions = True
-		shopify_settings.save()
-	except Exception:
-		frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
-		frappe.log_error(frappe.get_traceback())
-
-def disable_shopify():
-	# due to frappe.db.set_value wrongly written and enable_shopify being default 1
-	# Shopify Settings isn't properly configured and leads to error
-	shopify = frappe.get_doc('Shopify Settings')
-
-	if shopify.app_type == "Public" or shopify.app_type == None or \
-		(shopify.enable_shopify and not (shopify.shopify_url or shopify.api_key)):
-		frappe.db.set_value("Shopify Settings", None, "enable_shopify", 0)
diff --git a/erpnext/patches/v12_0/set_default_shopify_app_type.py b/erpnext/patches/v12_0/set_default_shopify_app_type.py
deleted file mode 100644
index d040ea7..0000000
--- a/erpnext/patches/v12_0/set_default_shopify_app_type.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from __future__ import unicode_literals
-import frappe
-
-def execute():
-	frappe.reload_doc('erpnext_integrations', 'doctype', 'shopify_settings')
-	frappe.db.set_value('Shopify Settings', None, 'app_type', 'Private')
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
new file mode 100644
index 0000000..f882fde
--- /dev/null
+++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
@@ -0,0 +1,13 @@
+# Copyright (c) 2020, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+from erpnext.regional.south_africa.setup import make_custom_fields
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'South Africa'})
+	if not company:
+		return
+
+	make_custom_fields()
diff --git a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
index 48999e6..0d8109c 100644
--- a/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
+++ b/erpnext/patches/v13_0/add_missing_fg_item_for_stock_entry.py
@@ -10,6 +10,7 @@
 	if not frappe.db.has_column('Work Order', 'has_batch_no'):
 		return
 
+	frappe.reload_doc('manufacturing', 'doctype', 'manufacturing_settings')
 	if cint(frappe.db.get_single_value('Manufacturing Settings', 'make_serial_no_batch_from_work_order')):
 		return
 
@@ -29,19 +30,20 @@
 		return
 
 	repost_stock_entries = []
+
 	stock_entries = frappe.db.sql_list('''
 		SELECT
 			se.name
 		FROM
 			`tabStock Entry` se
 		WHERE
-			se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in {work_orders}
+			se.purpose = 'Manufacture' and se.docstatus < 2 and se.work_order in %s
 			and not exists(
 				select name from `tabStock Entry Detail` sed where sed.parent = se.name and sed.is_finished_item = 1
 			)
-		Order BY
+		ORDER BY
 			se.posting_date, se.posting_time
-	'''.format(work_orders=tuple(work_orders)))
+	''',  (work_orders,))
 
 	if stock_entries:
 		print('Length of stock entries', len(stock_entries))
@@ -107,4 +109,4 @@
 		"company": doc.company
 	})
 
-	create_repost_item_valuation_entry(args)
\ No newline at end of file
+	create_repost_item_valuation_entry(args)
diff --git a/erpnext/patches/v13_0/rename_issue_doctype_fields.py b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
index fa1dfed..41c51c3 100644
--- a/erpnext/patches/v13_0/rename_issue_doctype_fields.py
+++ b/erpnext/patches/v13_0/rename_issue_doctype_fields.py
@@ -37,7 +37,7 @@
 
 	if frappe.db.exists('DocType', 'Opportunity'):
 		opportunities = frappe.db.get_all('Opportunity', fields=['name', 'mins_to_first_response'], order_by='creation desc')
-		frappe.reload_doc('crm', 'doctype', 'opportunity')
+		frappe.reload_doctype('Opportunity', force=True)
 		rename_field('Opportunity', 'mins_to_first_response', 'first_response_time')
 
 		# change fieldtype to duration
diff --git a/erpnext/patches/v13_0/shopify_deprecation_warning.py b/erpnext/patches/v13_0/shopify_deprecation_warning.py
new file mode 100644
index 0000000..245d1a9
--- /dev/null
+++ b/erpnext/patches/v13_0/shopify_deprecation_warning.py
@@ -0,0 +1,10 @@
+import click
+
+
+def execute():
+
+	click.secho(
+		"Shopify 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/ecommerce_integrations",
+		fg="yellow",
+	)
diff --git a/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
new file mode 100644
index 0000000..eae5ff6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_amt_in_work_order_required_items.py
@@ -0,0 +1,10 @@
+import frappe
+
+def execute():
+	""" Correct amount in child table of required items table."""
+
+	frappe.reload_doc("manufacturing", "doctype", "work_order")
+	frappe.reload_doc("manufacturing", "doctype", "work_order_item")
+
+	frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
+
diff --git a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py b/erpnext/patches/v13_0/update_custom_fields_for_shopify.py
deleted file mode 100644
index f1d2ea2..0000000
--- a/erpnext/patches/v13_0/update_custom_fields_for_shopify.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-# MIT License. See license.txt
-
-from __future__ import unicode_literals
-import frappe
-from erpnext.erpnext_integrations.doctype.shopify_settings.shopify_settings import setup_custom_fields
-
-def execute():
-	if frappe.db.get_single_value('Shopify Settings', 'enable_shopify'):
-		setup_custom_fields()
diff --git a/erpnext/patches/v13_0/update_export_type_for_gst.py b/erpnext/patches/v13_0/update_export_type_for_gst.py
new file mode 100644
index 0000000..478a2a6
--- /dev/null
+++ b/erpnext/patches/v13_0/update_export_type_for_gst.py
@@ -0,0 +1,24 @@
+import frappe
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	# Update custom fields
+	fieldname = frappe.db.get_value('Custom Field', {'dt': 'Customer', 'fieldname': 'export_type'})
+	if fieldname:
+		frappe.db.set_value('Custom Field', fieldname, 'default', '')
+
+	fieldname = frappe.db.get_value('Custom Field', {'dt': 'Supplier', 'fieldname': 'export_type'})
+	if fieldname:
+		frappe.db.set_value('Custom Field', fieldname, 'default', '')
+
+	# Update Customer/Supplier Masters
+	frappe.db.sql("""
+		UPDATE `tabCustomer` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas', 'Deemed Export')
+	""")
+
+	frappe.db.sql("""
+		UPDATE `tabSupplier` set export_type = '' WHERE gst_category NOT IN ('SEZ', 'Overseas')
+	""")
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_tds_check_field.py b/erpnext/patches/v13_0/update_tds_check_field.py
new file mode 100644
index 0000000..3d14958
--- /dev/null
+++ b/erpnext/patches/v13_0/update_tds_check_field.py
@@ -0,0 +1,9 @@
+import frappe
+
+def execute():
+	if frappe.db.has_table("Tax Withholding Category") \
+		and frappe.db.has_column("Tax Withholding Category", "round_off_tax_amount"):
+		frappe.db.sql("""
+			UPDATE `tabTax Withholding Category` set round_off_tax_amount = 0
+			WHERE round_off_tax_amount IS NULL
+		""")
\ No newline at end of file
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index ebeddf9..b978cbe 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -7,6 +7,7 @@
 from frappe.model.document import Document
 from frappe import _, bold
 from frappe.utils import getdate, date_diff, comma_and, formatdate
+from erpnext.hr.utils import validate_active_employee
 
 class AdditionalSalary(Document):
 	def on_submit(self):
@@ -19,6 +20,7 @@
 		self.update_employee_referral(cancel=True)
 
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_dates()
 		self.validate_salary_structure()
 		self.validate_recurring_additional_salary_overlap()
@@ -110,11 +112,11 @@
 		no_of_days = date_diff(getdate(end_date), getdate(start_date)) + 1
 		return amount_per_day * no_of_days
 
+@frappe.whitelist()
 def get_additional_salaries(employee, start_date, end_date, component_type):
 	additional_salary_list = frappe.db.sql("""
-		select name, salary_component as component, type, amount,
-		overwrite_salary_structure_amount as overwrite,
-		deduct_full_tax_on_selected_payroll_date
+		select name, salary_component as component, type, amount, overwrite_salary_structure_amount as overwrite,
+		deduct_full_tax_on_selected_payroll_date, is_recurring
 		from `tabAdditional Salary`
 		where employee=%(employee)s
 			and docstatus = 1
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
index 27df30a..5ebe514 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.py
@@ -9,10 +9,11 @@
 from frappe.model.document import Document
 from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period_days, get_period_factor
 from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
-from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount
+from erpnext.hr.utils import get_sal_slip_total_benefit_given, get_holidays_for_employee, get_previous_claimed_amount, validate_active_employee
 
 class EmployeeBenefitApplication(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_duplicate_on_payroll_period()
 		if not self.max_benefits:
 			self.max_benefits = get_max_benefits_remaining(self.employee, self.date, self.payroll_period)
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
index d9937a7..c6713f3 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.py
@@ -8,12 +8,13 @@
 from frappe.utils import flt
 from frappe.model.document import Document
 from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_max_benefits
-from erpnext.hr.utils import get_previous_claimed_amount
+from erpnext.hr.utils import get_previous_claimed_amount, validate_active_employee
 from erpnext.payroll.doctype.payroll_period.payroll_period import get_payroll_period
 from erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment import get_assigned_salary_structure
 
 class EmployeeBenefitClaim(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		max_benefits = get_max_benefits(self.employee, self.claim_date)
 		if not max_benefits or max_benefits <= 0:
 			frappe.throw(_("Employee {0} has no maximum benefit amount").format(self.employee))
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
index ead3db1..6b918ba 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.py
@@ -6,9 +6,11 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from erpnext.hr.utils import validate_active_employee
 
 class EmployeeIncentive(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		self.validate_salary_structure()
 
 	def validate_salary_structure(self):
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
index fb71a28..e11d60a 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.py
@@ -8,11 +8,12 @@
 from frappe import _
 from frappe.utils import flt
 from frappe.model.mapper import get_mapped_doc
-from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
+from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
 	calculate_annual_eligible_hra_exemption, validate_duplicate_exemption_for_payroll_period
 
 class EmployeeTaxExemptionDeclaration(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		validate_tax_declaration(self.declarations)
 		validate_duplicate_exemption_for_payroll_period(self.doctype, self.name, self.payroll_period, self.employee)
 		self.set_total_declared_amount()
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
index 5bc33a6..8131ae0 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.py
@@ -7,11 +7,12 @@
 from frappe.model.document import Document
 from frappe import _
 from frappe.utils import flt
-from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, \
+from erpnext.hr.utils import validate_tax_declaration, get_total_exemption_amount, validate_active_employee, \
 	calculate_hra_exemption_for_period, validate_duplicate_exemption_for_payroll_period
 
 class EmployeeTaxExemptionProofSubmission(Document):
 	def validate(self):
+		validate_active_employee(self.employee)
 		validate_tax_declaration(self.tax_exemption_proofs)
 		self.set_total_actual_amount()
 		self.set_total_exemption_amount()
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
index 049ea26..055bea7 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.py
@@ -7,11 +7,10 @@
 from frappe.model.document import Document
 from frappe import _
 from frappe.utils import getdate
-
+from erpnext.hr.utils import validate_active_employee
 class RetentionBonus(Document):
 	def validate(self):
-		if frappe.get_value('Employee', self.employee, 'status') != 'Active':
-			frappe.throw(_('Cannot create Retention Bonus for Left or Inactive Employees'))
+		validate_active_employee(self.employee)
 		if getdate(self.bonus_payment_date) < getdate():
 			frappe.throw(_('Bonus Payment Date cannot be a past date'))
 
diff --git a/erpnext/payroll/doctype/salary_component/salary_component.js b/erpnext/payroll/doctype/salary_component/salary_component.js
index dbf7514..e9e6f81 100644
--- a/erpnext/payroll/doctype/salary_component/salary_component.js
+++ b/erpnext/payroll/doctype/salary_component/salary_component.js
@@ -4,11 +4,18 @@
 frappe.ui.form.on('Salary Component', {
 	setup: function(frm) {
 		frm.set_query("account", "accounts", function(doc, cdt, cdn) {
-			var d = locals[cdt][cdn];
+			let d = frappe.get_doc(cdt, cdn);
+
+			let root_type = "Liability";
+			if (frm.doc.type == "Deduction") {
+				root_type = "Expense";
+			}
+
 			return {
 				filters: {
 					"is_group": 0,
-					"company": d.company
+					"company": d.company,
+					"root_type": root_type
 				}
 			};
 		});
diff --git a/erpnext/payroll/doctype/salary_detail/salary_detail.json b/erpnext/payroll/doctype/salary_detail/salary_detail.json
index 393f647..97608d7 100644
--- a/erpnext/payroll/doctype/salary_detail/salary_detail.json
+++ b/erpnext/payroll/doctype/salary_detail/salary_detail.json
@@ -12,6 +12,7 @@
   "year_to_date",
   "section_break_5",
   "additional_salary",
+  "is_recurring_additional_salary",
   "statistical_component",
   "depends_on_payment_days",
   "exempted_from_income_tax",
@@ -235,11 +236,19 @@
    "label": "Year To Date",
    "options": "currency",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.parenttype=='Salary Slip' && doc.parentfield=='earnings' && doc.additional_salary",
+   "fieldname": "is_recurring_additional_salary",
+   "fieldtype": "Check",
+   "label": "Is Recurring Additional Salary",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2021-01-14 13:39:15.847158",
+ "modified": "2021-03-14 13:39:15.847158",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Detail",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index f82b0d5..b39cef8 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -7,18 +7,19 @@
 
 from frappe.utils import add_days, cint, cstr, flt, getdate, rounded, date_diff, money_in_words, formatdate, get_first_day
 from frappe.model.naming import make_autoname
+from frappe.utils.background_jobs import enqueue
 
 from frappe import msgprint, _
 from erpnext.payroll.doctype.payroll_entry.payroll_entry import get_start_end_dates
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from erpnext.utilities.transaction_base import TransactionBase
-from frappe.utils.background_jobs import enqueue
 from erpnext.payroll.doctype.additional_salary.additional_salary import get_additional_salaries
 from erpnext.payroll.doctype.payroll_period.payroll_period import get_period_factor, get_payroll_period
 from erpnext.payroll.doctype.employee_benefit_application.employee_benefit_application import get_benefit_component_amount
 from erpnext.payroll.doctype.employee_benefit_claim.employee_benefit_claim import get_benefit_claim_amount, get_last_payroll_period_benefits
 from erpnext.loan_management.doctype.loan_repayment.loan_repayment import calculate_amounts, create_repayment_entry
 from erpnext.accounts.utils import get_fiscal_year
+from erpnext.hr.utils import validate_active_employee
 from six import iteritems
 
 class SalarySlip(TransactionBase):
@@ -39,6 +40,7 @@
 
 	def validate(self):
 		self.status = self.get_status()
+		validate_active_employee(self.employee)
 		self.validate_dates()
 		self.check_existing()
 		if not self.salary_slip_based_on_timesheet:
@@ -616,7 +618,8 @@
 				get_salary_component_data(additional_salary.component),
 				additional_salary.amount,
 				component_type,
-				additional_salary
+				additional_salary,
+				is_recurring = additional_salary.is_recurring
 			)
 
 	def add_tax_components(self, payroll_period):
@@ -637,7 +640,7 @@
 			tax_row = get_salary_component_data(d)
 			self.update_component_row(tax_row, tax_amount, "deductions")
 
-	def update_component_row(self, component_data, amount, component_type, additional_salary=None):
+	def update_component_row(self, component_data, amount, component_type, additional_salary=None, is_recurring = 0):
 		component_row = None
 		for d in self.get(component_type):
 			if d.salary_component != component_data.salary_component:
@@ -675,6 +678,7 @@
 				component_row.set(attr, component_data.get(attr))
 
 		if additional_salary:
+			component_row.is_recurring_additional_salary = is_recurring
 			component_row.default_amount = 0
 			component_row.additional_amount = amount
 			component_row.additional_salary = additional_salary.name
@@ -708,6 +712,7 @@
 		# get remaining numbers of sub-period (period for which one salary is processed)
 		remaining_sub_periods = get_period_factor(self.employee,
 			self.start_date, self.end_date, self.payroll_frequency, payroll_period)[1]
+
 		# get taxable_earnings, paid_taxes for previous period
 		previous_taxable_earnings = self.get_taxable_earnings_for_prev_period(payroll_period.start_date,
 			self.start_date, tax_slab.allow_tax_exemption)
@@ -867,8 +872,16 @@
 
 			if earning.is_tax_applicable:
 				if additional_amount:
-					taxable_earnings += (amount - additional_amount)
-					additional_income += additional_amount
+					if not earning.is_recurring_additional_salary:
+						taxable_earnings += (amount - additional_amount)
+						additional_income += additional_amount
+					else:
+						to_date = frappe.db.get_value("Additional Salary", earning.additional_salary, 'to_date')
+						period = (getdate(to_date).month - getdate(self.start_date).month) + 1
+						if period > 0:
+							taxable_earnings += (amount - additional_amount) * period
+							additional_income += additional_amount * period
+
 					if earning.deduct_full_tax_on_selected_payroll_date:
 						additional_income_with_full_tax += additional_amount
 					continue
diff --git a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
index 374dd7e..3957d83 100644
--- a/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/test_salary_structure.py
@@ -129,7 +129,7 @@
 		"earnings": make_earning_salary_component(setup=True,  test_tax=test_tax, company_list=["_Test Company"]),
 		"deductions": make_deduction_salary_component(setup=True, test_tax=test_tax, company_list=["_Test Company"]),
 		"payroll_frequency": payroll_frequency,
-		"payment_account": get_random("Account", filters={"account_currency": currency}),
+		"payment_account": get_random("Account", filters={'account_currency': currency}),
 		"currency": currency
 	}
 	if other_details and isinstance(other_details, dict):
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index c8fbe0b..1e4b2b0 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -14,6 +14,7 @@
 from erpnext.hr.doctype.holiday_list.holiday_list import is_holiday
 from frappe.model.document import Document
 from erpnext.education.doctype.student_attendance.student_attendance import get_holiday_list
+from erpnext.controllers.employee_boarding_controller import update_employee_boarding_status
 
 class Project(Document):
 	def get_feed(self):
@@ -37,6 +38,7 @@
 		self.send_welcome_email()
 		self.update_costing()
 		self.update_percent_complete()
+		update_employee_boarding_status(self)
 
 	def copy_from_template(self):
 		'''
@@ -132,6 +134,7 @@
 	def update_project(self):
 		'''Called externally by Task'''
 		self.update_percent_complete()
+		update_employee_boarding_status(self)
 		self.update_costing()
 		self.db_update()
 
diff --git a/erpnext/projects/doctype/timesheet/timesheet.py b/erpnext/projects/doctype/timesheet/timesheet.py
index c8bd80f..ae38d4c 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.py
+++ b/erpnext/projects/doctype/timesheet/timesheet.py
@@ -15,12 +15,15 @@
 	WorkstationHolidayError)
 from erpnext.manufacturing.doctype.manufacturing_settings.manufacturing_settings import get_mins_between_operations
 from erpnext.setup.utils import get_exchange_rate
+from erpnext.hr.utils import validate_active_employee
 
 class OverlapError(frappe.ValidationError): pass
 class OverWorkLoggedError(frappe.ValidationError): pass
 
 class Timesheet(Document):
 	def validate(self):
+		if self.employee:
+			validate_active_employee(self.employee)
 		self.set_employee_name()
 		self.set_status()
 		self.validate_dates()
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index 142fe79..239fbb9 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -16,7 +16,7 @@
 				doctype: "Bank Transaction",
 				filters: { name: this.bank_transaction_name },
 				fieldname: [
-					"date",
+					"date as reference_date",
 					"deposit",
 					"withdrawal",
 					"currency",
diff --git a/erpnext/public/js/contact.js b/erpnext/public/js/contact.js
new file mode 100644
index 0000000..41a0e8a
--- /dev/null
+++ b/erpnext/public/js/contact.js
@@ -0,0 +1,16 @@
+
+
+frappe.ui.form.on("Contact", {
+	refresh(frm) {
+		frm.set_query('link_doctype', "links", function() {
+			return {
+				query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
+				filters: {
+					fieldtype: ["in", ["HTML", "Text Editor"]],
+					fieldname: ["in", ["contact_html", "company_description"]],
+				}
+			};
+		});
+		frm.refresh_field("links");
+	}
+});
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
index cc2d9f0..54e4886 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -3,7 +3,7 @@
 
 frappe.ui.form.on('E Invoice Settings', {
 	refresh(frm) {
-		const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
+		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/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 6415204..0ee5b09 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -214,9 +214,8 @@
 
 			for d in item_details:
 				if d.item_code not in self.invoice_items.get(d.parent, {}):
-					self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
-						sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
-							if i.item_code == d.item_code and i.parent == d.parent))
+					self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+					self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
 
 				if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
 					self.is_nil_exempt.append(d.item_code)
@@ -281,9 +280,15 @@
 		if self.get('invoice_items'):
 			# Build itemised tax for export invoices, nil and exempted where tax table is blank
 			for invoice, items in iteritems(self.invoice_items):
-				if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')
-					== "Without Payment of Tax"):
+				if invoice not in self.items_based_on_tax_rate and self.invoice_detail_map.get(invoice, {}).get('export_type') \
+					== "Without Payment of Tax" and self.invoice_detail_map.get(invoice, {}).get('gst_category') == "Overseas":
 					self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
+				else:
+					for item in items.keys():
+						if item in self.is_nil_exempt + self.is_non_gst and \
+							item not in self.items_based_on_tax_rate.get(invoice, {}).get(0, []):
+								self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, [])
+								self.items_based_on_tax_rate[invoice][0].append(item)
 
 	def set_outward_taxable_supplies(self):
 		inter_state_supply_details = {}
@@ -322,6 +327,9 @@
 									inter_state_supply_details[(gst_category, place_of_supply)]['txval'] += taxable_value
 									inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
 
+			if self.invoice_cess.get(inv):
+				self.report_dict['sup_details']['osup_det']['csamt'] += flt(self.invoice_cess.get(inv), 2)
+
 		self.set_inter_state_supply(inter_state_supply_details)
 
 	def set_supplies_liable_to_reverse_charge(self):
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py b/erpnext/regional/doctype/south_africa_vat_settings/__init__.py
similarity index 100%
rename from erpnext/erpnext_integrations/doctype/shopify_settings/__init__.py
rename to erpnext/regional/doctype/south_africa_vat_settings/__init__.py
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js
new file mode 100644
index 0000000..e37a61a
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.js
@@ -0,0 +1,23 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('South Africa VAT Settings', {
+	refresh: function(frm) {
+		frm.set_query("company", function() {
+			return {
+				filters: {
+					country: "South Africa",
+				}
+			};
+		});
+		frm.set_query("account", "vat_accounts", function() {
+			return {
+				filters: {
+					company: frm.doc.company,
+					account_type: "Tax",
+					is_group: 0
+				}
+			};
+		});
+	}
+});
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json
new file mode 100644
index 0000000..8a51829
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.json
@@ -0,0 +1,76 @@
+{
+ "actions": [],
+ "autoname": "field:company",
+ "creation": "2021-07-08 22:34:33.668015",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "company",
+  "vat_accounts"
+ ],
+ "fields": [
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "vat_accounts",
+   "fieldtype": "Table",
+   "label": "VAT Accounts",
+   "options": "South Africa VAT Account",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-07-14 02:17:52.476762",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "South Africa VAT Settings",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Auditor",
+   "share": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py
new file mode 100644
index 0000000..d74154b
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/south_africa_vat_settings.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+class SouthAfricaVATSettings(Document):
+	pass
diff --git a/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py
new file mode 100644
index 0000000..1c36652
--- /dev/null
+++ b/erpnext/regional/doctype/south_africa_vat_settings/test_south_africa_vat_settings.py
@@ -0,0 +1,8 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+import unittest
+
+class TestSouthAfricaVATSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 81c7a6b..f8a230d 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -969,7 +969,7 @@
 			"attached_to_doctype": doctype,
 			"attached_to_name": docname,
 			"attached_to_field": "qrcode_image",
-			"is_private": 1,
+			"is_private": 0,
 			"content": qr_image.getvalue()})
 		_file.save()
 		frappe.db.commit()
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 9265460..e9372f9 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -641,7 +641,6 @@
 				'label': 'Export Type',
 				'fieldtype': 'Select',
 				'insert_after': 'gst_category',
-				'default': 'Without Payment of Tax',
 				'depends_on':'eval:in_list(["SEZ", "Overseas"], doc.gst_category)',
 				'options': '\nWith Payment of Tax\nWithout Payment of Tax'
 			}
@@ -660,7 +659,6 @@
 				'label': 'Export Type',
 				'fieldtype': 'Select',
 				'insert_after': 'gst_category',
-				'default': 'Without Payment of Tax',
 				'depends_on':'eval:in_list(["SEZ", "Overseas", "Deemed Export"], doc.gst_category)',
 				'options': '\nWith Payment of Tax\nWithout Payment of Tax'
 			}
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index fbe47d0..88c350a 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -859,4 +859,15 @@
 
 		depreciation_amount = flt(depreciable_value * (flt(rate_of_depreciation) / 100))
 
-	return depreciation_amount
\ No newline at end of file
+	return depreciation_amount
+
+def set_item_tax_from_hsn_code(item):
+	if not item.taxes and item.gst_hsn_code: 
+		hsn_doc = frappe.get_doc("GST HSN Code", item.gst_hsn_code)
+
+		for tax in hsn_doc.taxes:
+			item.append('taxes', {
+				'item_tax_template': tax.item_tax_template,
+				'tax_category': tax.tax_category,
+				'valid_from': tax.valid_from
+			})
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index cfcb8c3..4b73094 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -217,9 +217,8 @@
 
 		for d in items:
 			if d.item_code not in self.invoice_items.get(d.parent, {}):
-				self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
-					sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
-						if i.item_code == d.item_code and i.parent == d.parent))
+				self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, 0.0)
+				self.invoice_items[d.parent][d.item_code] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
 
 				item_tax_rate = {}
 
@@ -287,7 +286,8 @@
 		# Build itemised tax for export invoices where tax table is blank
 		for invoice, items in iteritems(self.invoice_items):
 			if invoice not in self.items_based_on_tax_rate and invoice not in unidentified_gst_accounts_invoice \
-				and frappe.db.get_value(self.doctype, invoice, "export_type") == "Without Payment of Tax":
+				and self.invoices.get(invoice, {}).get('export_type') == "Without Payment of Tax" \
+				and self.invoices.get(invoice, {}).get('gst_category') == "Overseas":
 					self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
 
 	def get_columns(self):
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/regional/report/vat_audit_report/__init__.py
similarity index 100%
copy from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
copy to erpnext/regional/report/vat_audit_report/__init__.py
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.js b/erpnext/regional/report/vat_audit_report/vat_audit_report.js
new file mode 100644
index 0000000..39ef9b5
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.js
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["VAT Audit Report"] = {
+	"filters": [
+		{
+			"fieldname": "company",
+			"label": __("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"reqd": 1,
+			"default": frappe.defaults.get_user_default("Company")
+		},
+		{
+			"fieldname": "from_date",
+			"label": __("From Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -2),
+			"width": "80"
+		},
+		{
+			"fieldname": "to_date",
+			"label": __("To Date"),
+			"fieldtype": "Date",
+			"reqd": 1,
+			"default": frappe.datetime.get_today()
+		}
+	]
+};
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.json b/erpnext/regional/report/vat_audit_report/vat_audit_report.json
new file mode 100644
index 0000000..8917e8f
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-07-09 11:07:43.473518",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-07-09 11:07:43.473518",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "VAT Audit Report",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "VAT Audit Report",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Auditor"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/vat_audit_report/vat_audit_report.py b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
new file mode 100644
index 0000000..f45ba01
--- /dev/null
+++ b/erpnext/regional/report/vat_audit_report/vat_audit_report.py
@@ -0,0 +1,253 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from frappe import _
+from frappe.utils import formatdate
+
+def execute(filters=None):
+	return VATAuditReport(filters).run()
+
+class VATAuditReport(object):
+
+	def __init__(self, filters=None):
+		self.filters = frappe._dict(filters or {})
+		self.columns = []
+		self.data = []
+		self.doctypes = ["Purchase Invoice", "Sales Invoice"]
+
+	def run(self):
+		self.get_sa_vat_accounts()
+		self.get_columns()
+		for doctype in self.doctypes:
+			self.select_columns = """
+			name as voucher_no,
+			posting_date, remarks"""
+			columns = ", supplier as party, credit_to as account" if doctype=="Purchase Invoice" \
+				else ", customer as party, debit_to as account"
+			self.select_columns += columns
+
+			self.get_invoice_data(doctype)
+
+			if self.invoices:
+				self.get_invoice_items(doctype)
+				self.get_items_based_on_tax_rate(doctype)
+				self.get_data(doctype)
+
+		return self.columns, self.data
+
+	def get_sa_vat_accounts(self):
+		self.sa_vat_accounts = frappe.get_list("South Africa VAT Account",
+			filters = {"parent": self.filters.company}, pluck="account")
+		if not self.sa_vat_accounts and not frappe.flags.in_test and not frappe.flags.in_migrate:
+			frappe.throw(_("Please set VAT Accounts in South Africa VAT Settings"))
+
+	def get_invoice_data(self, doctype):
+		conditions = self.get_conditions()
+		self.invoices = frappe._dict()
+
+		invoice_data = frappe.db.sql("""
+			SELECT
+				{select_columns}
+			FROM
+				`tab{doctype}`
+			WHERE
+				docstatus = 1 {where_conditions}
+				and is_opening = "No"
+			ORDER BY
+				posting_date DESC
+			""".format(select_columns=self.select_columns, doctype=doctype,
+				where_conditions=conditions), self.filters, as_dict=1)
+
+		for d in invoice_data:
+			self.invoices.setdefault(d.voucher_no, d)
+
+	def get_invoice_items(self, doctype):
+		self.invoice_items = frappe._dict()
+
+		items = frappe.db.sql("""
+			SELECT
+				item_code, parent, taxable_value, base_net_amount, is_zero_rated
+			FROM
+				`tab%s Item`
+			WHERE
+				parent in (%s)
+			""" % (doctype, ", ".join(["%s"]*len(self.invoices))), tuple(self.invoices), as_dict=1)
+		for d in items:
+			if d.item_code not in self.invoice_items.get(d.parent, {}):
+				self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, {
+					'net_amount': 0.0})
+				self.invoice_items[d.parent][d.item_code]['net_amount'] += d.get('taxable_value', 0) or d.get('base_net_amount', 0)
+				self.invoice_items[d.parent][d.item_code]['is_zero_rated'] = d.is_zero_rated
+
+	def get_items_based_on_tax_rate(self, doctype):
+		self.items_based_on_tax_rate = frappe._dict()
+		self.item_tax_rate = frappe._dict()
+		self.tax_doctype = "Purchase Taxes and Charges" if doctype=="Purchase Invoice" \
+			else "Sales Taxes and Charges"
+
+		self.tax_details = frappe.db.sql("""
+			SELECT
+				parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
+			FROM
+				`tab%s`
+			WHERE
+				parenttype = %s and docstatus = 1
+				and parent in (%s)
+			ORDER BY
+				account_head
+			""" % (self.tax_doctype, "%s", ", ".join(["%s"]*len(self.invoices.keys()))),
+			tuple([doctype] + list(self.invoices.keys())))
+
+		for parent, account, item_wise_tax_detail, tax_amount in self.tax_details:
+			if item_wise_tax_detail:
+				try:
+					if account in self.sa_vat_accounts:
+						item_wise_tax_detail = json.loads(item_wise_tax_detail)
+					else:
+						continue
+					for item_code, taxes in item_wise_tax_detail.items():
+						is_zero_rated = self.invoice_items.get(parent).get(item_code).get("is_zero_rated")
+						#to skip items with non-zero tax rate in multiple rows
+						if taxes[0] == 0 and not is_zero_rated:
+							continue
+						tax_rate, item_amount_map = self.get_item_amount_map(parent, item_code, taxes)
+
+						if tax_rate is not None:
+							rate_based_dict = self.items_based_on_tax_rate.setdefault(parent, {}) \
+								.setdefault(tax_rate, [])
+							if item_code not in rate_based_dict:
+								rate_based_dict.append(item_code)
+				except ValueError:
+					continue
+
+	def get_item_amount_map(self, parent, item_code, taxes):
+		net_amount = self.invoice_items.get(parent).get(item_code).get("net_amount")
+		tax_rate = taxes[0]
+		tax_amount = taxes[1]
+		gross_amount = net_amount + tax_amount
+		item_amount_map = self.item_tax_rate.setdefault(parent, {}) \
+			.setdefault(item_code, [])
+		amount_dict = {
+			"tax_rate": tax_rate,
+			"gross_amount": gross_amount,
+			"tax_amount": tax_amount,
+			"net_amount": net_amount
+		}
+		item_amount_map.append(amount_dict)
+
+		return tax_rate, item_amount_map
+
+	def get_conditions(self):
+		conditions = ""
+		for opts in (("company", " and company=%(company)s"),
+			("from_date", " and posting_date>=%(from_date)s"),
+			("to_date", " and posting_date<=%(to_date)s")):
+			if self.filters.get(opts[0]):
+				conditions += opts[1]
+
+		return conditions
+
+	def get_data(self, doctype):
+		consolidated_data = self.get_consolidated_data(doctype)
+		section_name = _("Purchases") if doctype == "Purchase Invoice" else _("Sales")
+
+		for rate, section in consolidated_data.items():
+			rate = int(rate)
+			label = frappe.bold(section_name + "- " + "Rate" + " " + str(rate) + "%")
+			section_head = {"posting_date": label}
+			total_gross = total_tax = total_net = 0
+			self.data.append(section_head)
+			for row in section.get("data"):
+				self.data.append(row)
+				total_gross += row["gross_amount"]
+				total_tax += row["tax_amount"]
+				total_net += row["net_amount"]
+
+			total = {
+				"posting_date": frappe.bold(_("Total")),
+				"gross_amount": total_gross,
+				"tax_amount": total_tax,
+				"net_amount": total_net,
+				"bold":1
+			}
+			self.data.append(total)
+			self.data.append({})
+
+	def get_consolidated_data(self, doctype):
+		consolidated_data_map={}
+		for inv, inv_data in self.invoices.items():
+			if self.items_based_on_tax_rate.get(inv):
+				for rate, items in self.items_based_on_tax_rate.get(inv).items():
+					consolidated_data_map.setdefault(rate, {"data": []})
+					for item in items:
+						row = {}
+						item_details = self.item_tax_rate.get(inv).get(item)
+						row["account"] = inv_data.get("account")
+						row["posting_date"] = formatdate(inv_data.get("posting_date"), "dd-mm-yyyy")
+						row["voucher_type"] = doctype
+						row["voucher_no"] = inv
+						row["remarks"] = inv_data.get("remarks")
+						row["gross_amount"]= item_details[0].get("gross_amount")
+						row["tax_amount"]= item_details[0].get("tax_amount")
+						row["net_amount"]= item_details[0].get("net_amount")
+						consolidated_data_map[rate]["data"].append(row)
+
+		return consolidated_data_map
+
+	def get_columns(self):
+		self.columns = [
+			{
+				"fieldname": "posting_date",
+				"label": "Posting Date",
+				"fieldtype": "Data",
+				"width": 200
+			},
+			{
+				"fieldname": "account",
+				"label": "Account",
+				"fieldtype": "Link",
+				"options": "Account",
+				"width": 150
+			},
+			{
+				"fieldname": "voucher_type",
+				"label": "Voucher Type",
+				"fieldtype": "Data",
+				"width": 140,
+				"hidden": 1
+			},
+			{
+				"fieldname": "voucher_no",
+				"label": "Reference",
+				"fieldtype": "Dynamic Link",
+				"options": "voucher_type",
+				"width": 150
+			},
+			{
+				"fieldname": "remarks",
+				"label": "Details",
+				"fieldtype": "Data",
+				"width": 150
+			},
+			{
+				"fieldname": "net_amount",
+				"label": "Net Amount",
+				"fieldtype": "Currency",
+				"width": 150
+			},
+			{
+				"fieldname": "tax_amount",
+				"label": "Tax Amount",
+				"fieldtype": "Currency",
+				"width": 150
+			},
+			{
+				"fieldname": "gross_amount",
+				"label": "Gross Amount",
+				"fieldtype": "Currency",
+				"width": 150
+			},
+		]
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/regional/south_africa/__init__.py
similarity index 100%
copy from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
copy to erpnext/regional/south_africa/__init__.py
diff --git a/erpnext/regional/south_africa/setup.py b/erpnext/regional/south_africa/setup.py
new file mode 100644
index 0000000..ac783b8
--- /dev/null
+++ b/erpnext/regional/south_africa/setup.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+
+# import frappe, os, json
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.permissions import add_permission, update_permission_property
+
+def setup(company=None, patch=True):
+	add_permissions()
+
+def make_custom_fields(update=True):
+	is_zero_rated = dict(fieldname='is_zero_rated', label='Is Zero Rated',
+		fieldtype='Check', fetch_from='item_code.is_zero_rated',
+		insert_after='description', print_hide=1)
+	custom_fields = {
+		'Item': [
+			dict(fieldname='is_zero_rated', label='Is Zero Rated',
+				fieldtype='Check', insert_after='item_group',
+				print_hide=1)
+		],
+		'Sales Invoice Item': is_zero_rated,
+		'Purchase Invoice Item': is_zero_rated
+	}
+	
+	create_custom_fields(custom_fields, update=update)
+
+def add_permissions():
+	"""Add Permissions for South Africa VAT Settings and South Africa VAT Account"""
+	for doctype in ('South Africa VAT Settings', 'South Africa VAT Account'):
+		add_permission(doctype, 'All', 0)
+		for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
+			add_permission(doctype, role, 0)
+			update_permission_property(doctype, role, 0, 'write', 1)
+			update_permission_property(doctype, role, 0, 'create', 1)
\ No newline at end of file
diff --git a/erpnext/selling/doctype/campaign/README.md b/erpnext/selling/doctype/campaign/README.md
deleted file mode 100644
index a837318..0000000
--- a/erpnext/selling/doctype/campaign/README.md
+++ /dev/null
@@ -1 +0,0 @@
-Sales campaign / promotion, like special discount, exhibition, newsletter etc.
\ No newline at end of file
diff --git a/erpnext/selling/doctype/campaign/__init__.py b/erpnext/selling/doctype/campaign/__init__.py
deleted file mode 100644
index baffc48..0000000
--- a/erpnext/selling/doctype/campaign/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from __future__ import unicode_literals
diff --git a/erpnext/selling/doctype/campaign/campaign.js b/erpnext/selling/doctype/campaign/campaign.js
deleted file mode 100644
index 72a90d0..0000000
--- a/erpnext/selling/doctype/campaign/campaign.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-
-frappe.ui.form.on("Campaign", "refresh", function(frm) {
-	erpnext.toggle_naming_series();
-	if(frm.doc.__islocal) {
-		frm.toggle_display("naming_series", frappe.boot.sysdefaults.campaign_naming_by=="Naming Series");
-	}
-	else{
-		cur_frm.add_custom_button(__("View Leads"), function() {
-			frappe.route_options = {"source": "Campaign","campaign_name": frm.doc.name}
-			frappe.set_route("List", "Lead");
-		}, "fa fa-list", true);
-	}
-})
diff --git a/erpnext/selling/doctype/campaign/campaign_dashboard.py b/erpnext/selling/doctype/campaign/campaign_dashboard.py
deleted file mode 100644
index 3cef560..0000000
--- a/erpnext/selling/doctype/campaign/campaign_dashboard.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from __future__ import unicode_literals
-from frappe import _
-
-def get_data():
-	return {
-		'fieldname': 'campaign_name',
-		'transactions': [
-			{
-				'label': _('Email Campaigns'),
-				'items': ['Email Campaign']
-			},
-			{
-				'label': _('Social Media Campaigns'),
-				'items': ['Social Media Post']
-			}
-		]
-	}
diff --git a/erpnext/selling/doctype/campaign/test_campaign.py b/erpnext/selling/doctype/campaign/test_campaign.py
deleted file mode 100644
index 4d062ff..0000000
--- a/erpnext/selling/doctype/campaign/test_campaign.py
+++ /dev/null
@@ -1,7 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: GNU General Public License v3. See license.txt
-from __future__ import unicode_literals
-
-
-import frappe
-test_records = frappe.get_test_records('Campaign')
\ No newline at end of file
diff --git a/erpnext/selling/doctype/campaign/test_records.json b/erpnext/selling/doctype/campaign/test_records.json
deleted file mode 100644
index 625d3b3..0000000
--- a/erpnext/selling/doctype/campaign/test_records.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
-  "campaign_name": "_Test Campaign", 
-  "doctype": "Campaign"
- }, 
- {
-  "campaign_name": "_Test Campaign 1", 
-  "doctype": "Campaign"
- }
-]
\ No newline at end of file
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 6e36d28..a4a4b0e 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -564,7 +564,6 @@
 		)
 
 		set_dynamic_rate_header_width();
-		this.scroll_to_item($item_to_update);
 
 		function set_dynamic_rate_header_width() {
 			const rate_cols = Array.from(me.$cart_items_wrapper.find(".item-rate-amount"));
@@ -639,12 +638,6 @@
 		$($img).parent().replaceWith(`<div class="item-image item-abbr">${item_abbr}</div>`);
 	}
 
-	scroll_to_item($item) {
-		if ($item.length === 0) return;
-		const scrollTop = $item.offset().top - this.$cart_items_wrapper.offset().top + this.$cart_items_wrapper.scrollTop();
-		this.$cart_items_wrapper.animate({ scrollTop });
-	}
-
 	update_selector_value_in_cart_item(selector, value, item) {
 		const $item_to_update = this.get_cart_item(item);
 		$item_to_update.attr(`data-${selector}`, escape(value));
diff --git a/erpnext/selling/page/sales_funnel/sales_funnel.css b/erpnext/selling/page/sales_funnel/sales_funnel.css
index 89e904f..455d37c 100644
--- a/erpnext/selling/page/sales_funnel/sales_funnel.css
+++ b/erpnext/selling/page/sales_funnel/sales_funnel.css
@@ -1,3 +1,4 @@
 .funnel-wrapper {
 	margin: 15px;
+	width: 100%;
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
index c5c01c5..4ff2dd7 100644
--- a/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
+++ b/erpnext/setup/doctype/currency_exchange/test_currency_exchange.py
@@ -62,12 +62,12 @@
 
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
 		self.assertEqual(exchange_rate, 62.9)
-		
-		# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+
+		# Exchange rate as on 15th Dec, 2015
 		self.clear_cache()
 		exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_selling")
 		self.assertFalse(exchange_rate == 60)
-		self.assertEqual(flt(exchange_rate, 3), 66.894)
+		self.assertEqual(flt(exchange_rate, 3), 66.999)
 
 	def test_exchange_rate_strict(self):
 		# strict currency settings
@@ -77,28 +77,17 @@
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-01", "for_buying")
 		self.assertEqual(exchange_rate, 60.0)
 
-		# Will fetch from fixer.io
 		self.clear_cache()
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
-		self.assertEqual(flt(exchange_rate, 3), 67.79)
+		self.assertEqual(flt(exchange_rate, 3), 67.235)
 
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-30", "for_selling")
 		self.assertEqual(exchange_rate, 62.9)
 
-		# Exchange rate as on 15th Dec, 2015, should be fetched from fixer.io
+		# Exchange rate as on 15th Dec, 2015
 		self.clear_cache()
 		exchange_rate = get_exchange_rate("USD", "INR", "2015-12-15", "for_buying")
-		self.assertEqual(flt(exchange_rate, 3), 66.894)
-
-		exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-10", "for_selling")
-		self.assertEqual(exchange_rate, 65.1)
-
-		# NGN is not available on fixer.io so these should return 0
-		exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-09", "for_selling")
-		self.assertEqual(exchange_rate, 0)
-
-		exchange_rate = get_exchange_rate("INR", "NGN", "2016-01-11", "for_selling")
-		self.assertEqual(exchange_rate, 0)
+		self.assertEqual(flt(exchange_rate, 3), 66.999)
 
 	def test_exchange_rate_strict_switched(self):
 		# Start with allow_stale is True
@@ -111,4 +100,4 @@
 		# Will fetch from fixer.io
 		self.clear_cache()
 		exchange_rate = get_exchange_rate("USD", "INR", "2016-01-15", "for_buying")
-		self.assertEqual(flt(exchange_rate, 3), 67.79)
+		self.assertEqual(flt(exchange_rate, 3), 67.235)
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index 691d331..8a49155 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -18,7 +18,7 @@
 		doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
 		for doctype in self.doctypes_to_be_ignored:
 			if doctype.doctype_name not in doctypes_to_be_ignored_list:
-				frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it. "), 
+				frappe.throw(_("DocTypes should not be added manually to the 'Excluded DocTypes' table. You are only allowed to remove entries from it."),
 					title=_("Not Allowed"))
 
 	def before_submit(self):
@@ -31,7 +31,7 @@
 		clear_notifications()
 		self.delete_company_transactions()
 
-	def populate_doctypes_to_be_ignored_table(self):		
+	def populate_doctypes_to_be_ignored_table(self):
 		doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
 		for doctype in doctypes_to_be_ignored_list:
 			self.append('doctypes_to_be_ignored', {
@@ -74,7 +74,7 @@
 		doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
 		docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
 
-		tables = self.get_all_child_doctypes()	
+		tables = self.get_all_child_doctypes()
 		for docfield in docfields:
 			if docfield['parent'] != self.doctype:
 				no_of_docs = self.get_number_of_docs_linked_with_specified_company(docfield['parent'], docfield['fieldname'])
@@ -90,7 +90,7 @@
 					naming_series = frappe.db.get_value('DocType', docfield['parent'], 'autoname')
 					if naming_series:
 						if '#' in naming_series:
-							self.update_naming_series(naming_series, docfield['parent'])	
+							self.update_naming_series(naming_series, docfield['parent'])
 
 	def get_doctypes_to_be_ignored_list(self):
 		singles = frappe.get_all('DocType', filters = {'issingle': 1}, pluck = 'name')
@@ -101,9 +101,9 @@
 		return doctypes_to_be_ignored_list
 
 	def get_doctypes_with_company_field(self, doctypes_to_be_ignored_list):
-		docfields = frappe.get_all('DocField', 
+		docfields = frappe.get_all('DocField',
 			filters = {
-				'fieldtype': 'Link', 
+				'fieldtype': 'Link',
 				'options': 'Company',
 				'parent': ['not in', doctypes_to_be_ignored_list]},
 			fields=['parent', 'fieldname'])
@@ -121,7 +121,7 @@
 			self.append('doctypes', {
 				'doctype_name' : doctype,
 				'no_of_docs' : no_of_docs
-			})		
+			})
 
 	def delete_child_tables(self, doctype, company_fieldname):
 		parent_docs_to_be_deleted = frappe.get_all(doctype, {
@@ -129,7 +129,7 @@
 		}, pluck = 'name')
 
 		child_tables = frappe.get_all('DocField', filters = {
-			'fieldtype': 'Table', 
+			'fieldtype': 'Table',
 			'parent': doctype
 		}, pluck = 'options')
 
diff --git a/erpnext/setup/utils.py b/erpnext/setup/utils.py
index d5dbd4c..e49259e 100644
--- a/erpnext/setup/utils.py
+++ b/erpnext/setup/utils.py
@@ -93,21 +93,21 @@
 
 	try:
 		cache = frappe.cache()
-		key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date,from_currency, to_currency)
+		key = "currency_exchange_rate_{0}:{1}:{2}".format(transaction_date, from_currency, to_currency)
 		value = cache.get(key)
 
 		if not value:
 			import requests
-			api_url = "https://frankfurter.app/{0}".format(transaction_date)
+			api_url = "https://api.exchangerate.host/convert"
 			response = requests.get(api_url, params={
-				"base": from_currency,
-				"symbols": to_currency
+				"date": transaction_date,
+				"from": from_currency,
+				"to": to_currency
 			})
 			# expire in 6 hours
 			response.raise_for_status()
-			value = response.json()["rates"][to_currency]
-
-			cache.set_value(key, value, expires_in_sec=6 * 60 * 60)
+			value = response.json()["result"]
+			cache.setex(name=key, time=21600, value=flt(value))
 		return flt(value)
 	except:
 		frappe.log_error(title="Get Exchange Rate")
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index b6eef6c..b37ae3f 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -162,19 +162,19 @@
 
 		out = float(frappe.db.sql("""select sum(actual_qty)
 			from `tabStock Ledger Entry`
-			where warehouse=%s and batch_no=%s {0}""".format(cond),
+			where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(cond),
 			(warehouse, batch_no))[0][0] or 0)
 
 	if batch_no and not warehouse:
 		out = frappe.db.sql('''select warehouse, sum(actual_qty) as qty
 			from `tabStock Ledger Entry`
-			where batch_no=%s
+			where is_cancelled = 0 and batch_no=%s
 			group by warehouse''', batch_no, as_dict=1)
 
 	if not batch_no and item_code and warehouse:
 		out = frappe.db.sql('''select batch_no, sum(actual_qty) as qty
 			from `tabStock Ledger Entry`
-			where item_code = %s and warehouse=%s
+			where is_cancelled = 0 and item_code = %s and warehouse=%s
 			group by batch_no''', (item_code, warehouse), as_dict=1)
 
 	return out
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 87bd9e6..fd080fd 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -138,20 +138,6 @@
 		frm.toggle_reqd('customer', frm.doc.is_customer_provided_item ? 1:0);
 	},
 
-	gst_hsn_code: function(frm) {
-		if((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
-			frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
-				$.each(hsn_doc.taxes || [], function(i, tax) {
-					let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
-					a.item_tax_template = tax.item_tax_template;
-					a.tax_category = tax.tax_category;
-					a.valid_from = tax.valid_from;
-					frm.refresh_field('taxes');
-				});
-			});
-		}
-	},
-
 	is_fixed_asset: function(frm) {
 		// set serial no to false & toggles its visibility
 		frm.set_value('has_serial_no', 0);
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 42cc67c..614c53a 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -123,6 +123,7 @@
 		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")
@@ -1305,3 +1306,7 @@
 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
\ No newline at end of file
diff --git a/erpnext/stock/doctype/item/regional/india.js b/erpnext/stock/doctype/item/regional/india.js
new file mode 100644
index 0000000..77ae51f
--- /dev/null
+++ b/erpnext/stock/doctype/item/regional/india.js
@@ -0,0 +1,15 @@
+frappe.ui.form.on('Item', {
+	gst_hsn_code: function(frm) {
+		if ((!frm.doc.taxes || !frm.doc.taxes.length) && frm.doc.gst_hsn_code) {
+			frappe.db.get_doc("GST HSN Code", frm.doc.gst_hsn_code).then(hsn_doc => {
+				$.each(hsn_doc.taxes || [], function(i, tax) {
+					let a = frappe.model.add_child(cur_frm.doc, 'Item Tax', 'taxes');
+					a.item_tax_template = tax.item_tax_template;
+					a.tax_category = tax.tax_category;
+					a.valid_from = tax.valid_from;
+					frm.refresh_field('taxes');
+				});
+			});
+		}
+	},
+});
\ No newline at end of file
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 3ad9909..026b85e 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -162,8 +162,15 @@
 						from `tabStock Entry Detail` where material_request = %s
 						and material_request_item = %s and docstatus = 1""",
 						(self.name, d.name))[0][0])
+					mr_qty_allowance = frappe.db.get_single_value('Stock Settings', 'mr_qty_allowance')
 
-					if d.ordered_qty and d.ordered_qty > d.stock_qty:
+					if mr_qty_allowance:
+						allowed_qty = d.qty + (d.qty * (mr_qty_allowance/100))
+						if d.ordered_qty and d.ordered_qty > allowed_qty:
+							frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1}  \
+								cannot be greater than allowed requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, allowed_qty, d.item_code))
+
+					elif d.ordered_qty and d.ordered_qty > d.stock_qty:
 						frappe.throw(_("The total Issue / Transfer quantity {0} in Material Request {1}  \
 							cannot be greater than requested quantity {2} for Item {3}").format(d.ordered_qty, d.parent, d.qty, d.item_code))
 
diff --git a/erpnext/stock/doctype/material_request/test_material_request.py b/erpnext/stock/doctype/material_request/test_material_request.py
index 72a3a5e..b4776ba 100644
--- a/erpnext/stock/doctype/material_request/test_material_request.py
+++ b/erpnext/stock/doctype/material_request/test_material_request.py
@@ -329,6 +329,58 @@
 		self.assertEqual(current_requested_qty_item1, existing_requested_qty_item1 + 54.0)
 		self.assertEqual(current_requested_qty_item2, existing_requested_qty_item2 + 3.0)
 
+	def test_over_transfer_qty_allowance(self):
+		mr = frappe.new_doc('Material Request')
+		mr.company = "_Test Company"
+		mr.scheduled_date = today()
+		mr.append('items',{
+			"item_code": "_Test FG Item",
+			"item_name": "_Test FG Item",
+			"qty": 10,
+			"schedule_date": today(),
+			"uom": "_Test UOM 1",
+			"warehouse": "_Test Warehouse - _TC"
+		})
+
+		mr.material_request_type = "Material Transfer"
+		mr.insert()
+		mr.submit()
+
+		frappe.db.set_value('Stock Settings', None, 'mr_qty_allowance', 20)
+
+		# map a stock entry
+
+		se_doc = make_stock_entry(mr.name)
+		se_doc.update({
+			"posting_date": today(),
+			"posting_time": "00:00",
+		})
+		se_doc.get("items")[0].update({
+			"qty": 13,
+			"transfer_qty": 12.0,
+			"s_warehouse": "_Test Warehouse - _TC",
+			"t_warehouse": "_Test Warehouse 1 - _TC",
+			"basic_rate": 1.0
+		})
+
+		# make available the qty in _Test Warehouse 1 before transfer
+		sr = frappe.new_doc("Stock Reconciliation")
+		sr.company = "_Test Company"
+		sr.purpose = "Opening Stock"
+		sr.append('items', {
+			"item_code": "_Test FG Item",
+			"warehouse": "_Test Warehouse - _TC",
+			"qty": 20,
+			"valuation_rate": 0.01
+		})
+		sr.insert()
+		sr.submit()
+		se = frappe.copy_doc(se_doc)
+		se.insert()
+		self.assertRaises(frappe.ValidationError)
+		se.items[0].qty = 12
+		se.submit()
+
 	def test_completed_qty_for_over_transfer(self):
 		existing_requested_qty_item1 = self._get_requested_qty("_Test Item Home Desktop 100", "_Test Warehouse - _TC")
 		existing_requested_qty_item2 = self._get_requested_qty("_Test Item Home Desktop 200", "_Test Warehouse - _TC")
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index e795742..516ae43 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -239,6 +239,7 @@
 			and sle.`item_code`=%(item_code)s
 			and sle.`company` = %(company)s
 			and batch.disabled = 0
+			and sle.is_cancelled=0
 			and IFNULL(batch.`expiry_date`, '2200-01-01') > %(today)s
 			{warehouse_condition}
 		GROUP BY
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 41800e3..899d7e8 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -352,7 +352,7 @@
 						if self.is_return or flt(d.item_tax_amount):
 							loss_account = expenses_included_in_valuation
 						else:
-							loss_account = self.get_company_default("default_expense_account")
+							loss_account = self.get_company_default("default_expense_account", ignore_validation=True) or stock_rbnb
 
 						cost_center = d.cost_center or frappe.get_cached_value("Company", self.company, "cost_center")
 
@@ -436,7 +436,7 @@
 			"cost_center": cost_center,
 			"debit": debit,
 			"credit": credit,
-			"against_account": against_account,
+			"against": against_account,
 			"remarks": remarks,
 		}
 
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index dbba21f..82461cb 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -336,10 +336,13 @@
 		se3.cancel()
 		po.reload()
 		pr2.load_from_db()
-		pr2.cancel()
 
-		po.load_from_db()
-		po.cancel()
+		if pr2.docstatus == 1 and frappe.db.get_value('Stock Ledger Entry',
+			{'voucher_no': pr2.name, 'is_cancelled': 0}, 'name'):
+			pr2.cancel()
+
+			po.load_from_db()
+			po.cancel()
 
 	def test_serial_no_supplier(self):
 		pr = make_purchase_receipt(item_code="_Test Serialized Item With Series", qty=1)
@@ -1044,7 +1047,7 @@
 			'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)
@@ -1055,13 +1058,13 @@
 	def test_purchase_receipt_with_exchange_rate_difference(self):
 		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice as create_purchase_invoice
 		from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import make_purchase_receipt as create_purchase_receipt
-		
+
 		pi = create_purchase_invoice(company="_Test Company with perpetual inventory",
 			cost_center = "Main - TCP1",
 			warehouse = "Stores - TCP1",
 			expense_account ="_Test Account Cost for Goods Sold - TCP1",
 			currency = "USD", conversion_rate = 70)
-		
+
 		pr = create_purchase_receipt(pi.name)
 		pr.conversion_rate = 80
 		pr.items[0].purchase_invoice = pi.name
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
index b3e4286..4cd40bf 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.js
@@ -29,13 +29,50 @@
 				};
 			});
 		}
+
+		frm.trigger('setup_realtime_progress');
 	},
+
+	setup_realtime_progress: function(frm) {
+		frappe.realtime.on('item_reposting_progress', data => {
+			if (frm.doc.name !== data.name) {
+				return;
+			}
+
+			if (frm.doc.status == 'In Progress') {
+				frm.doc.current_index = data.current_index;
+				frm.doc.items_to_be_repost = data.items_to_be_repost;
+
+				frm.dashboard.reset();
+				frm.trigger('show_reposting_progress');
+			}
+		});
+	},
+
 	refresh: function(frm) {
 		if (frm.doc.status == "Failed" && frm.doc.docstatus==1) {
 			frm.add_custom_button(__('Restart'), function () {
 				frm.trigger("restart_reposting");
 			}).addClass("btn-primary");
 		}
+
+		frm.trigger('show_reposting_progress');
+	},
+
+	show_reposting_progress: function(frm) {
+		var bars = [];
+
+		let total_count = frm.doc.items_to_be_repost ? JSON.parse(frm.doc.items_to_be_repost).length : 0;
+		let progress = flt(cint(frm.doc.current_index) / total_count * 100, 2) || 0.5;
+		var title = __('Reposting Completed {0}%', [progress]);
+
+		bars.push({
+			'title': title,
+			'width': progress + '%',
+			'progress_class': 'progress-bar-success'
+		});
+
+		frm.dashboard.add_progress(__('Reposting Progress'), bars);
 	},
 
 	restart_reposting: function(frm) {
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 071fc86..a800bf8 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.json
@@ -21,7 +21,10 @@
   "allow_zero_rate",
   "amended_from",
   "error_section",
-  "error_log"
+  "error_log",
+  "items_to_be_repost",
+  "distinct_item_and_warehouse",
+  "current_index"
  ],
  "fields": [
   {
@@ -142,12 +145,39 @@
    "fieldname": "allow_zero_rate",
    "fieldtype": "Check",
    "label": "Allow Zero Rate"
+  },
+  {
+   "fieldname": "items_to_be_repost",
+   "fieldtype": "Code",
+   "hidden": 1,
+   "label": "Items to Be Repost",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "distinct_item_and_warehouse",
+   "fieldtype": "Code",
+   "hidden": 1,
+   "label": "Distinct Item and Warehouse",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "current_index",
+   "fieldtype": "Int",
+   "hidden": 1,
+   "label": "Current Index",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-10 07:52:12.476589",
+ "modified": "2021-07-22 18:59:43.057878",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Repost Item Valuation",
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 5f31d9c..b22759d 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -81,7 +81,7 @@
 def repost_sl_entries(doc):
 	if doc.based_on == 'Transaction':
 		repost_future_sle(voucher_type=doc.voucher_type, voucher_no=doc.voucher_no,
-			allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher)
+			allow_negative_stock=doc.allow_negative_stock, via_landed_cost_voucher=doc.via_landed_cost_voucher, doc=doc)
 	else:
 		repost_future_sle(args=[frappe._dict({
 			"item_code": doc.item_code,
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index fcb6f0f..95c7311 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1789,7 +1789,7 @@
 	from `tabBatch` b, `tabStock Ledger Entry` sle
 	where b.expiry_date <= %s
 	and b.expiry_date is not NULL
-	and b.batch_id = sle.batch_no
+	and b.batch_id = sle.batch_no and sle.is_cancelled = 0
 	group by sle.warehouse, sle.item_code, sle.batch_no""",(nowdate()), as_dict=1)
 
 @frappe.whitelist()
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index 93482e8..b4f4583 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -60,7 +60,7 @@
 		if self.batch_no and not self.get("allow_negative_stock"):
 			batch_bal_after_transaction = flt(frappe.db.sql("""select sum(actual_qty)
 				from `tabStock Ledger Entry`
-				where warehouse=%s and item_code=%s and batch_no=%s""",
+				where is_cancelled =0 and warehouse=%s and item_code=%s and batch_no=%s""",
 				(self.warehouse, self.item_code, self.batch_no))[0][0])
 
 			if batch_bal_after_transaction < 0:
@@ -152,7 +152,7 @@
 				last_transaction_time = frappe.db.sql("""
 					select MAX(timestamp(posting_date, posting_time)) as posting_time
 					from `tabStock Ledger Entry`
-					where docstatus = 1 and item_code = %s
+					where docstatus = 1 and is_cancelled = 0 and item_code = %s
 					and warehouse = %s""", (self.item_code, self.warehouse))[0][0]
 
 				cur_doc_posting_datetime = "%s %s" % (self.posting_date, self.get("posting_time") or "00:00:00")
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
index 4540954..84f65a0 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.js
@@ -56,25 +56,40 @@
 	},
 
 	get_items: function(frm) {
-		let fields = [{
-			label: 'Warehouse', fieldname: 'warehouse', fieldtype: 'Link', options: 'Warehouse', reqd: 1,
-			"get_query": function() {
-				return {
-					"filters": {
-						"company": frm.doc.company,
-					}
-				};
+		let fields = [
+			{
+				label: 'Warehouse',
+				fieldname: 'warehouse',
+				fieldtype: 'Link',
+				options: 'Warehouse',
+				reqd: 1,
+				"get_query": function() {
+					return {
+						"filters": {
+							"company": frm.doc.company,
+						}
+					};
+				}
+			},
+			{
+				label: "Item Code",
+				fieldname: "item_code",
+				fieldtype: "Link",
+				options: "Item",
+				"get_query": function() {
+					return {
+						"filters": {
+							"disabled": 0,
+						}
+					};
+				}
+			},
+			{
+				label: __("Ignore Empty Stock"),
+				fieldname: "ignore_empty_stock",
+				fieldtype: "Check"
 			}
-		}, {
-			label: "Item Code", fieldname: "item_code", fieldtype: "Link", options: "Item",
-			"get_query": function() {
-				return {
-					"filters": {
-						"disabled": 0,
-					}
-				};
-			}
-		}];
+		];
 
 		frappe.prompt(fields, function(data) {
 			frappe.call({
@@ -84,22 +99,21 @@
 					posting_date: frm.doc.posting_date,
 					posting_time: frm.doc.posting_time,
 					company: frm.doc.company,
-					item_code: data.item_code
+					item_code: data.item_code,
+					ignore_empty_stock: data.ignore_empty_stock
 				},
 				callback: function(r) {
+					if (r.exc || !r.message || !r.message.length) return;
+
 					frm.clear_table("items");
-					for (var i=0; i<r.message.length; i++) {
-						var d = frm.add_child("items");
-						$.extend(d, r.message[i]);
 
-						if (!d.qty) {
-							d.qty = 0;
-						}
+					r.message.forEach((row) => {
+						let item = frm.add_child("items");
+						$.extend(item, row);
 
-						if (!d.valuation_rate) {
-							d.valuation_rate = 0;
-						}
-					}
+						item.qty = item.qty || 0;
+						item.valuation_rate = item.valuation_rate || 0;
+					});
 					frm.refresh_field("items");
 				}
 			});
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 9875491..0bae7cf 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -483,7 +483,8 @@
 			self._cancel()
 
 @frappe.whitelist()
-def get_items(warehouse, posting_date, posting_time, company, item_code=None):
+def get_items(warehouse, posting_date, posting_time, company, item_code=None, ignore_empty_stock=False):
+	ignore_empty_stock = cint(ignore_empty_stock)
 	items = [frappe._dict({
 		'item_code': item_code,
 		'warehouse': warehouse
@@ -497,18 +498,24 @@
 
 	for d in items:
 		if d.item_code in itemwise_batch_data:
-			stock_bal = get_stock_balance(d.item_code, d.warehouse,
-				posting_date, posting_time, with_valuation_rate=True)
+			valuation_rate = get_stock_balance(d.item_code, d.warehouse,
+				posting_date, posting_time, with_valuation_rate=True)[1]
 
 			for row in itemwise_batch_data.get(d.item_code):
-				args = get_item_data(row, row.qty, stock_bal[1])
+				if ignore_empty_stock and not row.qty:
+					continue
+
+				args = get_item_data(row, row.qty, valuation_rate)
 				res.append(args)
 		else:
 			stock_bal = get_stock_balance(d.item_code, d.warehouse, posting_date, posting_time,
 				with_valuation_rate=True , with_serial_no=cint(d.has_serial_no))
+			qty, valuation_rate, serial_no = stock_bal[0], stock_bal[1], stock_bal[2] if cint(d.has_serial_no) else ''
 
-			args = get_item_data(d, stock_bal[0], stock_bal[1],
-				stock_bal[2] if cint(d.has_serial_no) else '')
+			if ignore_empty_stock and not stock_bal[0]:
+					continue
+
+			args = get_item_data(d, qty, valuation_rate, serial_no)
 
 			res.append(args)
 
@@ -516,24 +523,44 @@
 
 def get_items_for_stock_reco(warehouse, company):
 	lft, rgt = frappe.db.get_value("Warehouse", warehouse, ["lft", "rgt"])
-	items = frappe.db.sql("""
-		select i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
-		from tabBin bin, tabItem i
-		where i.name=bin.item_code and IFNULL(i.disabled, 0) = 0 and i.is_stock_item = 1
-		and i.has_variants = 0 and exists(
-			select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=bin.warehouse
-		)
-	""", (lft, rgt), as_dict=1)
+	items = frappe.db.sql(f"""
+		select
+			i.name as item_code, i.item_name, bin.warehouse as warehouse, i.has_serial_no, i.has_batch_no
+		from
+			tabBin bin, tabItem i
+		where
+			i.name = bin.item_code
+			and IFNULL(i.disabled, 0) = 0
+			and i.is_stock_item = 1
+			and i.has_variants = 0
+			and exists(
+				select name from `tabWarehouse` where lft >= {lft} and rgt <= {rgt} and name = bin.warehouse
+			)
+	""", as_dict=1)
 
 	items += frappe.db.sql("""
-		select i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
-		from tabItem i, `tabItem Default` id
-		where i.name = id.parent
-			and exists(select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse)
-			and i.is_stock_item = 1 and i.has_variants = 0 and IFNULL(i.disabled, 0) = 0 and id.company=%s
+		select
+			i.name as item_code, i.item_name, id.default_warehouse as warehouse, i.has_serial_no, i.has_batch_no
+		from
+			tabItem i, `tabItem Default` id
+		where
+			i.name = id.parent
+			and exists(
+				select name from `tabWarehouse` where lft >= %s and rgt <= %s and name=id.default_warehouse
+			)
+			and i.is_stock_item = 1
+			and i.has_variants = 0
+			and IFNULL(i.disabled, 0) = 0
+			and id.company = %s
 		group by i.name
 	""", (lft, rgt, company), as_dict=1)
 
+	# remove duplicates
+	# check if item-warehouse key extracted from each entry exists in set iw_keys
+	# and update iw_keys
+	iw_keys = set()
+	items = [item for item in items if [(item.item_code, item.warehouse) not in iw_keys, iw_keys.add((item.item_code, item.warehouse))][0]]
+
 	return items
 
 def get_item_data(row, qty, valuation_rate, serial_no=None):
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 2a9dcfb..f75cb56 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -18,6 +18,7 @@
   "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",
   "allow_negative_stock",
@@ -283,6 +284,12 @@
    "fieldtype": "Select",
    "label": "Action If Quality Inspection Is Rejected",
    "options": "Stop\nWarn"
+  },
+  {
+   "description": "The percentage you are allowed to transfer more against the quantity ordered. For example, if you have ordered 100 units, and your Allowance is 10%, then you are allowed transfer 110 units.",
+   "fieldname": "mr_qty_allowance",
+   "fieldtype": "Float",
+   "label": "Over Transfer Allowance"
   }
  ],
  "icon": "icon-cog",
@@ -290,7 +297,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-07-10 16:17:42.159829",
+ "modified": "2021-06-28 17:02:26.683002",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
@@ -310,4 +317,4 @@
  "sort_field": "modified",
  "sort_order": "ASC",
  "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 cf52803..2ed7a04 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -312,8 +312,8 @@
 		"transaction_date": args.get("transaction_date"),
 		"against_blanket_order": args.get("against_blanket_order"),
 		"bom_no": item.get("default_bom"),
-		"weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
-		"weight_uom": args.get("weight_uom") or item.get("weight_uom")
+		"weight_per_unit": item.get("weight_per_unit"),
+		"weight_uom": item.get("weight_uom")
 	})
 
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
diff --git a/erpnext/erpnext_integrations/doctype/shopify_log/__init__.py b/erpnext/stock/report/cogs_by_item_group/__init__.py
similarity index 100%
copy from erpnext/erpnext_integrations/doctype/shopify_log/__init__.py
copy to erpnext/stock/report/cogs_by_item_group/__init__.py
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js
new file mode 100644
index 0000000..d7c50a6
--- /dev/null
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.js
@@ -0,0 +1,31 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+
+frappe.query_reports["COGS By Item Group"] = {
+	filters: [
+    {
+      label: __("Company"),
+      fieldname: "company",
+      fieldtype: "Link",
+      options: "Company",
+      mandatory: true,
+      default: frappe.defaults.get_user_default("Company"),
+    },
+    {
+      label: __("From Date"),
+      fieldname: "from_date",
+      fieldtype: "Date",
+      mandatory: true,
+      default: frappe.datetime.year_start(),
+    },
+    {
+      label: __("To Date"),
+      fieldname: "to_date",
+      fieldtype: "Date",
+      mandatory: true,
+      default: frappe.datetime.get_today(),
+    },
+	]
+};
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json
new file mode 100644
index 0000000..a14adf8
--- /dev/null
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.json
@@ -0,0 +1,32 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-06-02 18:59:19.830928",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-06-02 18:59:55.470621",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "COGS By Item Group",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "GL Entry",
+ "report_name": "COGS By Item Group",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Accounts User"
+  },
+  {
+   "role": "Accounts Manager"
+  },
+  {
+   "role": "Auditor"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
new file mode 100644
index 0000000..9e5e63e
--- /dev/null
+++ b/erpnext/stock/report/cogs_by_item_group/cogs_by_item_group.py
@@ -0,0 +1,188 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from collections import OrderedDict
+import datetime
+from typing import Dict, List, Tuple, Union
+
+import frappe
+from frappe import _
+from frappe.utils import date_diff
+
+from erpnext.accounts.report.general_ledger.general_ledger import get_gl_entries
+
+
+Filters = frappe._dict
+Row = frappe._dict
+Data = List[Row]
+Columns = List[Dict[str, str]]
+DateTime = Union[datetime.date, datetime.datetime]
+FilteredEntries = List[Dict[str, Union[str, float, DateTime, None]]]
+ItemGroupsDict = Dict[Tuple[int, int], Dict[str, Union[str, int]]]
+SVDList = List[frappe._dict]
+
+
+def execute(filters: Filters) -> Tuple[Columns, Data]:
+	update_filters_with_account(filters)
+	validate_filters(filters)
+	columns = get_columns()
+	data = get_data(filters)
+	return columns, data
+
+
+def update_filters_with_account(filters: Filters) -> None:
+	account = frappe.get_value("Company", filters.get("company"), "default_expense_account")
+	filters.update(dict(account=account))
+
+
+def validate_filters(filters: Filters) -> None:
+	if filters.from_date > filters.to_date:
+		frappe.throw(_("From Date must be before To Date"))
+
+
+def get_columns() -> Columns:
+	return [
+		{
+			'label': 'Item Group',
+			'fieldname': 'item_group',
+			'fieldtype': 'Data',
+			'width': '200'
+		},
+		{
+			'label': 'COGS Debit',
+			'fieldname': 'cogs_debit',
+			'fieldtype': 'Currency',
+			'width': '200'
+		}
+	]
+
+
+def get_data(filters: Filters) -> Data:
+	filtered_entries = get_filtered_entries(filters)
+	svd_list = get_stock_value_difference_list(filtered_entries)
+	leveled_dict = get_leveled_dict()
+
+	assign_self_values(leveled_dict, svd_list)
+	assign_agg_values(leveled_dict)
+	
+	data = []
+	for item in leveled_dict.items():
+		i = item[1]
+		if i['agg_value'] == 0:
+			continue
+		data.append(get_row(i['name'], i['agg_value'], i['is_group'], i['level']))
+		if i['self_value'] < i['agg_value'] and i['self_value'] > 0:
+			data.append(get_row(i['name'], i['self_value'], 0, i['level'] + 1))
+	return data
+
+
+def get_filtered_entries(filters: Filters) -> FilteredEntries:
+	gl_entries = get_gl_entries(filters, [])
+	filtered_entries = []
+	for entry in gl_entries:
+		posting_date = entry.get('posting_date')
+		from_date = filters.get('from_date')
+		if date_diff(from_date, posting_date) > 0:
+			continue
+		filtered_entries.append(entry)
+	return filtered_entries
+
+
+def get_stock_value_difference_list(filtered_entries: FilteredEntries) -> SVDList:
+	voucher_nos = [fe.get('voucher_no') for fe in filtered_entries]
+	svd_list = frappe.get_list(
+		'Stock Ledger Entry', fields=['item_code','stock_value_difference'],
+		filters=[('voucher_no', 'in', voucher_nos)]
+	)
+	assign_item_groups_to_svd_list(svd_list)
+	return svd_list
+
+
+def get_leveled_dict() -> OrderedDict:
+	item_groups_dict = get_item_groups_dict()
+	lr_list = sorted(item_groups_dict, key=lambda x : int(x[0]))
+	leveled_dict = OrderedDict()
+	current_level = 0
+	nesting_r = []
+	for l, r in lr_list:
+		while current_level > 0 and nesting_r[-1] < l:
+			nesting_r.pop()
+			current_level -= 1
+
+		leveled_dict[(l,r)] = {
+			'level' : current_level,
+			'name' : item_groups_dict[(l,r)]['name'],
+			'is_group' : item_groups_dict[(l,r)]['is_group']
+		}
+
+		if int(r) - int(l) > 1:
+			current_level += 1
+			nesting_r.append(r)
+
+	update_leveled_dict(leveled_dict)
+	return leveled_dict
+
+
+def assign_self_values(leveled_dict: OrderedDict, svd_list: SVDList) -> None:
+	key_dict = {v['name']:k for k, v in leveled_dict.items()}
+	for item in svd_list:
+		key = key_dict[item.get("item_group")]
+		leveled_dict[key]['self_value'] += -item.get("stock_value_difference")
+
+
+def assign_agg_values(leveled_dict: OrderedDict) -> None:
+	keys = list(leveled_dict.keys())[::-1]
+	prev_level = leveled_dict[keys[-1]]['level']
+	accu = [0]
+	for k in keys[:-1]:
+		curr_level = leveled_dict[k]['level']
+		if curr_level == prev_level:
+			accu[-1] += leveled_dict[k]['self_value']
+			leveled_dict[k]['agg_value'] = leveled_dict[k]['self_value']
+
+		elif curr_level > prev_level:
+			accu.append(leveled_dict[k]['self_value'])
+			leveled_dict[k]['agg_value'] = accu[-1]
+
+		elif curr_level < prev_level:
+			accu[-1] += leveled_dict[k]['self_value']
+			leveled_dict[k]['agg_value'] = accu[-1]
+
+		prev_level = curr_level
+
+	# root node
+	rk = keys[-1]
+	leveled_dict[rk]['agg_value'] = sum(accu) + leveled_dict[rk]['self_value']
+
+
+def get_row(name:str, value:float, is_bold:int, indent:int) -> Row:
+	item_group = name
+	if is_bold:
+		item_group = frappe.bold(item_group)
+	return frappe._dict(item_group=item_group, cogs_debit=value, indent=indent)
+			
+
+def assign_item_groups_to_svd_list(svd_list: SVDList) -> None:
+	ig_map = get_item_groups_map(svd_list)
+	for item in svd_list:
+		item.item_group = ig_map[item.get("item_code")]
+
+
+def get_item_groups_map(svd_list: SVDList) -> Dict[str, str]:
+	item_codes = set(i['item_code'] for i in svd_list)
+	ig_list = frappe.get_list(
+		'Item', fields=['item_code','item_group'],
+		filters=[('item_code', 'in', item_codes)]
+	)
+	return {i['item_code']:i['item_group'] for i in ig_list}
+
+
+def get_item_groups_dict() -> ItemGroupsDict:
+	item_groups_list = frappe.get_all("Item Group", fields=("name", "is_group", "lft", "rgt"))
+	return {(i['lft'],i['rgt']):{'name':i['name'], 'is_group':i['is_group']}
+		for i in item_groups_list}
+
+
+def update_leveled_dict(leveled_dict: OrderedDict) -> None:
+	for k in leveled_dict:
+		leveled_dict[k].update({'self_value':0, 'agg_value':0})
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index 14d543b..bfc4471 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -22,6 +22,7 @@
 	data = []
 
 	filters = {
+		"is_cancelled": 0,
 		"company": report_filters.company,
 		"posting_date": ("<=", report_filters.as_on_date)
 	}
@@ -34,7 +35,7 @@
 		key = (d.voucher_type, d.voucher_no)
 		gl_data = voucher_wise_gl_data.get(key) or {}
 		d.account_value = gl_data.get("account_value", 0)
-		d.difference_value = (d.stock_value - d.account_value)
+		d.difference_value = abs(d.stock_value - d.account_value)
 		if abs(d.difference_value) > 0.1:
 			data.append(d)
 
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index b6a8063..9e56ad4 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -16,8 +16,6 @@
 	is_reposting_item_valuation_in_progress()
 	if not filters: filters = {}
 
-	validate_filters(filters)
-
 	from_date = filters.get('from_date')
 	to_date = filters.get('to_date')
 
@@ -295,12 +293,6 @@
 
 	return dict((d.parent + d.warehouse, d) for d in item_reorder_details)
 
-def validate_filters(filters):
-	if not (filters.get("item_code") or filters.get("warehouse")):
-		sle_count = flt(frappe.db.sql("""select count(name) from `tabStock Ledger Entry`""")[0][0])
-		if sle_count > 500000:
-			frappe.throw(_("Please set filter based on Item or Warehouse due to a large amount of entries."))
-
 def get_variants_attributes():
 	'''Return all item variant attributes.'''
 	return [i.name for i in frappe.get_all('Item Attribute')]
diff --git a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
index 5873a7a..4108a57 100644
--- a/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
+++ b/erpnext/stock/report/supplier_wise_sales_analytics/supplier_wise_sales_analytics.py
@@ -69,7 +69,7 @@
 		i.stock_uom, sle.actual_qty, sle.stock_value_difference,
 		sle.voucher_no, sle.voucher_type
 		from `tabStock Ledger Entry` sle, `tabItem` i
-		where sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
+		where sle.is_cancelled = 0 and sle.item_code=i.name and sle.actual_qty < 0 %s""" % conditions, values, as_dict=1):
 			consumed_details.setdefault(d.item_code, []).append(d)
 
 	return consumed_details
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index c15d1ed..f990ce0 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -127,30 +127,26 @@
 	sle.submit()
 	return sle
 
-def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False):
+def repost_future_sle(args=None, voucher_type=None, voucher_no=None, allow_negative_stock=None, via_landed_cost_voucher=False, doc=None):
 	if not args and voucher_type and voucher_no:
-		args = get_args_for_voucher(voucher_type, voucher_no)
+		args = get_items_to_be_repost(voucher_type, voucher_no, doc)
 
-	distinct_item_warehouses = {}
-	for i, d in enumerate(args):
-		distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
-			"reposting_status": False,
-			"sle": d,
-			"args_idx": i
-		}))
+	distinct_item_warehouses = get_distinct_item_warehouse(args, doc)
 
-	i = 0
+	i = get_current_index(doc) or 0
 	while i < len(args):
+		validate_item_warehouse(args[i])
+
 		obj = update_entries_after({
-			"item_code": args[i].item_code,
-			"warehouse": args[i].warehouse,
-			"posting_date": args[i].posting_date,
-			"posting_time": args[i].posting_time,
-			"creation": args[i].get("creation"),
-			"distinct_item_warehouses": distinct_item_warehouses
+			'item_code': args[i].get('item_code'),
+			'warehouse': args[i].get('warehouse'),
+			'posting_date': args[i].get('posting_date'),
+			'posting_time': args[i].get('posting_time'),
+			'creation': args[i].get('creation'),
+			'distinct_item_warehouses': distinct_item_warehouses
 		}, allow_negative_stock=allow_negative_stock, via_landed_cost_voucher=via_landed_cost_voucher)
 
-		distinct_item_warehouses[(args[i].item_code, args[i].warehouse)].reposting_status = True
+		distinct_item_warehouses[(args[i].get('item_code'), args[i].get('warehouse'))].reposting_status = True
 
 		if obj.new_items_found:
 			for item_wh, data in iteritems(distinct_item_warehouses):
@@ -159,11 +155,41 @@
 					args.append(data.sle)
 				elif data.sle_changed and not data.reposting_status:
 					args[data.args_idx] = data.sle
-				
+
 				data.sle_changed = False
 		i += 1
 
-def get_args_for_voucher(voucher_type, voucher_no):
+		if doc and i % 2 == 0:
+			update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
+	if doc and args:
+		update_args_in_repost_item_valuation(doc, i, args, distinct_item_warehouses)
+
+def validate_item_warehouse(args):
+	for field in ['item_code', 'warehouse', 'posting_date', 'posting_time']:
+		if not args.get(field):
+			validation_msg = f'The field {frappe.unscrub(args.get(field))} is required for the reposting'
+			frappe.throw(_(validation_msg))
+
+def update_args_in_repost_item_valuation(doc, index, args, distinct_item_warehouses):
+	frappe.db.set_value(doc.doctype, doc.name, {
+		'items_to_be_repost': json.dumps(args, default=str),
+		'distinct_item_and_warehouse': json.dumps({str(k): v for k,v in distinct_item_warehouses.items()}, default=str),
+		'current_index': index
+	})
+
+	frappe.db.commit()
+
+	frappe.publish_realtime('item_reposting_progress', {
+		'name': doc.name,
+		'items_to_be_repost': json.dumps(args, default=str),
+		'current_index': index
+	})
+
+def get_items_to_be_repost(voucher_type, voucher_no, doc=None):
+	if doc and doc.items_to_be_repost:
+		return json.loads(doc.items_to_be_repost) or []
+
 	return frappe.db.get_all("Stock Ledger Entry",
 		filters={"voucher_type": voucher_type, "voucher_no": voucher_no},
 		fields=["item_code", "warehouse", "posting_date", "posting_time", "creation"],
@@ -171,6 +197,25 @@
 		group_by="item_code, warehouse"
 	)
 
+def get_distinct_item_warehouse(args=None, doc=None):
+	distinct_item_warehouses = {}
+	if doc and doc.distinct_item_and_warehouse:
+		distinct_item_warehouses = json.loads(doc.distinct_item_and_warehouse)
+		distinct_item_warehouses = {frappe.safe_eval(k): frappe._dict(v) for k, v in distinct_item_warehouses.items()}
+	else:
+		for i, d in enumerate(args):
+			distinct_item_warehouses.setdefault((d.item_code, d.warehouse), frappe._dict({
+				"reposting_status": False,
+				"sle": d,
+				"args_idx": i
+			}))
+
+	return distinct_item_warehouses
+
+def get_current_index(doc=None):
+	if doc and doc.current_index:
+		return doc.current_index
+
 class update_entries_after(object):
 	"""
 		update valution rate and qty after transaction