Merge remote-tracking branch 'upstream/develop' into remove-india
diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml
index b644568..722c125 100644
--- a/.github/workflows/docs-checker.yml
+++ b/.github/workflows/docs-checker.yml
@@ -12,7 +12,7 @@
       - name: 'Setup Environment'
         uses: actions/setup-python@v2
         with:
-          python-version: 3.8
+          python-version: '3.10'
 
       - name: 'Clone repo'
         uses: actions/checkout@v2
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index ebb88c9..af6d8f2 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -11,10 +11,10 @@
     steps:
       - uses: actions/checkout@v2
 
-      - name: Set up Python 3.8
+      - name: Set up Python 3.10
         uses: actions/setup-python@v2
         with:
-          python-version: 3.8
+          python-version: '3.10'
 
       - name: Install and Run Pre-commit
         uses: pre-commit/action@v2.0.3
@@ -22,10 +22,8 @@
       - name: Download Semgrep rules
         run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
 
-      - uses: returntocorp/semgrep-action@v1
-        env:
-            SEMGREP_TIMEOUT: 120
-        with:
-            config: >-
-              r/python.lang.correctness
-              ./frappe-semgrep-rules/rules
+      - name: Download semgrep
+        run: pip install semgrep==0.97.0
+
+      - name: Run Semgrep rules
+        run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index 2cf4444..a71db72 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -35,9 +35,9 @@
         uses: actions/checkout@v2
 
       - name: Setup Python
-        uses: actions/setup-python@v2
+        uses: "gabrielfalcao/pyenv-action@v9"
         with:
-          python-version: 3.8
+          versions: 3.10:latest, 3.7:latest
 
       - name: Setup Node
         uses: actions/setup-node@v2
@@ -52,7 +52,7 @@
         uses: actions/cache@v2
         with:
           path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
           restore-keys: |
             ${{ runner.os }}-pip-
             ${{ runner.os }}-
@@ -82,7 +82,10 @@
             ${{ runner.os }}-yarn-
 
       - name: Install
-        run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
+        run: |
+          pip install frappe-bench
+          pyenv global $(pyenv versions | grep '3.10')
+          bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
         env:
           DB: mariadb
           TYPE: server
@@ -96,18 +99,23 @@
           git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
           git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
 
+          pyenv global $(pyenv versions | grep '3.7')
           for version in $(seq 12 13)
           do
               echo "Updating to v$version"
               branch_name="version-$version-hotfix"
 
+
               git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
               git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
 
               git -C "apps/frappe" checkout -q -f $branch_name
               git -C "apps/erpnext" checkout -q -f $branch_name
 
-              bench setup requirements --python
+              rm -rf ~/frappe-bench/env
+              bench setup env
+              bench pip install -e ./apps/erpnext
+
               bench --site test_site migrate
           done
 
@@ -115,5 +123,10 @@
           echo "Updating to latest version"
           git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
           git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
-          bench setup requirements --python
+
+          pyenv global $(pyenv versions | grep '3.10')
+          rm -rf ~/frappe-bench/env
+          bench -v setup env
+          bench pip install -e ./apps/erpnext
+
           bench --site test_site migrate
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index cdb6849..f65cb46 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -39,7 +39,7 @@
       fail-fast: false
 
       matrix:
-        container: [1, 2, 3]
+        container: [1, 2, 3, 4]
 
     name: Python Unit Tests
 
@@ -59,7 +59,7 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.8
+          python-version: '3.10'
 
       - name: Setup Node
         uses: actions/setup-node@v2
@@ -74,7 +74,7 @@
         uses: actions/cache@v2
         with:
           path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
           restore-keys: |
             ${{ runner.os }}-pip-
             ${{ runner.os }}-
diff --git a/.github/workflows/server-tests-postgres.yml b/.github/workflows/server-tests-postgres.yml
index 77d3c1a..53a94db 100644
--- a/.github/workflows/server-tests-postgres.yml
+++ b/.github/workflows/server-tests-postgres.yml
@@ -21,7 +21,7 @@
     strategy:
       fail-fast: false
       matrix:
-       container: [1, 2, 3]
+       container: [1]
 
     name: Python Unit Tests
 
@@ -46,7 +46,7 @@
       - name: Setup Python
         uses: actions/setup-python@v2
         with:
-          python-version: 3.8
+          python-version: '3.10'
 
       - name: Setup Node
         uses: actions/setup-node@v2
@@ -61,7 +61,7 @@
         uses: actions/cache@v2
         with:
           path: ~/.cache/pip
-          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
+          key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
           restore-keys: |
             ${{ runner.os }}-pip-
             ${{ runner.os }}-
diff --git a/CODEOWNERS b/CODEOWNERS
index bfc2601..ecbae86 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,33 +3,30 @@
 # These owners will be the default owners for everything in
 # the repo. Unless a later match takes precedence,
 
-erpnext/accounts/               @nextchamp-saqib @deepeshgarg007
-erpnext/assets/                 @nextchamp-saqib @deepeshgarg007
-erpnext/erpnext_integrations/   @nextchamp-saqib
+erpnext/accounts/               @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
+erpnext/assets/                 @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
 erpnext/loan_management/        @nextchamp-saqib @deepeshgarg007
-erpnext/regional                @nextchamp-saqib @deepeshgarg007
-erpnext/selling                 @nextchamp-saqib @deepeshgarg007
+erpnext/regional                @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
+erpnext/selling                 @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
 erpnext/support/                @nextchamp-saqib @deepeshgarg007
 pos*                            @nextchamp-saqib
 
-erpnext/buying/                 @marination @rohitwaghchaure @ankush
+erpnext/buying/                 @marination @rohitwaghchaure @s-aga-r
 erpnext/e_commerce/             @marination
-erpnext/maintenance/            @marination @rohitwaghchaure
-erpnext/manufacturing/          @marination @rohitwaghchaure @ankush
+erpnext/maintenance/            @marination @rohitwaghchaure @s-aga-r
+erpnext/manufacturing/          @marination @rohitwaghchaure @s-aga-r
 erpnext/portal/                 @marination
-erpnext/quality_management/     @marination @rohitwaghchaure
+erpnext/quality_management/     @marination @rohitwaghchaure @s-aga-r
 erpnext/shopping_cart/          @marination
-erpnext/stock/                  @marination @rohitwaghchaure @ankush
+erpnext/stock/                  @marination @rohitwaghchaure @s-aga-r
 
-erpnext/crm/                    @ruchamahabal @pateljannat
-erpnext/education/              @ruchamahabal @pateljannat
-erpnext/hr/                     @ruchamahabal @pateljannat
-erpnext/payroll                 @ruchamahabal @pateljannat
-erpnext/projects/               @ruchamahabal @pateljannat
+erpnext/crm/                    @NagariaHussain 
+erpnext/education/              @rutwikhdev 
+erpnext/projects/               @ruchamahabal
 
-erpnext/controllers/            @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush
-erpnext/patches/                @deepeshgarg007 @nextchamp-saqib @marination @ankush
+erpnext/controllers/            @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
+erpnext/patches/                @deepeshgarg007 @nextchamp-saqib @marination
 erpnext/public/                 @nextchamp-saqib @marination
 
 .github/                        @ankush
-requirements.txt                @gavindsouza
+pyproject.toml                  @gavindsouza @ankush
diff --git a/dev-requirements.txt b/dev-requirements.txt
deleted file mode 100644
index 15545c0..0000000
--- a/dev-requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-hypothesis~=6.31.0
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 8eccd2b..ba4cdd6 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -476,6 +476,13 @@
 			this.frm.trigger("calculate_timesheet_totals");
 		}
 	}
+
+	is_cash_or_non_trade_discount() {
+		this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
+		if (!this.frm.doc.is_cash_or_non_trade_discount) {
+			this.frm.set_value("additional_discount_account", "");
+		}
+	}
 };
 
 // for backward compatibility: combine new and previous states
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 6361b4c..17dd804 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -106,6 +106,7 @@
   "loyalty_redemption_cost_center",
   "section_break_49",
   "apply_discount_on",
+  "is_cash_or_non_trade_discount",
   "base_discount_amount",
   "additional_discount_account",
   "column_break_51",
@@ -1766,8 +1767,6 @@
    "width": "50%"
   },
   {
-   "fetch_from": "sales_partner.commission_rate",
-   "fetch_if_empty": 1,
    "fieldname": "commission_rate",
    "fieldtype": "Float",
    "hide_days": 1,
@@ -1966,7 +1965,7 @@
   {
    "fieldname": "additional_discount_account",
    "fieldtype": "Link",
-   "label": "Additional Discount Account",
+   "label": "Discount Account",
    "options": "Account"
   },
   {
@@ -2004,6 +2003,13 @@
    "fieldtype": "Currency",
    "label": "Amount Eligible for Commission",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.apply_discount_on == \"Grand Total\"",
+   "fieldname": "is_cash_or_non_trade_discount",
+   "fieldtype": "Check",
+   "label": "Is Cash or Non Trade Discount"
   }
  ],
  "icon": "fa fa-file-text",
@@ -2016,7 +2022,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2022-06-10 03:52:51.409913",
+ "modified": "2022-06-16 16:22:44.870575",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index fa37d81..f8c26d1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1008,7 +1008,7 @@
 		)
 
 		if grand_total and not self.is_internal_transfer():
-			# Didnot use base_grand_total to book rounding loss gle
+			# Did not use base_grand_total to book rounding loss gle
 			gl_entries.append(
 				self.get_gl_dict(
 					{
@@ -1033,6 +1033,22 @@
 				)
 			)
 
+		if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
+			gl_entries.append(
+				self.get_gl_dict(
+					{
+						"account": self.additional_discount_account,
+						"against": self.debit_to,
+						"debit": self.base_discount_amount,
+						"debit_in_account_currency": self.discount_amount,
+						"cost_center": self.cost_center,
+						"project": self.project,
+					},
+					self.currency,
+					item=self,
+				)
+			)
+
 	def make_tax_gl_entries(self, gl_entries):
 		enable_discount_accounting = cint(
 			frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
@@ -2093,6 +2109,8 @@
 		source_document_warehouse_field = "from_warehouse"
 		target_document_warehouse_field = "target_warehouse"
 
+	received_items = get_received_items(source_name, target_doctype, target_detail_field)
+
 	validate_inter_company_transaction(source_doc, doctype)
 	details = get_inter_company_details(source_doc, doctype)
 
@@ -2157,12 +2175,17 @@
 				shipping_address_name=target_doc.shipping_address_name,
 			)
 
+	def update_item(source, target, source_parent):
+		target.qty = flt(source.qty) - received_items.get(source.name, 0.0)
+
 	item_field_map = {
 		"doctype": target_doctype + " Item",
 		"field_no_map": ["income_account", "expense_account", "cost_center", "warehouse"],
 		"field_map": {
 			"rate": "rate",
 		},
+		"postprocess": update_item,
+		"condition": lambda doc: doc.qty > 0,
 	}
 
 	if doctype in ["Sales Invoice", "Sales Order"]:
@@ -2200,6 +2223,28 @@
 	return doclist
 
 
+def get_received_items(reference_name, doctype, reference_fieldname):
+	target_doctypes = frappe.get_all(
+		doctype,
+		filters={"inter_company_invoice_reference": reference_name, "docstatus": 1},
+		as_list=True,
+	)
+
+	if target_doctypes:
+		target_doctypes = list(target_doctypes[0])
+
+	received_items_map = frappe._dict(
+		frappe.get_all(
+			doctype + " Item",
+			filters={"parent": ("in", target_doctypes)},
+			fields=[reference_fieldname, "qty"],
+			as_list=1,
+		)
+	)
+
+	return received_items_map
+
+
 def set_purchase_references(doc):
 	# add internal PO or PR links if any
 	if doc.is_internal_transfer():
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
index c0005f7..0a765f3 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice_dashboard.py
@@ -11,6 +11,7 @@
 			"Payment Request": "reference_name",
 			"Sales Invoice": "return_against",
 			"Auto Repeat": "reference_document",
+			"Purchase Invoice": "inter_company_invoice_reference",
 		},
 		"internal_links": {
 			"Sales Order": ["items", "sales_order"],
@@ -30,5 +31,6 @@
 			{"label": _("Reference"), "items": ["Timesheet", "Delivery Note", "Sales Order"]},
 			{"label": _("Returns"), "items": ["Sales Invoice"]},
 			{"label": _("Subscription"), "items": ["Auto Repeat"]},
+			{"label": _("Internal Transfers"), "items": ["Purchase Invoice"]},
 		],
 	}
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 1911152..411b313 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -128,6 +128,7 @@
 					credit_note_in_account_currency=0.0,
 					outstanding_in_account_currency=0.0,
 				)
+			self.get_invoices(ple)
 
 			if self.filters.get("group_by_party"):
 				self.init_subtotal_row(ple.party)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index da45610..33dbe3f 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -425,7 +425,7 @@
 						company: me.frm.doc.company
 					},
 					allow_child_item_selection: true,
-					child_fielname: "items",
+					child_fieldname: "items",
 					child_columns: ["item_code", "qty"]
 				})
 			}, __("Get Items From"));
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index d732b75..5f84de6 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -140,6 +140,43 @@
 		# ordered qty decreases as ordered qty is 0 (deleted row)
 		self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10)  # 0
 
+	def test_supplied_items_validations_on_po_update_after_submit(self):
+		po = create_purchase_order(item_code="_Test FG Item", is_subcontracted=1, qty=5, rate=100)
+		item = po.items[0]
+
+		original_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
+
+		# Just update rate
+		trans_item = [
+			{
+				"item_code": "_Test FG Item",
+				"rate": 20,
+				"qty": 5,
+				"conversion_factor": 1.0,
+				"docname": item.name,
+			}
+		]
+		update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
+		po.reload()
+
+		new_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
+		self.assertEqual(set(original_supplied_items.keys()), set(new_supplied_items.keys()))
+
+		# Update qty to 2x
+		trans_item[0]["qty"] *= 2
+		update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
+		po.reload()
+
+		new_supplied_items = {po.name: po.required_qty for po in po.supplied_items}
+		self.assertEqual(2 * sum(original_supplied_items.values()), sum(new_supplied_items.values()))
+
+		# Set transfer qty and attempt to update qty, shouldn't be allowed
+		po.supplied_items[0].supplied_qty = 2
+		po.supplied_items[0].db_update()
+		trans_item[0]["qty"] *= 2
+		with self.assertRaises(frappe.ValidationError):
+			update_child_qty_rate("Purchase Order", json.dumps(trans_item), po.name)
+
 	def test_update_child(self):
 		mr = make_material_request(qty=10)
 		po = make_purchase_order(mr.name)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index fc6fdcd..ceac815 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2440,7 +2440,7 @@
 		update_bin_qty(row.item_code, row.warehouse, qty_dict)
 
 
-def validate_and_delete_children(parent, data):
+def validate_and_delete_children(parent, data) -> bool:
 	deleted_children = []
 	updated_item_names = [d.get("docname") for d in data]
 	for item in parent.items:
@@ -2459,6 +2459,8 @@
 	for d in deleted_children:
 		update_bin_on_delete(d, parent.doctype)
 
+	return bool(deleted_children)
+
 
 @frappe.whitelist()
 def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
@@ -2522,13 +2524,38 @@
 		):
 			frappe.throw(_("Cannot set quantity less than received quantity"))
 
+	def should_update_supplied_items(doc) -> bool:
+		"""Subcontracted PO can allow following changes *after submit*:
+
+		1. Change rate of subcontracting - regardless of other changes.
+		2. Change qty and/or add new items and/or remove items
+		        Exception: Transfer/Consumption is already made, qty change not allowed.
+		"""
+
+		supplied_items_processed = any(
+			item.supplied_qty or item.consumed_qty or item.returned_qty for item in doc.supplied_items
+		)
+
+		update_supplied_items = (
+			any_qty_changed or items_added_or_removed or any_conversion_factor_changed
+		)
+		if update_supplied_items and supplied_items_processed:
+			frappe.throw(_("Item qty can not be updated as raw materials are already processed."))
+
+		return update_supplied_items
+
 	data = json.loads(trans_items)
 
+	any_qty_changed = False  # updated to true if any item's qty changes
+	items_added_or_removed = False  # updated to true if any new item is added or removed
+	any_conversion_factor_changed = False
+
 	sales_doctypes = ["Sales Order", "Sales Invoice", "Delivery Note", "Quotation"]
 	parent = frappe.get_doc(parent_doctype, parent_doctype_name)
 
 	check_doc_permissions(parent, "write")
-	validate_and_delete_children(parent, data)
+	_removed_items = validate_and_delete_children(parent, data)
+	items_added_or_removed |= _removed_items
 
 	for d in data:
 		new_child_flag = False
@@ -2539,6 +2566,7 @@
 
 		if not d.get("docname"):
 			new_child_flag = True
+			items_added_or_removed = True
 			check_doc_permissions(parent, "create")
 			child_item = get_new_child_item(d)
 		else:
@@ -2561,6 +2589,7 @@
 			qty_unchanged = prev_qty == new_qty
 			uom_unchanged = prev_uom == new_uom
 			conversion_factor_unchanged = prev_con_fac == new_con_fac
+			any_conversion_factor_changed |= not conversion_factor_unchanged
 			date_unchanged = (
 				prev_date == getdate(new_date) if prev_date and new_date else False
 			)  # in case of delivery note etc
@@ -2574,6 +2603,8 @@
 				continue
 
 		validate_quantity(child_item, d)
+		if flt(child_item.get("qty")) != flt(d.get("qty")):
+			any_qty_changed = True
 
 		child_item.qty = flt(d.get("qty"))
 		rate_precision = child_item.precision("rate") or 2
@@ -2679,8 +2710,9 @@
 		parent.update_ordered_and_reserved_qty()
 		parent.update_receiving_percentage()
 		if parent.is_subcontracted:
-			parent.update_reserved_qty_for_subcontract()
-			parent.create_raw_materials_supplied("supplied_items")
+			if should_update_supplied_items(parent):
+				parent.update_reserved_qty_for_subcontract()
+				parent.create_raw_materials_supplied("supplied_items")
 			parent.save()
 	else:  # Sales Order
 		parent.validate_warehouse()
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 2144055..0d8cffe 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -500,6 +500,9 @@
 		else:
 			self.doc.grand_total = flt(self.doc.net_total)
 
+		if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
+			self.doc.grand_total -= self.doc.discount_amount
+
 		if self.doc.get("taxes"):
 			self.doc.total_taxes_and_charges = flt(
 				self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
@@ -594,6 +597,12 @@
 			if not self.doc.apply_discount_on:
 				frappe.throw(_("Please select Apply Discount On"))
 
+			if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
+				"is_cash_or_non_trade_discount"
+			):
+				self.discount_amount_applied = True
+				return
+
 			self.doc.base_discount_amount = flt(
 				self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
 			)
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 9216c45..d47373f 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -46,6 +46,10 @@
   "fax",
   "address_section",
   "address_html",
+  "column_break_38",
+  "city",
+  "state",
+  "country",
   "column_break2",
   "contact_html",
   "qualification_tab",
@@ -333,9 +337,8 @@
   },
   {
    "fieldname": "no_of_employees",
-   "fieldtype": "Select",
-   "label": "No. of Employees",
-   "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
+   "fieldtype": "Int",
+   "label": "No. of Employees"
   },
   {
    "fieldname": "column_break_22",
@@ -477,13 +480,33 @@
    "fieldname": "disabled",
    "fieldtype": "Check",
    "label": "Disabled"
+  },
+  {
+   "fieldname": "column_break_38",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "city",
+   "fieldtype": "Data",
+   "label": "City"
+  },
+  {
+   "fieldname": "state",
+   "fieldtype": "Data",
+   "label": "State"
+  },
+  {
+   "fieldname": "country",
+   "fieldtype": "Link",
+   "label": "Country",
+   "options": "Country"
   }
  ],
  "icon": "fa fa-user",
  "idx": 5,
  "image_field": "image",
  "links": [],
- "modified": "2022-06-21 15:10:06.613519",
+ "modified": "2022-06-27 21:56:17.392756",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Lead",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 8ddd4e3..1a6f23b 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -32,9 +32,12 @@
   "column_break_23",
   "industry",
   "market_segment",
-  "column_break_31",
-  "territory",
   "website",
+  "column_break_31",
+  "city",
+  "state",
+  "country",
+  "territory",
   "section_break_14",
   "currency",
   "column_break_36",
@@ -463,9 +466,8 @@
   },
   {
    "fieldname": "no_of_employees",
-   "fieldtype": "Select",
-   "label": "No of Employees",
-   "options": "1-10\n11-20\n21-30\n31-100\n11-50\n51-200\n201-500\n101-500\n500-1000\n501-1000\n>1000\n1000+"
+   "fieldtype": "Int",
+   "label": "No of Employees"
   },
   {
    "fieldname": "annual_revenue",
@@ -603,12 +605,28 @@
    "label": "Notes",
    "no_copy": 1,
    "options": "CRM Note"
+  },
+  {
+   "fieldname": "city",
+   "fieldtype": "Data",
+   "label": "City"
+  },
+  {
+   "fieldname": "state",
+   "fieldtype": "Data",
+   "label": "State"
+  },
+  {
+   "fieldname": "country",
+   "fieldtype": "Link",
+   "label": "Country",
+   "options": "Country"
   }
  ],
  "icon": "fa fa-info-sign",
  "idx": 195,
  "links": [],
- "modified": "2022-06-21 15:04:34.363959",
+ "modified": "2022-06-27 18:44:32.858696",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Opportunity",
diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py
index 33441b1..a2528c3 100644
--- a/erpnext/crm/utils.py
+++ b/erpnext/crm/utils.py
@@ -1,6 +1,7 @@
 import frappe
 from frappe.model.document import Document
-from frappe.utils import cstr, now
+from frappe.utils import cstr, now, today
+from pypika import functions
 
 
 def update_lead_phone_numbers(contact, method):
@@ -177,6 +178,27 @@
 	return data
 
 
+def open_leads_opportunities_based_on_todays_event():
+	event = frappe.qb.DocType("Event")
+	event_link = frappe.qb.DocType("Event Participants")
+
+	query = (
+		frappe.qb.from_(event)
+		.join(event_link)
+		.on(event_link.parent == event.name)
+		.select(event_link.reference_doctype, event_link.reference_docname)
+		.where(
+			(event_link.reference_doctype.isin(["Lead", "Opportunity"]))
+			& (event.status == "Open")
+			& (functions.Date(event.starts_on) == today())
+		)
+	)
+	data = query.run(as_dict=True)
+
+	for d in data:
+		frappe.db.set_value(d.reference_doctype, d.reference_docname, "status", "Open")
+
+
 class CRMNote(Document):
 	@frappe.whitelist()
 	def add_note(self, note):
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index fc420c1..9c1b4b9 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -376,6 +376,14 @@
 		"0/30 * * * *": [
 			"erpnext.utilities.doctype.video.video.update_youtube_data",
 		],
+		# Hourly but offset by 30 minutes
+		"30 * * * *": [
+			"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
+		],
+		# Daily but offset by 45 minutes
+		"45 0 * * *": [
+			"erpnext.stock.reorder_item.reorder_item",
+		],
 	},
 	"all": [
 		"erpnext.projects.doctype.project.project.project_status_update_reminder",
@@ -385,7 +393,6 @@
 	"hourly": [
 		"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
 		"erpnext.accounts.doctype.subscription.subscription.process_all",
-		"erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs",
 		"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
 		"erpnext.projects.doctype.project.project.hourly_reminder",
 		"erpnext.projects.doctype.project.project.collect_project_status",
@@ -396,7 +403,6 @@
 		"erpnext.bulk_transaction.doctype.bulk_transaction_log.bulk_transaction_log.retry_failing_transaction",
 	],
 	"daily": [
-		"erpnext.stock.reorder_item.reorder_item",
 		"erpnext.support.doctype.issue.issue.auto_close_tickets",
 		"erpnext.crm.doctype.opportunity.opportunity.auto_close_opportunity",
 		"erpnext.controllers.accounts_controller.update_invoice_status",
@@ -431,6 +437,7 @@
 		"erpnext.hr.utils.allocate_earned_leaves",
 		"erpnext.loan_management.doctype.process_loan_security_shortfall.process_loan_security_shortfall.create_process_loan_security_shortfall",
 		"erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual.process_loan_interest_accrual_for_term_loans",
+		"erpnext.crm.utils.open_leads_opportunities_based_on_todays_event",
 	],
 	"weekly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_weekly"],
 	"monthly": ["erpnext.hr.doctype.employee.employee_reminders.send_reminders_in_advance_monthly"],
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index e1ccc11..7b7d057 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -624,7 +624,7 @@
 		data = self.get_data_for_eval()
 		for struct_row in self._salary_structure_doc.get(component_type):
 			amount = self.eval_condition_and_formula(struct_row, data)
-			if amount and struct_row.statistical_component == 0:
+			if amount is not None and struct_row.statistical_component == 0:
 				self.update_component_row(struct_row, amount, component_type)
 
 	def get_data_for_eval(self):
@@ -854,6 +854,10 @@
 			component_row, joining_date, relieving_date
 		)[0]
 
+		# remove 0 valued components that have been updated later
+		if component_row.amount == 0:
+			self.remove(component_row)
+
 	def set_precision_for_component_amounts(self):
 		for component_type in ("earnings", "deductions"):
 			for component_row in self.get(component_type):
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 01f72ad..62b98ec 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -1107,9 +1107,25 @@
 		}
 	}
 
+	is_a_mapped_document(item) {
+		const mapped_item_field_map = {
+			"Delivery Note Item": ["si_detail", "so_detail", "dn_detail"],
+			"Sales Invoice Item": ["dn_detail", "so_detail", "sales_invoice_item"],
+			"Purchase Receipt Item": ["purchase_order_item", "purchase_invoice_item", "purchase_receipt_item"],
+			"Purchase Invoice Item": ["purchase_order_item", "pr_detail", "po_detail"],
+		};
+		const mappped_fields = mapped_item_field_map[item.doctype] || [];
+
+		return mappped_fields
+			.map((field) => item[field])
+			.filter(Boolean).length > 0;
+	}
+
 	batch_no(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
-		this.apply_price_list(item, true);
+		if (!this.is_a_mapped_document(item)) {
+			this.apply_price_list(item, true);
+		}
 	}
 
 	toggle_conversion_factor(item) {
@@ -1483,48 +1499,46 @@
 	}
 
 	_set_values_for_item_list(children) {
-		var me = this;
-		var items_rule_dict = {};
+		const items_rule_dict = {};
 
-		for(var i=0, l=children.length; i<l; i++) {
-			var d = children[i] ;
-			let item_row = frappe.get_doc(d.doctype, d.name);
-			var existing_pricing_rule = frappe.model.get_value(d.doctype, d.name, "pricing_rules");
-			for(var k in d) {
-				var v = d[k];
-				if (["doctype", "name"].indexOf(k)===-1) {
-					if(k=="price_list_rate") {
-						item_row['rate'] = v;
+		for (const child of children) {
+			const existing_pricing_rule = frappe.model.get_value(child.doctype, child.name, "pricing_rules");
+
+			for (const [key, value] of Object.entries(child)) {
+				if (!["doctype", "name"].includes(key)) {
+					if (key === "price_list_rate") {
+						frappe.model.set_value(child.doctype, child.name, "rate", value);
 					}
 
-					if (k !== 'free_item_data') {
-						item_row[k] = v;
+					if (key !== "free_item_data") {
+						frappe.model.set_value(child.doctype, child.name, key, value);
 					}
 				}
 			}
 
-			frappe.model.round_floats_in(item_row, ["price_list_rate", "discount_percentage"]);
+			frappe.model.round_floats_in(
+				frappe.get_doc(child.doctype, child.name),
+				["price_list_rate", "discount_percentage"],
+			);
 
 			// if pricing rule set as blank from an existing value, apply price_list
-			if(!me.frm.doc.ignore_pricing_rule && existing_pricing_rule && !d.pricing_rules) {
-				me.apply_price_list(frappe.get_doc(d.doctype, d.name));
-			} else if(!d.pricing_rules) {
-				me.remove_pricing_rule(frappe.get_doc(d.doctype, d.name));
+			if (!this.frm.doc.ignore_pricing_rule && existing_pricing_rule && !child.pricing_rules) {
+				this.apply_price_list(frappe.get_doc(child.doctype, child.name));
+			} else if (!child.pricing_rules) {
+				this.remove_pricing_rule(frappe.get_doc(child.doctype, child.name));
 			}
 
-			if (d.free_item_data.length > 0) {
-				me.apply_product_discount(d);
+			if (child.free_item_data.length > 0) {
+				this.apply_product_discount(child);
 			}
 
-			if (d.apply_rule_on_other_items) {
-				items_rule_dict[d.name] = d;
+			if (child.apply_rule_on_other_items) {
+				items_rule_dict[child.name] = child;
 			}
 		}
 
-		me.frm.refresh_field('items');
-		me.apply_rule_on_other_items(items_rule_dict);
-
-		me.calculate_taxes_and_totals();
+		this.apply_rule_on_other_items(items_rule_dict);
+		this.calculate_taxes_and_totals();
 	}
 
 	apply_rule_on_other_items(args) {
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 01710f1..096175a 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -713,7 +713,7 @@
 			get_query: opts.get_query,
 			add_filters_group: 1,
 			allow_child_item_selection: opts.allow_child_item_selection,
-			child_fieldname: opts.child_fielname,
+			child_fieldname: opts.child_fieldname,
 			child_columns: opts.child_columns,
 			size: opts.size,
 			action: function(selections, args) {
diff --git a/erpnext/selling/doctype/product_bundle/product_bundle.js b/erpnext/selling/doctype/product_bundle/product_bundle.js
index 7a04c6a..3096b69 100644
--- a/erpnext/selling/doctype/product_bundle/product_bundle.js
+++ b/erpnext/selling/doctype/product_bundle/product_bundle.js
@@ -1,19 +1,13 @@
-// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-cur_frm.cscript.refresh = function(doc, cdt, cdn) {
-	cur_frm.toggle_enable('new_item_code', doc.__islocal);
-}
-
-cur_frm.fields_dict.new_item_code.get_query = function() {
-	return{
-		query: "erpnext.selling.doctype.product_bundle.product_bundle.get_new_item_code"
-	}
-}
-cur_frm.fields_dict.new_item_code.query_description = __('Please select Item where "Is Stock Item" is "No" and "Is Sales Item" is "Yes" and there is no other Product Bundle');
-
-cur_frm.cscript.onload = function() {
-	// set add fetch for item_code's item_name and description
-	cur_frm.add_fetch('item_code', 'stock_uom', 'uom');
-	cur_frm.add_fetch('item_code', 'description', 'description');
-}
+frappe.ui.form.on("Product Bundle", {
+	refresh: function (frm) {
+		frm.toggle_enable("new_item_code", frm.is_new());
+		frm.set_query("new_item_code", () => {
+			return {
+				query: "erpnext.selling.doctype.product_bundle.product_bundle.get_new_item_code",
+			};
+		});
+	},
+});
diff --git a/erpnext/selling/doctype/product_bundle_item/product_bundle_item.json b/erpnext/selling/doctype/product_bundle_item/product_bundle_item.json
index dc071e4..fc8caeb 100644
--- a/erpnext/selling/doctype/product_bundle_item/product_bundle_item.json
+++ b/erpnext/selling/doctype/product_bundle_item/product_bundle_item.json
@@ -33,6 +33,8 @@
    "reqd": 1
   },
   {
+   "fetch_from": "item_code.description",
+   "fetch_if_empty": 1,
    "fieldname": "description",
    "fieldtype": "Text Editor",
    "in_list_view": 1,
@@ -51,6 +53,8 @@
    "print_hide": 1
   },
   {
+   "fetch_from": "item_code.stock_uom",
+   "fetch_if_empty": 1,
    "fieldname": "uom",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -64,7 +68,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-02-28 14:06:05.725655",
+ "modified": "2022-06-27 05:30:18.475150",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Product Bundle Item",
@@ -72,5 +76,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index a513b8f..863fbc4 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -207,6 +207,15 @@
 
 def _make_sales_order(source_name, target_doc=None, ignore_permissions=False):
 	customer = _make_customer(source_name, ignore_permissions)
+	ordered_items = frappe._dict(
+		frappe.db.get_all(
+			"Sales Order Item",
+			{"prevdoc_docname": source_name, "docstatus": 1},
+			["item_code", "sum(qty)"],
+			group_by="item_code",
+			as_list=1,
+		)
+	)
 
 	def set_missing_values(source, target):
 		if customer:
@@ -222,7 +231,9 @@
 		target.run_method("calculate_taxes_and_totals")
 
 	def update_item(obj, target, source_parent):
-		target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
+		balance_qty = obj.qty - ordered_items.get(obj.item_code, 0.0)
+		target.qty = balance_qty if balance_qty > 0 else 0
+		target.stock_qty = flt(target.qty) * flt(obj.conversion_factor)
 
 		if obj.against_blanket_order:
 			target.against_blanket_order = obj.against_blanket_order
@@ -238,6 +249,7 @@
 				"doctype": "Sales Order Item",
 				"field_map": {"parent": "prevdoc_docname"},
 				"postprocess": update_item,
+				"condition": lambda doc: doc.qty > 0,
 			},
 			"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
 			"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 0de5b2d..3dec303 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -219,8 +219,8 @@
 		["default_discount_account", {}],
 		["discount_allowed_account", {"root_type": "Expense"}],
 		["discount_received_account", {"root_type": "Income"}],
-		["exchange_gain_loss_account", {"root_type": "Expense"}],
-		["unrealized_exchange_gain_loss_account", {"root_type": "Expense"}],
+		["exchange_gain_loss_account", {"root_type": ["in", ["Expense", "Income"]]}],
+		["unrealized_exchange_gain_loss_account", {"root_type": ["in", ["Expense", "Income"]]}],
 		["accumulated_depreciation_account",
 			{"root_type": "Asset", "account_type": "Accumulated Depreciation"}],
 		["depreciation_expense_account", {"root_type": "Expense", "account_type": "Depreciation"}],
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index c9d5f61..6e7622c 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -260,6 +260,16 @@
 	}
 
 	dialog.set_primary_action(__('Create Stock Entry'), function () {
+		if (source && (dialog.get_value("qty") == 0 || dialog.get_value("qty") > actual_qty)) {
+			frappe.msgprint(__("Quantity must be greater than zero, and less or equal to {0}", [actual_qty]));
+			return;
+		}
+
+		if (dialog.get_value("source") === dialog.get_value("target")) {
+			frappe.msgprint(__("Source and target warehouse must be different"));
+			return;
+		}
+
 		frappe.model.with_doctype('Stock Entry', function () {
 			let doc = frappe.model.get_new_doc('Stock Entry');
 			doc.from_warehouse = dialog.get_value('source');
diff --git a/erpnext/utilities/doctype/video/video.py b/erpnext/utilities/doctype/video/video.py
index a39d0a9..15dbccd 100644
--- a/erpnext/utilities/doctype/video/video.py
+++ b/erpnext/utilities/doctype/video/video.py
@@ -9,6 +9,7 @@
 import pytz
 from frappe import _
 from frappe.model.document import Document
+from frappe.utils import cint
 from pyyoutube import Api
 
 
@@ -46,7 +47,7 @@
 def get_frequency(value):
 	# Return numeric value from frequency field, return 1 as fallback default value: 1 hour
 	if value != "Daily":
-		return frappe.utils.cint(value[:2].strip())
+		return cint(value[:2].strip())
 	elif value:
 		return 24
 	return 1
@@ -120,24 +121,12 @@
 			video_stats = entry.to_dict().get("statistics")
 			video_id = entry.to_dict().get("id")
 			stats = {
-				"like_count": video_stats.get("likeCount"),
-				"view_count": video_stats.get("viewCount"),
-				"dislike_count": video_stats.get("dislikeCount"),
-				"comment_count": video_stats.get("commentCount"),
-				"video_id": video_id,
+				"like_count": cint(video_stats.get("likeCount")),
+				"view_count": cint(video_stats.get("viewCount")),
+				"dislike_count": cint(video_stats.get("dislikeCount")),
+				"comment_count": cint(video_stats.get("commentCount")),
 			}
-
-			frappe.db.sql(
-				"""
-				UPDATE `tabVideo`
-				SET
-					like_count  = %(like_count)s,
-					view_count = %(view_count)s,
-					dislike_count = %(dislike_count)s,
-					comment_count = %(comment_count)s
-				WHERE youtube_video_id = %(video_id)s""",
-				stats,
-			)
+			frappe.db.set_value("Video", video_id, stats)
 
 	video_list = frappe.get_all("Video", fields=["youtube_video_id"])
 	if len(video_list) > 50:
diff --git a/pyproject.toml b/pyproject.toml
index 8043dd9..5acfd39 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,35 @@
+[project]
+name = "erpnext"
+authors = [
+    { name = "Frappe Technologies Pvt Ltd", email = "developers@frappe.io"}
+]
+description = "Open Source ERP"
+requires-python = ">=3.10"
+readme = "README.md"
+dynamic = ["version"]
+dependencies = [
+    # Core dependencies
+    "pycountry~=20.7.3",
+    "python-stdnum~=1.16",
+    "Unidecode~=1.2.0",
+    "redisearch~=2.1.0",
+
+    # integration dependencies
+    "gocardless-pro~=1.22.0",
+    "googlemaps",
+    "plaid-python~=7.2.1",
+    "python-youtube~=0.8.0",
+    "taxjar~=1.9.2",
+    "tweepy~=3.10.0",
+]
+
+[build-system]
+requires = ["flit_core >=3.4,<4"]
+build-backend = "flit_core.buildapi"
+
+[tool.bench.dev-dependencies]
+hypothesis = "~=6.31.0"
+
 [tool.black]
 line-length = 99
 
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 83e5375..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,11 +0,0 @@
-# frappe   # https://github.com/frappe/frappe is installed during bench-init
-gocardless-pro~=1.22.0
-googlemaps
-plaid-python~=7.2.1
-pycountry~=20.7.3
-python-stdnum~=1.16
-python-youtube~=0.8.0
-taxjar~=1.9.2
-tweepy~=3.10.0
-Unidecode~=1.2.0
-redisearch~=2.1.0
diff --git a/setup.py b/setup.py
index 1faff04..0ea4d07 100644
--- a/setup.py
+++ b/setup.py
@@ -1,23 +1,6 @@
-from setuptools import setup, find_packages
-import re, ast
+# TODO: Remove this file when  v15.0.0 is released
+from setuptools import setup
 
-# get version from __version__ variable in erpnext/__init__.py
-_version_re = re.compile(r"__version__\s+=\s+(.*)")
+name = "frappe"
 
-with open("requirements.txt") as f:
-	install_requires = f.read().strip().split("\n")
-
-with open("erpnext/__init__.py", "rb") as f:
-	version = str(ast.literal_eval(_version_re.search(f.read().decode("utf-8")).group(1)))
-
-setup(
-	name="erpnext",
-	version=version,
-	description="Open Source ERP",
-	author="Frappe Technologies",
-	author_email="info@erpnext.com",
-	packages=find_packages(),
-	zip_safe=False,
-	include_package_data=True,
-	install_requires=install_requires,
-)
+setup()