Merge pull request #31410 from alyf-de/apply-price-list

fix: apply price list rate
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/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 3cc28a3..4e7a653 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -149,22 +149,6 @@
 					}
 				});
 			}
-			else if(frm.doc.voucher_type=="Opening Entry") {
-				return frappe.call({
-					type:"GET",
-					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_opening_accounts",
-					args: {
-						"company": frm.doc.company
-					},
-					callback: function(r) {
-						frappe.model.clear_table(frm.doc, "accounts");
-						if(r.message) {
-							update_jv_details(frm.doc, r.message);
-						}
-						cur_frm.set_value("is_opening", "Yes");
-					}
-				});
-			}
 		}
 	},
 
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 4493c72..8e5ba37 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -137,7 +137,8 @@
    "fieldname": "finance_book",
    "fieldtype": "Link",
    "label": "Finance Book",
-   "options": "Finance Book"
+   "options": "Finance Book",
+   "read_only": 1
   },
   {
    "fieldname": "2_add_edit_gl_entries",
@@ -538,7 +539,7 @@
  "idx": 176,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-04-06 17:18:46.865259",
+ "modified": "2022-06-23 22:01:32.348337",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 787efd2..50df65b 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -1205,24 +1205,6 @@
 
 
 @frappe.whitelist()
-def get_opening_accounts(company):
-	"""get all balance sheet accounts for opening entry"""
-	accounts = frappe.db.sql_list(
-		"""select
-			name from tabAccount
-		where
-			is_group=0 and report_type='Balance Sheet' and company={0} and
-			name not in (select distinct account from tabWarehouse where
-			account is not null and account != '')
-		order by name asc""".format(
-			frappe.db.escape(company)
-		)
-	)
-
-	return [{"account": a, "balance": get_balance_on(a)} for a in accounts]
-
-
-@frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
 	if not frappe.db.has_column("Journal Entry", searchfield):
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 23ad223..4e0d1c9 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -165,17 +165,6 @@
 
 		super(PurchaseInvoice, self).set_missing_values(for_validate)
 
-	def check_conversion_rate(self):
-		default_currency = erpnext.get_company_currency(self.company)
-		if not default_currency:
-			throw(_("Please enter default currency in Company Master"))
-		if (
-			(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
-			or not self.conversion_rate
-			or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
-		):
-			throw(_("Conversion rate cannot be 0 or 1"))
-
 	def validate_credit_to_acc(self):
 		if not self.credit_to:
 			self.credit_to = get_party_account("Supplier", self.supplier, self.company)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index a580d45..657cd99 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -114,6 +114,7 @@
 		self.set_income_account_for_fixed_assets()
 		self.validate_item_cost_centers()
 		self.validate_income_account()
+		self.check_conversion_rate()
 
 		validate_inter_company_party(
 			self.doctype, self.customer, self.company, self.inter_company_invoice_reference
@@ -2116,6 +2117,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)
 
@@ -2180,12 +2183,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"]:
@@ -2223,6 +2231,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/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index b8154dd..448ec54 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -792,6 +792,54 @@
 		jv.cancel()
 		self.assertEqual(frappe.db.get_value("Sales Invoice", w.name, "outstanding_amount"), 562.0)
 
+	def test_outstanding_on_cost_center_allocation(self):
+		# setup cost centers
+		from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
+		from erpnext.accounts.doctype.cost_center_allocation.test_cost_center_allocation import (
+			create_cost_center_allocation,
+		)
+
+		cost_centers = [
+			"Main Cost Center 1",
+			"Sub Cost Center 1",
+			"Sub Cost Center 2",
+		]
+		for cc in cost_centers:
+			create_cost_center(cost_center_name=cc, company="_Test Company")
+
+		cca = create_cost_center_allocation(
+			"_Test Company",
+			"Main Cost Center 1 - _TC",
+			{"Sub Cost Center 1 - _TC": 60, "Sub Cost Center 2 - _TC": 40},
+		)
+
+		# make invoice
+		si = frappe.copy_doc(test_records[0])
+		si.is_pos = 0
+		si.insert()
+		si.submit()
+
+		from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+
+		# make payment - fully paid
+		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
+		pe.reference_no = "1"
+		pe.reference_date = nowdate()
+		pe.paid_from_account_currency = si.currency
+		pe.paid_to_account_currency = si.currency
+		pe.source_exchange_rate = 1
+		pe.target_exchange_rate = 1
+		pe.paid_amount = si.outstanding_amount
+		pe.cost_center = cca.main_cost_center
+		pe.insert()
+		pe.submit()
+
+		# cancel cost center allocation
+		cca.cancel()
+
+		si.reload()
+		self.assertEqual(si.outstanding_amount, 0)
+
 	def test_sales_invoice_gl_entry_without_perpetual_inventory(self):
 		si = frappe.copy_doc(test_records[1])
 		si.insert()
@@ -1583,6 +1631,17 @@
 
 		self.assertTrue(gle)
 
+	def test_invoice_exchange_rate(self):
+		si = create_sales_invoice(
+			customer="_Test Customer USD",
+			debit_to="_Test Receivable USD - _TC",
+			currency="USD",
+			conversion_rate=1,
+			do_not_save=1,
+		)
+
+		self.assertRaises(frappe.ValidationError, si.save)
+
 	def test_invalid_currency(self):
 		# Customer currency = USD
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 8146804..76ef3ab 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -132,7 +132,7 @@
 			for sub_cost_center, percentage in cost_center_allocation.get(cost_center, {}).items():
 				gle = copy.deepcopy(d)
 				gle.cost_center = sub_cost_center
-				for field in ("debit", "credit", "debit_in_account_currency", "credit_in_company_currency"):
+				for field in ("debit", "credit", "debit_in_account_currency", "credit_in_account_currency"):
 					gle[field] = flt(flt(d.get(field)) * percentage / 100, precision)
 				new_gl_map.append(gle)
 		else:
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/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
index 20f7643..9d2deea 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
@@ -43,7 +43,7 @@
 			"options": "Account",
 			"width": 170,
 		},
-		{"label": _("Amount"), "fieldname": "amount", "width": 120},
+		{"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 120},
 	]
 
 	return columns
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.py b/erpnext/accounts/report/general_ledger/general_ledger.py
index e4b561e..e77e828 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/general_ledger.py
@@ -425,7 +425,7 @@
 			update_value_in_dict(totals, "opening", gle)
 			update_value_in_dict(totals, "closing", gle)
 
-		elif gle.posting_date <= to_date:
+		elif gle.posting_date <= to_date or (cstr(gle.is_opening) == "Yes" and show_opening_entries):
 			if not group_by_voucher_consolidated:
 				update_value_in_dict(gle_map[group_by_value].totals, "total", gle)
 				update_value_in_dict(gle_map[group_by_value].totals, "closing", gle)
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 257488d..a880c2f 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -252,6 +252,7 @@
 			number_of_pending_depreciations += 1
 
 		skip_row = False
+		should_get_last_day = is_last_day_of_the_month(finance_book.depreciation_start_date)
 
 		for n in range(start[finance_book.idx - 1], number_of_pending_depreciations):
 			# If depreciation is already completed (for double declining balance)
@@ -265,6 +266,9 @@
 					finance_book.depreciation_start_date, n * cint(finance_book.frequency_of_depreciation)
 				)
 
+				if should_get_last_day:
+					schedule_date = get_last_day(schedule_date)
+
 				# schedule date will be a year later from start date
 				# so monthly schedule date is calculated by removing 11 months from it
 				monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
@@ -849,14 +853,9 @@
 			if args.get("rate_of_depreciation") and on_validate:
 				return args.get("rate_of_depreciation")
 
-			no_of_years = (
-				flt(args.get("total_number_of_depreciations") * flt(args.get("frequency_of_depreciation")))
-				/ 12
-			)
 			value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
 
-			# square root of flt(salvage_value) / flt(asset_cost)
-			depreciation_rate = math.pow(value, 1.0 / flt(no_of_years, 2))
+			depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
 
 			return 100 * (1 - flt(depreciation_rate, float_precision))
 
@@ -1105,9 +1104,18 @@
 def get_total_days(date, frequency):
 	period_start_date = add_months(date, cint(frequency) * -1)
 
+	if is_last_day_of_the_month(date):
+		period_start_date = get_last_day(period_start_date)
+
 	return date_diff(date, period_start_date)
 
 
+def is_last_day_of_the_month(date):
+	last_day_of_the_month = get_last_day(date)
+
+	return getdate(last_day_of_the_month) == getdate(date)
+
+
 @erpnext.allow_regional
 def get_depreciation_amount(asset, depreciable_value, row):
 	if row.depreciation_method in ("Straight Line", "Manual"):
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index e759ad0..f8a8fc5 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -707,6 +707,39 @@
 
 		self.assertEqual(schedules, expected_schedules)
 
+	def test_monthly_depreciation_by_wdv_method(self):
+		asset = create_asset(
+			calculate_depreciation=1,
+			available_for_use_date="2022-02-15",
+			purchase_date="2022-02-15",
+			depreciation_method="Written Down Value",
+			gross_purchase_amount=10000,
+			expected_value_after_useful_life=5000,
+			depreciation_start_date="2022-02-28",
+			total_number_of_depreciations=5,
+			frequency_of_depreciation=1,
+		)
+
+		expected_schedules = [
+			["2022-02-28", 645.0, 645.0],
+			["2022-03-31", 1206.8, 1851.8],
+			["2022-04-30", 1051.12, 2902.92],
+			["2022-05-31", 915.52, 3818.44],
+			["2022-06-30", 797.42, 4615.86],
+			["2022-07-15", 384.14, 5000.0],
+		]
+
+		schedules = [
+			[
+				cstr(d.schedule_date),
+				flt(d.depreciation_amount, 2),
+				flt(d.accumulated_depreciation_amount, 2),
+			]
+			for d in asset.get("schedules")
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
 	def test_discounted_wdv_depreciation_rate_for_indian_region(self):
 		# set indian company
 		company_flag = frappe.flags.company
@@ -838,7 +871,7 @@
 		expected_values = [["2020-12-31", 30000.0], ["2021-12-31", 30000.0], ["2022-12-31", 30000.0]]
 
 		for i, schedule in enumerate(asset.schedules):
-			self.assertEqual(expected_values[i][0], schedule.schedule_date)
+			self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
 			self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
 
 	def test_set_accumulated_depreciation(self):
@@ -1333,6 +1366,32 @@
 		asset.cost_center = "Main - _TC"
 		asset.submit()
 
+	def test_depreciation_on_final_day_of_the_month(self):
+		"""Tests if final day of the month is picked each time, if the depreciation start date is the last day of the month."""
+
+		asset = create_asset(
+			item_code="Macbook Pro",
+			calculate_depreciation=1,
+			purchase_date="2020-01-30",
+			available_for_use_date="2020-02-15",
+			depreciation_start_date="2020-02-29",
+			frequency_of_depreciation=1,
+			total_number_of_depreciations=5,
+			submit=1,
+		)
+
+		expected_dates = [
+			"2020-02-29",
+			"2020-03-31",
+			"2020-04-30",
+			"2020-05-31",
+			"2020-06-30",
+			"2020-07-15",
+		]
+
+		for i, schedule in enumerate(asset.schedules):
+			self.assertEqual(getdate(expected_dates[i]), getdate(schedule.schedule_date))
+
 
 def create_asset_data():
 	if not frappe.db.exists("Asset Category", "Computers"):
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/buying/report/purchase_order_analysis/purchase_order_analysis.js b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
index ca3be03..721e54e 100644
--- a/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
+++ b/erpnext/buying/report/purchase_order_analysis/purchase_order_analysis.js
@@ -59,6 +59,7 @@
 				for (let option of status){
 					options.push({
 						"value": option,
+						"label": __(option),
 						"description": ""
 					})
 				}
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index ded9a30..ceac815 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1848,6 +1848,17 @@
 		jv.save()
 		jv.submit()
 
+	def check_conversion_rate(self):
+		default_currency = erpnext.get_company_currency(self.company)
+		if not default_currency:
+			throw(_("Please enter default currency in Company Master"))
+		if (
+			(self.currency == default_currency and flt(self.conversion_rate) != 1.00)
+			or not self.conversion_rate
+			or (self.currency != default_currency and flt(self.conversion_rate) == 1.00)
+		):
+			throw(_("Conversion rate cannot be 0 or 1"))
+
 
 @frappe.whitelist()
 def get_tax_rate(account_head):
@@ -2429,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:
@@ -2448,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"):
@@ -2511,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
@@ -2528,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:
@@ -2550,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
@@ -2563,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
@@ -2668,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/stock_controller.py b/erpnext/controllers/stock_controller.py
index feec42f..e90a4f6 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -166,7 +166,7 @@
 									"against": warehouse_account[sle.warehouse]["account"],
 									"cost_center": item_row.cost_center,
 									"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
-									"credit": flt(sle.stock_value_difference, precision),
+									"debit": -1 * flt(sle.stock_value_difference, precision),
 									"project": item_row.get("project") or self.get("project"),
 									"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
 								},
diff --git a/erpnext/crm/doctype/crm_note/__init__.py b/erpnext/crm/doctype/crm_note/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/crm_note/__init__.py
diff --git a/erpnext/crm/doctype/crm_note/crm_note.json b/erpnext/crm/doctype/crm_note/crm_note.json
new file mode 100644
index 0000000..fc2a4d1
--- /dev/null
+++ b/erpnext/crm/doctype/crm_note/crm_note.json
@@ -0,0 +1,48 @@
+{
+ "actions": [],
+ "autoname": "autoincrement",
+ "creation": "2022-06-04 15:49:23.416644",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "note",
+  "added_by",
+  "added_on"
+ ],
+ "fields": [
+  {
+   "columns": 5,
+   "fieldname": "note",
+   "fieldtype": "Text Editor",
+   "in_list_view": 1,
+   "label": "Note"
+  },
+  {
+   "fieldname": "added_by",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Added By",
+   "options": "User"
+  },
+  {
+   "fieldname": "added_on",
+   "fieldtype": "Datetime",
+   "in_list_view": 1,
+   "label": "Added On"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-06-04 16:29:07.807252",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "CRM Note",
+ "naming_rule": "Autoincrement",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/crm_note/crm_note.py b/erpnext/crm/doctype/crm_note/crm_note.py
new file mode 100644
index 0000000..6c7eeb4
--- /dev/null
+++ b/erpnext/crm/doctype/crm_note/crm_note.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class CRMNote(Document):
+	pass
diff --git a/erpnext/crm/doctype/crm_settings/crm_settings.json b/erpnext/crm/doctype/crm_settings/crm_settings.json
index a2a19b9..26a07d2 100644
--- a/erpnext/crm/doctype/crm_settings/crm_settings.json
+++ b/erpnext/crm/doctype/crm_settings/crm_settings.json
@@ -10,12 +10,10 @@
   "campaign_naming_by",
   "allow_lead_duplication_based_on_emails",
   "column_break_4",
-  "create_event_on_next_contact_date",
   "auto_creation_of_contact",
   "opportunity_section",
   "close_opportunity_after_days",
   "column_break_9",
-  "create_event_on_next_contact_date_opportunity",
   "quotation_section",
   "default_valid_till",
   "section_break_13",
@@ -56,12 +54,6 @@
    "label": "Auto Creation of Contact"
   },
   {
-   "default": "1",
-   "fieldname": "create_event_on_next_contact_date",
-   "fieldtype": "Check",
-   "label": "Create Event on Next Contact Date"
-  },
-  {
    "fieldname": "opportunity_section",
    "fieldtype": "Section Break",
    "label": "Opportunity"
@@ -74,12 +66,6 @@
    "label": "Close Replied Opportunity After Days"
   },
   {
-   "default": "1",
-   "fieldname": "create_event_on_next_contact_date_opportunity",
-   "fieldtype": "Check",
-   "label": "Create Event on Next Contact Date"
-  },
-  {
    "fieldname": "column_break_4",
    "fieldtype": "Column Break"
   },
@@ -105,7 +91,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-12-20 12:51:38.894252",
+ "modified": "2022-06-06 11:22:08.464253",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "CRM Settings",
@@ -143,5 +129,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 999599c..37fb350 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -24,31 +24,39 @@
 		this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
 			return { query: "frappe.core.doctype.user.user.user_query" }
 		});
-
-		this.frm.set_query("contact_by", function (doc, cdt, cdn) {
-			return { query: "frappe.core.doctype.user.user.user_query" }
-		});
 	}
 
 	refresh () {
+		var me = this;
 		let doc = this.frm.doc;
 		erpnext.toggle_naming_series();
-		frappe.dynamic_link = { doc: doc, fieldname: 'name', doctype: 'Lead' }
+		frappe.dynamic_link = {
+			doc: doc,
+			fieldname: 'name',
+			doctype: 'Lead'
+		};
 
 		if (!this.frm.is_new() && doc.__onload && !doc.__onload.is_customer) {
 			this.frm.add_custom_button(__("Customer"), this.make_customer, __("Create"));
-			this.frm.add_custom_button(__("Opportunity"), this.make_opportunity, __("Create"));
+			this.frm.add_custom_button(__("Opportunity"), function() {
+				me.frm.trigger("make_opportunity");
+			}, __("Create"));
 			this.frm.add_custom_button(__("Quotation"), this.make_quotation, __("Create"));
-			this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create"));
-			this.frm.add_custom_button(__('Add to Prospect'), this.add_lead_to_prospect, __('Action'));
+			if (!doc.__onload.linked_prospects.length) {
+				this.frm.add_custom_button(__("Prospect"), this.make_prospect, __("Create"));
+				this.frm.add_custom_button(__('Add to Prospect'), this.add_lead_to_prospect, __('Action'));
+			}
 		}
 
 		if (!this.frm.is_new()) {
 			frappe.contacts.render_address_and_contact(this.frm);
-			cur_frm.trigger('render_contact_day_html');
 		} else {
 			frappe.contacts.clear_address_and_contact(this.frm);
 		}
+
+		this.frm.dashboard.links_area.hide();
+		this.show_notes();
+		this.show_activities();
 	}
 
 	add_lead_to_prospect () {
@@ -74,7 +82,7 @@
 					}
 				},
 				freeze: true,
-				freeze_message: __('...Adding Lead to Prospect')
+				freeze_message: __('Adding Lead to Prospect...')
 			});
 		}, __('Add Lead to Prospect'), __('Add'));
 	}
@@ -86,13 +94,6 @@
 		})
 	}
 
-	make_opportunity () {
-		frappe.model.open_mapped_doc({
-			method: "erpnext.crm.doctype.lead.lead.make_opportunity",
-			frm: cur_frm
-		})
-	}
-
 	make_quotation () {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.crm.doctype.lead.lead.make_quotation",
@@ -111,9 +112,10 @@
 			prospect.fax = cur_frm.doc.fax;
 			prospect.website = cur_frm.doc.website;
 			prospect.prospect_owner = cur_frm.doc.lead_owner;
+			prospect.notes = cur_frm.doc.notes;
 
-			let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead');
-			lead_prospect_row.lead = cur_frm.doc.name;
+			let leads_row = frappe.model.add_child(prospect, 'leads');
+			leads_row.lead = cur_frm.doc.name;
 
 			frappe.set_route("Form", "Prospect", prospect.name);
 		});
@@ -125,26 +127,109 @@
 		}
 	}
 
-	contact_date () {
-		if (this.frm.doc.contact_date) {
-			let d = moment(this.frm.doc.contact_date);
-			d.add(1, "day");
-			this.frm.set_value("ends_on", d.format(frappe.defaultDatetimeFormat));
-		}
+	show_notes() {
+		if (this.frm.doc.docstatus == 1) return;
+
+		const crm_notes = new erpnext.utils.CRMNotes({
+			frm: this.frm,
+			notes_wrapper: $(this.frm.fields_dict.notes_html.wrapper),
+		});
+		crm_notes.refresh();
 	}
 
-	render_contact_day_html() {
-		if (cur_frm.doc.contact_date) {
-			let contact_date = frappe.datetime.obj_to_str(cur_frm.doc.contact_date);
-			let diff_days = frappe.datetime.get_day_diff(contact_date, frappe.datetime.get_today());
-			let color = diff_days > 0 ? "orange" : "green";
-			let message = diff_days > 0 ? __("Next Contact Date") : __("Last Contact Date");
-			let html = `<div class="col-xs-12">
-						<span class="indicator whitespace-nowrap ${color}"><span> ${message} : ${frappe.datetime.global_date_format(contact_date)}</span></span>
-					</div>` ;
-			cur_frm.dashboard.set_headline_alert(html);
-		}
+	show_activities() {
+		if (this.frm.doc.docstatus == 1) return;
+
+		const crm_activities = new erpnext.utils.CRMActivities({
+			frm: this.frm,
+			open_activities_wrapper: $(this.frm.fields_dict.open_activities_html.wrapper),
+			all_activities_wrapper: $(this.frm.fields_dict.all_activities_html.wrapper),
+			form_wrapper: $(this.frm.wrapper),
+		});
+		crm_activities.refresh();
 	}
 };
 
+
 extend_cscript(cur_frm.cscript, new erpnext.LeadController({ frm: cur_frm }));
+
+frappe.ui.form.on("Lead", {
+	make_opportunity: async function(frm) {
+		let existing_prospect = (await frappe.db.get_value("Prospect Lead",
+			{
+				"lead": frm.doc.name
+			},
+			"name", null, "Prospect"
+		)).message.name;
+
+		if (!existing_prospect) {
+			var fields = [
+				{
+					"label": "Create Prospect",
+					"fieldname": "create_prospect",
+					"fieldtype": "Check",
+					"default": 1
+				},
+				{
+					"label": "Prospect Name",
+					"fieldname": "prospect_name",
+					"fieldtype": "Data",
+					"default": frm.doc.company_name,
+					"depends_on": "create_prospect"
+				}
+			];
+		}
+		let existing_contact = (await frappe.db.get_value("Contact",
+			{
+				"first_name": frm.doc.first_name || frm.doc.lead_name,
+				"last_name": frm.doc.last_name
+			},
+			"name"
+		)).message.name;
+
+		if (!existing_contact) {
+			fields.push(
+				{
+					"label": "Create Contact",
+					"fieldname": "create_contact",
+					"fieldtype": "Check",
+					"default": "1"
+				}
+			);
+		}
+
+		if (fields) {
+			var d = new frappe.ui.Dialog({
+				title: __('Create Opportunity'),
+				fields: fields,
+				primary_action: function() {
+					var data = d.get_values();
+					frappe.call({
+						method: 'create_prospect_and_contact',
+						doc: frm.doc,
+						args: {
+							data: data,
+						},
+						freeze: true,
+						callback: function(r) {
+							if (!r.exc) {
+								frappe.model.open_mapped_doc({
+									method: "erpnext.crm.doctype.lead.lead.make_opportunity",
+									frm: frm
+								});
+							}
+							d.hide();
+						}
+					});
+				},
+				primary_action_label: __('Create')
+			});
+			d.show();
+		} else {
+			frappe.model.open_mapped_doc({
+				method: "erpnext.crm.doctype.lead.lead.make_opportunity",
+				frm: frm
+			});
+		}
+	}
+});
\ No newline at end of file
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index 542977e..d47373f 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -3,79 +3,81 @@
  "allow_events_in_timeline": 1,
  "allow_import": 1,
  "autoname": "naming_series:",
- "creation": "2013-04-10 11:45:37",
+ "creation": "2022-02-08 13:14:41.083327",
  "doctype": "DocType",
  "document_type": "Document",
  "email_append_to": 1,
  "engine": "InnoDB",
  "field_order": [
-  "lead_details",
   "naming_series",
   "salutation",
   "first_name",
   "middle_name",
   "last_name",
+  "column_break_1",
   "lead_name",
-  "col_break123",
-  "status",
-  "company_name",
-  "designation",
+  "job_title",
   "gender",
-  "contact_details_section",
+  "source",
+  "col_break123",
+  "lead_owner",
+  "status",
+  "customer",
+  "type",
+  "request_type",
+  "contact_info_tab",
   "email_id",
+  "website",
+  "column_break_20",
   "mobile_no",
   "whatsapp_no",
   "column_break_16",
   "phone",
   "phone_ext",
-  "additional_information_section",
+  "organization_section",
+  "company_name",
   "no_of_employees",
+  "column_break_28",
+  "annual_revenue",
   "industry",
   "market_segment",
-  "column_break_22",
+  "column_break_31",
+  "territory",
   "fax",
-  "website",
-  "type",
-  "request_type",
   "address_section",
   "address_html",
+  "column_break_38",
   "city",
-  "pincode",
-  "county",
-  "column_break2",
-  "contact_html",
   "state",
   "country",
-  "section_break_12",
-  "lead_owner",
-  "ends_on",
-  "column_break_14",
-  "contact_by",
-  "contact_date",
-  "lead_source_details_section",
-  "company",
-  "territory",
-  "language",
-  "column_break_50",
-  "source",
+  "column_break2",
+  "contact_html",
+  "qualification_tab",
+  "qualification_status",
+  "column_break_64",
+  "qualified_by",
+  "qualified_on",
+  "other_info_tab",
   "campaign_name",
+  "company",
+  "column_break_22",
+  "language",
+  "image",
+  "title",
+  "column_break_50",
+  "disabled",
   "unsubscribed",
   "blog_subscriber",
-  "notes_section",
-  "notes",
-  "other_information_section",
-  "customer",
-  "image",
-  "title"
+  "activities_tab",
+  "open_activities_html",
+  "all_activities_section",
+  "all_activities_html",
+  "notes_tab",
+  "notes_html",
+  "notes"
  ],
  "fields": [
   {
-   "fieldname": "lead_details",
-   "fieldtype": "Section Break",
-   "label": "Lead Details",
-   "options": "fa fa-user"
-  },
-  {
    "fieldname": "naming_series",
    "fieldtype": "Select",
    "label": "Series",
@@ -86,6 +88,7 @@
    "set_only_once": 1
   },
   {
+   "depends_on": "eval:!doc.__islocal",
    "fieldname": "lead_name",
    "fieldtype": "Data",
    "in_global_search": 1,
@@ -108,7 +111,7 @@
   {
    "fieldname": "email_id",
    "fieldtype": "Data",
-   "label": "Email Address",
+   "label": "Email",
    "oldfieldname": "email_id",
    "oldfieldtype": "Data",
    "options": "Email",
@@ -189,50 +192,9 @@
    "print_hide": 1
   },
   {
-   "fieldname": "section_break_12",
+   "fieldname": "contact_info_tab",
    "fieldtype": "Section Break",
-   "label": "Follow Up"
-  },
-  {
-   "fieldname": "contact_by",
-   "fieldtype": "Link",
-   "label": "Next Contact By",
-   "oldfieldname": "contact_by",
-   "oldfieldtype": "Link",
-   "options": "User",
-   "width": "100px"
-  },
-  {
-   "fieldname": "column_break_14",
-   "fieldtype": "Column Break"
-  },
-  {
-   "bold": 1,
-   "fieldname": "contact_date",
-   "fieldtype": "Datetime",
-   "label": "Next Contact Date",
-   "no_copy": 1,
-   "oldfieldname": "contact_date",
-   "oldfieldtype": "Date",
-   "width": "100px"
-  },
-  {
-   "bold": 1,
-   "fieldname": "ends_on",
-   "fieldtype": "Datetime",
-   "label": "Ends On",
-   "no_copy": 1
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "notes_section",
-   "fieldtype": "Section Break",
-   "label": "Notes"
-  },
-  {
-   "fieldname": "notes",
-   "fieldtype": "Text Editor",
-   "label": "Notes"
+   "label": "Contact Info"
   },
   {
    "fieldname": "address_html",
@@ -241,34 +203,6 @@
    "read_only": 1
   },
   {
-   "fieldname": "city",
-   "fieldtype": "Data",
-   "label": "City/Town",
-   "mandatory_depends_on": "eval: doc.address_title && doc.address_type"
-  },
-  {
-   "fieldname": "county",
-   "fieldtype": "Data",
-   "label": "County"
-  },
-  {
-   "fieldname": "state",
-   "fieldtype": "Data",
-   "label": "State"
-  },
-  {
-   "fieldname": "country",
-   "fieldtype": "Link",
-   "label": "Country",
-   "mandatory_depends_on": "eval: doc.address_title && doc.address_type",
-   "options": "Country"
-  },
-  {
-   "fieldname": "pincode",
-   "fieldtype": "Data",
-   "label": "Postal Code"
-  },
-  {
    "fieldname": "column_break2",
    "fieldtype": "Column Break"
   },
@@ -289,7 +223,7 @@
   {
    "fieldname": "mobile_no",
    "fieldtype": "Data",
-   "label": "Mobile No.",
+   "label": "Mobile No",
    "oldfieldname": "mobile_no",
    "oldfieldtype": "Data",
    "options": "Phone"
@@ -347,8 +281,7 @@
    "fieldtype": "Data",
    "label": "Website",
    "oldfieldname": "website",
-   "oldfieldtype": "Data",
-   "options": "URL"
+   "oldfieldtype": "Data"
   },
   {
    "fieldname": "territory",
@@ -381,14 +314,6 @@
    "print_hide": 1
   },
   {
-   "fieldname": "designation",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "in_standard_filter": 1,
-   "label": "Designation",
-   "options": "Designation"
-  },
-  {
    "fieldname": "language",
    "fieldtype": "Link",
    "label": "Print Language",
@@ -411,12 +336,6 @@
    "label": "Last Name"
   },
   {
-   "collapsible": 1,
-   "fieldname": "additional_information_section",
-   "fieldtype": "Section Break",
-   "label": "Additional Information"
-  },
-  {
    "fieldname": "no_of_employees",
    "fieldtype": "Int",
    "label": "No. of Employees"
@@ -428,36 +347,14 @@
   {
    "fieldname": "whatsapp_no",
    "fieldtype": "Data",
-   "label": "WhatsApp No.",
+   "label": "WhatsApp",
    "options": "Phone"
   },
   {
-   "collapsible": 1,
-   "depends_on": "eval: !doc.__islocal",
-   "fieldname": "address_section",
-   "fieldtype": "Section Break",
-   "label": "Address"
-  },
-  {
-   "fieldname": "lead_source_details_section",
-   "fieldtype": "Section Break",
-   "label": "Lead Source Details"
-  },
-  {
    "fieldname": "column_break_50",
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "other_information_section",
-   "fieldtype": "Section Break",
-   "label": "Other Information"
-  },
-  {
-   "fieldname": "contact_details_section",
-   "fieldtype": "Section Break",
-   "label": "Contact Details"
-  },
-  {
    "fieldname": "column_break_16",
    "fieldtype": "Column Break"
   },
@@ -465,17 +362,156 @@
    "fieldname": "phone_ext",
    "fieldtype": "Data",
    "label": "Phone Ext."
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "qualification_tab",
+   "fieldtype": "Section Break",
+   "label": "Qualification"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "notes_tab",
+   "fieldtype": "Tab Break",
+   "label": "Notes"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "other_info_tab",
+   "fieldtype": "Section Break",
+   "label": "Additional Information"
+  },
+  {
+   "fieldname": "column_break_1",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "qualified_by",
+   "fieldtype": "Link",
+   "label": "Qualified By",
+   "options": "User"
+  },
+  {
+   "fieldname": "qualified_on",
+   "fieldtype": "Date",
+   "label": "Qualified on"
+  },
+  {
+   "fieldname": "qualification_status",
+   "fieldtype": "Select",
+   "label": "Qualification Status",
+   "options": "Unqualified\nIn Process\nQualified"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "address_section",
+   "fieldtype": "Section Break",
+   "label": "Address & Contacts"
+  },
+  {
+   "fieldname": "column_break_64",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_20",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "job_title",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Job Title"
+  },
+  {
+   "fieldname": "annual_revenue",
+   "fieldtype": "Currency",
+   "label": "Annual Revenue"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "activities_tab",
+   "fieldtype": "Tab Break",
+   "label": "Activities"
+  },
+  {
+   "fieldname": "organization_section",
+   "fieldtype": "Section Break",
+   "label": "Organization"
+  },
+  {
+   "fieldname": "column_break_28",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_31",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "notes_html",
+   "fieldtype": "HTML",
+   "label": "Notes HTML"
+  },
+  {
+   "fieldname": "open_activities_html",
+   "fieldtype": "HTML",
+   "label": "Open Activities HTML"
+  },
+  {
+   "fieldname": "all_activities_section",
+   "fieldtype": "Section Break",
+   "label": "All Activities"
+  },
+  {
+   "fieldname": "all_activities_html",
+   "fieldtype": "HTML",
+   "label": "All Activities HTML"
+  },
+  {
+   "fieldname": "notes",
+   "fieldtype": "Table",
+   "hidden": 1,
+   "label": "Notes",
+   "no_copy": 1,
+   "options": "CRM Note"
+  },
+  {
+   "default": "0",
+   "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": "2021-08-04 00:24:57.208590",
+ "modified": "2022-06-27 21:56:17.392756",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Lead",
  "name_case": "Title Case",
+ "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
   {
@@ -535,6 +571,7 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "subject_field": "title",
  "title_field": "title"
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index c9a64ff..0d12499 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -1,27 +1,19 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
-
 import frappe
 from frappe import _
 from frappe.contacts.address_and_contact import load_address_and_contact
 from frappe.email.inbox import link_communication_to_document
 from frappe.model.mapper import get_mapped_doc
-from frappe.utils import (
-	comma_and,
-	cstr,
-	get_link_to_form,
-	getdate,
-	has_gravatar,
-	nowdate,
-	validate_email_address,
-)
+from frappe.utils import comma_and, get_link_to_form, has_gravatar, validate_email_address
 
 from erpnext.accounts.party import set_taxes
 from erpnext.controllers.selling_controller import SellingController
+from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
 
 
-class Lead(SellingController):
+class Lead(SellingController, CRMNote):
 	def get_feed(self):
 		return "{0}: {1}".format(_(self.status), self.lead_name)
 
@@ -29,6 +21,7 @@
 		customer = frappe.db.get_value("Customer", {"lead_name": self.name})
 		self.get("__onload").is_customer = customer
 		load_address_and_contact(self)
+		self.set_onload("linked_prospects", self.get_linked_prospects())
 
 	def validate(self):
 		self.set_full_name()
@@ -37,79 +30,42 @@
 		self.set_status()
 		self.check_email_id_is_unique()
 		self.validate_email_id()
-		self.validate_contact_date()
-		self.set_prev()
+
+	def before_insert(self):
+		self.contact_doc = None
+		if frappe.db.get_single_value("CRM Settings", "auto_creation_of_contact"):
+			self.contact_doc = self.create_contact()
+
+	def after_insert(self):
+		self.link_to_contact()
+
+	def on_update(self):
+		self.update_prospect()
+
+	def on_trash(self):
+		frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
+
+		self.unlink_dynamic_links()
+		self.remove_link_from_prospect()
 
 	def set_full_name(self):
 		if self.first_name:
-			self.lead_name = " ".join(filter(None, [self.first_name, self.middle_name, self.last_name]))
-
-	def validate_email_id(self):
-		if self.email_id:
-			if not self.flags.ignore_email_validation:
-				validate_email_address(self.email_id, throw=True)
-
-			if self.email_id == self.lead_owner:
-				frappe.throw(_("Lead Owner cannot be same as the Lead"))
-
-			if self.email_id == self.contact_by:
-				frappe.throw(_("Next Contact By cannot be same as the Lead Email Address"))
-
-			if self.is_new() or not self.image:
-				self.image = has_gravatar(self.email_id)
-
-	def validate_contact_date(self):
-		if self.contact_date and getdate(self.contact_date) < getdate(nowdate()):
-			frappe.throw(_("Next Contact Date cannot be in the past"))
-
-		if self.ends_on and self.contact_date and (getdate(self.ends_on) < getdate(self.contact_date)):
-			frappe.throw(_("Ends On date cannot be before Next Contact Date."))
-
-	def on_update(self):
-		self.add_calendar_event()
-		self.update_prospects()
-
-	def set_prev(self):
-		if self.is_new():
-			self._prev = frappe._dict({"contact_date": None, "ends_on": None, "contact_by": None})
-		else:
-			self._prev = frappe.db.get_value(
-				"Lead", self.name, ["contact_date", "ends_on", "contact_by"], as_dict=1
+			self.lead_name = " ".join(
+				filter(None, [self.salutation, self.first_name, self.middle_name, self.last_name])
 			)
 
-	def before_insert(self):
-		self.contact_doc = self.create_contact()
+	def set_lead_name(self):
+		if not self.lead_name:
+			# Check for leads being created through data import
+			if not self.company_name and not self.email_id and not self.flags.ignore_mandatory:
+				frappe.throw(_("A Lead requires either a person's name or an organization's name"))
+			elif self.company_name:
+				self.lead_name = self.company_name
+			else:
+				self.lead_name = self.email_id.split("@")[0]
 
-	def after_insert(self):
-		self.update_links()
-
-	def update_links(self):
-		# update contact links
-		if self.contact_doc:
-			self.contact_doc.append(
-				"links", {"link_doctype": "Lead", "link_name": self.name, "link_title": self.lead_name}
-			)
-			self.contact_doc.save()
-
-	def add_calendar_event(self, opts=None, force=False):
-		if frappe.db.get_single_value("CRM Settings", "create_event_on_next_contact_date"):
-			super(Lead, self).add_calendar_event(
-				{
-					"owner": self.lead_owner,
-					"starts_on": self.contact_date,
-					"ends_on": self.ends_on or "",
-					"subject": ("Contact " + cstr(self.lead_name)),
-					"description": ("Contact " + cstr(self.lead_name))
-					+ (self.contact_by and (". By : " + cstr(self.contact_by)) or ""),
-				},
-				force,
-			)
-
-	def update_prospects(self):
-		prospects = frappe.get_all("Prospect Lead", filters={"lead": self.name}, fields=["parent"])
-		for row in prospects:
-			prospect = frappe.get_doc("Prospect", row.parent)
-			prospect.save(ignore_permissions=True)
+	def set_title(self):
+		self.title = self.company_name or self.lead_name
 
 	def check_email_id_is_unique(self):
 		if self.email_id:
@@ -124,15 +80,47 @@
 
 				if duplicate_leads:
 					frappe.throw(
-						_("Email Address must be unique, already exists for {0}").format(comma_and(duplicate_leads)),
+						_("Email Address must be unique, it is already used in {0}").format(
+							comma_and(duplicate_leads)
+						),
 						frappe.DuplicateEntryError,
 					)
 
-	def on_trash(self):
-		frappe.db.sql("""update `tabIssue` set lead='' where lead=%s""", self.name)
+	def validate_email_id(self):
+		if self.email_id:
+			if not self.flags.ignore_email_validation:
+				validate_email_address(self.email_id, throw=True)
 
-		self.unlink_dynamic_links()
-		self.delete_events()
+			if self.email_id == self.lead_owner:
+				frappe.throw(_("Lead Owner cannot be same as the Lead Email Address"))
+
+			if self.is_new() or not self.image:
+				self.image = has_gravatar(self.email_id)
+
+	def link_to_contact(self):
+		# update contact links
+		if self.contact_doc:
+			self.contact_doc.append(
+				"links", {"link_doctype": "Lead", "link_name": self.name, "link_title": self.lead_name}
+			)
+			self.contact_doc.save()
+
+	def update_prospect(self):
+		lead_row_name = frappe.db.get_value(
+			"Prospect Lead", filters={"lead": self.name}, fieldname="name"
+		)
+		if lead_row_name:
+			lead_row = frappe.get_doc("Prospect Lead", lead_row_name)
+			lead_row.update(
+				{
+					"lead_name": self.lead_name,
+					"email": self.email_id,
+					"mobile_no": self.mobile_no,
+					"lead_owner": self.lead_owner,
+					"status": self.status,
+				}
+			)
+			lead_row.db_update()
 
 	def unlink_dynamic_links(self):
 		links = frappe.get_all(
@@ -155,6 +143,30 @@
 					linked_doc.remove(to_remove)
 					linked_doc.save(ignore_permissions=True)
 
+	def remove_link_from_prospect(self):
+		prospects = self.get_linked_prospects()
+
+		for d in prospects:
+			prospect = frappe.get_doc("Prospect", d.parent)
+			if len(prospect.get("leads")) == 1:
+				prospect.delete(ignore_permissions=True)
+			else:
+				to_remove = None
+				for d in prospect.get("leads"):
+					if d.lead == self.name:
+						to_remove = d
+
+				if to_remove:
+					prospect.remove(to_remove)
+					prospect.save(ignore_permissions=True)
+
+	def get_linked_prospects(self):
+		return frappe.get_all(
+			"Prospect Lead",
+			filters={"lead": self.name},
+			fields=["parent"],
+		)
+
 	def has_customer(self):
 		return frappe.db.get_value("Customer", {"lead_name": self.name})
 
@@ -171,50 +183,78 @@
 			"Quotation", {"party_name": self.name, "docstatus": 1, "status": "Lost"}
 		)
 
-	def set_lead_name(self):
-		if not self.lead_name:
-			# Check for leads being created through data import
-			if not self.company_name and not self.email_id and not self.flags.ignore_mandatory:
-				frappe.throw(_("A Lead requires either a person's name or an organization's name"))
-			elif self.company_name:
-				self.lead_name = self.company_name
-			else:
-				self.lead_name = self.email_id.split("@")[0]
+	@frappe.whitelist()
+	def create_prospect_and_contact(self, data):
+		data = frappe._dict(data)
+		if data.create_contact:
+			self.create_contact()
 
-	def set_title(self):
-		self.title = self.company_name or self.lead_name
+		if data.create_prospect:
+			self.create_prospect(data.prospect_name)
 
 	def create_contact(self):
-		if frappe.db.get_single_value("CRM Settings", "auto_creation_of_contact"):
-			if not self.lead_name:
-				self.set_full_name()
-				self.set_lead_name()
+		if not self.lead_name:
+			self.set_full_name()
+			self.set_lead_name()
 
-			contact = frappe.new_doc("Contact")
-			contact.update(
+		contact = frappe.new_doc("Contact")
+		contact.update(
+			{
+				"first_name": self.first_name or self.lead_name,
+				"last_name": self.last_name,
+				"salutation": self.salutation,
+				"gender": self.gender,
+				"job_title": self.job_title,
+				"company_name": self.company_name,
+			}
+		)
+
+		if self.email_id:
+			contact.append("email_ids", {"email_id": self.email_id, "is_primary": 1})
+
+		if self.phone:
+			contact.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1})
+
+		if self.mobile_no:
+			contact.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1})
+
+		contact.insert(ignore_permissions=True)
+		contact.reload()  # load changes by hooks on contact
+
+		return contact
+
+	def create_prospect(self, company_name):
+		try:
+			prospect = frappe.new_doc("Prospect")
+
+			prospect.company_name = company_name or self.company_name
+			prospect.no_of_employees = self.no_of_employees
+			prospect.industry = self.industry
+			prospect.market_segment = self.market_segment
+			prospect.annual_revenue = self.annual_revenue
+			prospect.territory = self.territory
+			prospect.fax = self.fax
+			prospect.website = self.website
+			prospect.prospect_owner = self.lead_owner
+			prospect.company = self.company
+			prospect.notes = self.notes
+
+			prospect.append(
+				"leads",
 				{
-					"first_name": self.first_name or self.lead_name,
-					"last_name": self.last_name,
-					"salutation": self.salutation,
-					"gender": self.gender,
-					"designation": self.designation,
-					"company_name": self.company_name,
-				}
+					"lead": self.name,
+					"lead_name": self.lead_name,
+					"email": self.email_id,
+					"mobile_no": self.mobile_no,
+					"lead_owner": self.lead_owner,
+					"status": self.status,
+				},
 			)
-
-			if self.email_id:
-				contact.append("email_ids", {"email_id": self.email_id, "is_primary": 1})
-
-			if self.phone:
-				contact.append("phone_nos", {"phone": self.phone, "is_primary_phone": 1})
-
-			if self.mobile_no:
-				contact.append("phone_nos", {"phone": self.mobile_no, "is_primary_mobile_no": 1})
-
-			contact.insert(ignore_permissions=True)
-			contact.reload()  # load changes by hooks on contact
-
-			return contact
+			prospect.flags.ignore_permissions = True
+			prospect.flags.ignore_mandatory = True
+			prospect.save()
+		except frappe.DuplicateEntryError:
+			frappe.throw(_("Prospect {0} already exists").format(company_name or self.company_name))
 
 
 @frappe.whitelist()
@@ -274,6 +314,8 @@
 					"company_name": "customer_name",
 					"email_id": "contact_email",
 					"mobile_no": "contact_mobile",
+					"lead_owner": "opportunity_owner",
+					"notes": "notes",
 				},
 			}
 		},
@@ -422,21 +464,25 @@
 	return lead
 
 
-def daily_open_lead():
-	leads = frappe.get_all("Lead", filters=[["contact_date", "Between", [nowdate(), nowdate()]]])
-	for lead in leads:
-		frappe.db.set_value("Lead", lead.name, "status", "Open")
-
-
 @frappe.whitelist()
 def add_lead_to_prospect(lead, prospect):
 	prospect = frappe.get_doc("Prospect", prospect)
-	prospect.append("prospect_lead", {"lead": lead})
+	prospect.append("leads", {"lead": lead})
 	prospect.save(ignore_permissions=True)
+
+	carry_forward_communication_and_comments = frappe.db.get_single_value(
+		"CRM Settings", "carry_forward_communication_and_comments"
+	)
+
+	if carry_forward_communication_and_comments:
+		copy_comments("Lead", lead, prospect)
+		link_communications("Lead", lead, prospect)
+	link_open_events("Lead", lead, prospect)
+
 	frappe.msgprint(
 		_("Lead {0} has been added to prospect {1}.").format(
 			frappe.bold(lead), frappe.bold(prospect.name)
 		),
-		title=_("Lead Added"),
+		title=_("Lead -> Prospect"),
 		indicator="green",
 	)
diff --git a/erpnext/crm/doctype/lead/lead_list.js b/erpnext/crm/doctype/lead/lead_list.js
index 75208fa..dbeaf60 100644
--- a/erpnext/crm/doctype/lead/lead_list.js
+++ b/erpnext/crm/doctype/lead/lead_list.js
@@ -16,7 +16,7 @@
 						prospect.prospect_owner = r.lead_owner;
 
 						leads.forEach(function(lead) {
-							let lead_prospect_row = frappe.model.add_child(prospect, 'prospect_lead');
+							let lead_prospect_row = frappe.model.add_child(prospect, 'leads');
 							lead_prospect_row.lead = lead.name;
 						});
 						frappe.set_route("Form", "Prospect", prospect.name);
diff --git a/erpnext/crm/doctype/lead/test_lead.py b/erpnext/crm/doctype/lead/test_lead.py
index 166ae2c..8fe688d 100644
--- a/erpnext/crm/doctype/lead/test_lead.py
+++ b/erpnext/crm/doctype/lead/test_lead.py
@@ -5,7 +5,10 @@
 import unittest
 
 import frappe
-from frappe.utils import random_string
+from frappe.utils import random_string, today
+
+from erpnext.crm.doctype.lead.lead import make_opportunity
+from erpnext.crm.utils import get_linked_prospect
 
 test_records = frappe.get_test_records("Lead")
 
@@ -83,6 +86,105 @@
 		self.assertEqual(frappe.db.exists("Lead", lead_doc.name), None)
 		self.assertEqual(len(address_1.get("links")), 1)
 
+	def test_prospect_creation_from_lead(self):
+		frappe.db.sql("delete from `tabLead` where lead_name='Rahul Tripathi'")
+		frappe.db.sql("delete from `tabProspect` where name='Prospect Company'")
+
+		lead = make_lead(
+			first_name="Rahul",
+			last_name="Tripathi",
+			email_id="rahul@gmail.com",
+			company_name="Prospect Company",
+		)
+
+		event = create_event("Meeting 1", today(), "Lead", lead.name)
+
+		lead.create_prospect(lead.company_name)
+
+		prospect = get_linked_prospect("Lead", lead.name)
+		self.assertEqual(prospect, "Prospect Company")
+
+		event.reload()
+		self.assertEqual(event.event_participants[1].reference_doctype, "Prospect")
+		self.assertEqual(event.event_participants[1].reference_docname, prospect)
+
+	def test_opportunity_from_lead(self):
+		frappe.db.sql("delete from `tabLead` where lead_name='Rahul Tripathi'")
+		frappe.db.sql("delete from `tabOpportunity` where party_name='Rahul Tripathi'")
+
+		lead = make_lead(
+			first_name="Rahul",
+			last_name="Tripathi",
+			email_id="rahul@gmail.com",
+			company_name="Prospect Company",
+		)
+
+		lead.add_note("test note")
+		event = create_event("Meeting 1", today(), "Lead", lead.name)
+		create_todo("followup", "Lead", lead.name)
+
+		opportunity = make_opportunity(lead.name)
+		opportunity.save()
+
+		self.assertEqual(opportunity.get("party_name"), lead.name)
+		self.assertEqual(opportunity.notes[0].note, "test note")
+
+		event.reload()
+		self.assertEqual(event.event_participants[1].reference_doctype, "Opportunity")
+		self.assertEqual(event.event_participants[1].reference_docname, opportunity.name)
+
+		self.assertTrue(
+			frappe.db.get_value(
+				"ToDo", {"reference_type": "Opportunity", "reference_name": opportunity.name}
+			)
+		)
+
+	def test_copy_events_from_lead_to_prospect(self):
+		frappe.db.sql("delete from `tabLead` where lead_name='Rahul Tripathi'")
+		frappe.db.sql("delete from `tabProspect` where name='Prospect Company'")
+
+		lead = make_lead(
+			first_name="Rahul",
+			last_name="Tripathi",
+			email_id="rahul@gmail.com",
+			company_name="Prospect Company",
+		)
+
+		lead.create_prospect(lead.company_name)
+		prospect = get_linked_prospect("Lead", lead.name)
+
+		event = create_event("Meeting", today(), "Lead", lead.name)
+
+		self.assertEqual(len(event.event_participants), 2)
+		self.assertEqual(event.event_participants[1].reference_doctype, "Prospect")
+		self.assertEqual(event.event_participants[1].reference_docname, prospect)
+
+
+def create_event(subject, starts_on, reference_type, reference_name):
+	event = frappe.new_doc("Event")
+	event.subject = subject
+	event.starts_on = starts_on
+	event.event_type = "Private"
+	event.all_day = 1
+	event.owner = "Administrator"
+	event.append(
+		"event_participants", {"reference_doctype": reference_type, "reference_docname": reference_name}
+	)
+	event.reference_type = reference_type
+	event.reference_name = reference_name
+	event.insert()
+	return event
+
+
+def create_todo(description, reference_type, reference_name):
+	todo = frappe.new_doc("ToDo")
+	todo.description = description
+	todo.owner = "Administrator"
+	todo.reference_type = reference_type
+	todo.reference_name = reference_name
+	todo.insert()
+	return todo
+
 
 def make_lead(**args):
 	args = frappe._dict(args)
@@ -93,6 +195,7 @@
 			"first_name": args.first_name or "_Test",
 			"last_name": args.last_name or "Lead",
 			"email_id": args.email_id or "new_lead_{}@example.com".format(random_string(5)),
+			"company_name": args.company_name or "_Test Company",
 		}
 	).insert()
 
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 8e7d67e..c53ea9d 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -32,13 +32,6 @@
 		}
 	},
 
-	contact_date: function(frm) {
-		if(frm.doc.contact_date < frappe.datetime.now_datetime()){
-			frm.set_value("contact_date", "");
-			frappe.throw(__("Next follow up date should be greater than now."))
-		}
-	},
-
 	onload_post_render: function(frm) {
 		frm.get_field("items").grid.set_multiple_add("item_code", "qty");
 	},
@@ -130,6 +123,13 @@
 				});
 			}
 		}
+
+		if (!frm.is_new()) {
+			frappe.contacts.render_address_and_contact(frm);
+			// frm.trigger('render_contact_day_html');
+		} else {
+			frappe.contacts.clear_address_and_contact(frm);
+		}
 	},
 
 	set_contact_link: function(frm) {
@@ -227,8 +227,7 @@
 			'total': flt(total),
 			'base_total': flt(base_total)
 		});
-	}
-
+	},
 });
 frappe.ui.form.on("Opportunity Item", {
 	calculate: function(frm, cdt, cdn) {
@@ -264,13 +263,14 @@
 		this.frm.trigger('currency');
 	}
 
+	refresh() {
+		this.show_notes();
+		this.show_activities();
+	}
+
 	setup_queries() {
 		var me = this;
 
-		if(this.frm.fields_dict.contact_by.df.options.match(/^User/)) {
-			this.frm.set_query("contact_by", erpnext.queries.user);
-		}
-
 		me.frm.set_query('customer_address', erpnext.queries.address_query);
 
 		this.frm.set_query("item_code", "items", function() {
@@ -287,6 +287,14 @@
 		}
 		else if (me.frm.doc.opportunity_from == "Customer") {
 			me.frm.set_query('party_name', erpnext.queries['customer']);
+		} else if (me.frm.doc.opportunity_from == "Prospect") {
+			me.frm.set_query('party_name', function() {
+				return {
+					filters: {
+						"company": me.frm.doc.company
+					}
+				};
+			});
 		}
 	}
 
@@ -303,6 +311,24 @@
 			frm: cur_frm
 		})
 	}
+
+	show_notes() {
+		const crm_notes = new erpnext.utils.CRMNotes({
+			frm: this.frm,
+			notes_wrapper: $(this.frm.fields_dict.notes_html.wrapper),
+		});
+		crm_notes.refresh();
+	}
+
+	show_activities() {
+		const crm_activities = new erpnext.utils.CRMActivities({
+			frm: this.frm,
+			open_activities_wrapper: $(this.frm.fields_dict.open_activities_html.wrapper),
+			all_activities_wrapper: $(this.frm.fields_dict.all_activities_html.wrapper),
+			form_wrapper: $(this.frm.wrapper),
+		});
+		crm_activities.refresh();
+	}
 };
 
 extend_cscript(cur_frm.cscript, new erpnext.crm.Opportunity({frm: cur_frm}));
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 089f2d2..1a6f23b 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -1,5 +1,6 @@
 {
  "actions": [],
+ "allow_events_in_timeline": 1,
  "allow_import": 1,
  "allow_rename": 1,
  "autoname": "naming_series:",
@@ -11,69 +12,88 @@
  "email_append_to": 1,
  "engine": "InnoDB",
  "field_order": [
-  "from_section",
   "naming_series",
   "opportunity_from",
   "party_name",
   "customer_name",
-  "source",
-  "column_break0",
-  "title",
-  "opportunity_type",
   "status",
-  "converted_by",
+  "column_break0",
+  "opportunity_type",
+  "source",
+  "opportunity_owner",
+  "column_break_10",
   "sales_stage",
-  "first_response_time",
   "expected_closing",
-  "next_contact",
-  "contact_by",
-  "contact_date",
-  "column_break2",
-  "to_discuss",
+  "probability",
+  "organization_details_section",
+  "no_of_employees",
+  "annual_revenue",
+  "customer_group",
+  "column_break_23",
+  "industry",
+  "market_segment",
+  "website",
+  "column_break_31",
+  "city",
+  "state",
+  "country",
+  "territory",
   "section_break_14",
   "currency",
+  "column_break_36",
   "conversion_rate",
-  "base_opportunity_amount",
-  "with_items",
   "column_break_17",
-  "probability",
   "opportunity_amount",
+  "base_opportunity_amount",
+  "more_info",
+  "company",
+  "campaign",
+  "transaction_date",
+  "column_break1",
+  "language",
+  "amended_from",
+  "title",
+  "first_response_time",
+  "lost_detail_section",
+  "lost_reasons",
+  "order_lost_reason",
+  "column_break_56",
+  "competitors",
+  "contact_info",
+  "primary_contact_section",
+  "contact_person",
+  "job_title",
+  "column_break_54",
+  "contact_email",
+  "contact_mobile",
+  "column_break_22",
+  "whatsapp",
+  "phone",
+  "phone_ext",
+  "address_contact_section",
+  "address_html",
+  "customer_address",
+  "address_display",
+  "column_break3",
+  "contact_html",
+  "contact_display",
   "items_section",
   "items",
   "section_break_32",
   "base_total",
   "column_break_33",
   "total",
-  "contact_info",
-  "customer_address",
-  "address_display",
-  "territory",
-  "customer_group",
-  "column_break3",
-  "contact_person",
-  "contact_display",
-  "contact_email",
-  "contact_mobile",
-  "more_info",
-  "company",
-  "campaign",
-  "column_break1",
-  "transaction_date",
-  "language",
-  "amended_from",
-  "lost_detail_section",
-  "lost_reasons",
-  "order_lost_reason",
-  "column_break_56",
-  "competitors"
+  "activities_tab",
+  "open_activities_html",
+  "all_activities_section",
+  "all_activities_html",
+  "notes_tab",
+  "notes_html",
+  "notes",
+  "dashboard_tab"
  ],
  "fields": [
   {
-   "fieldname": "from_section",
-   "fieldtype": "Section Break",
-   "options": "fa fa-user"
-  },
-  {
    "fieldname": "naming_series",
    "fieldtype": "Select",
    "in_list_view": 1,
@@ -113,8 +133,9 @@
    "bold": 1,
    "fieldname": "customer_name",
    "fieldtype": "Data",
+   "hidden": 1,
    "in_global_search": 1,
-   "label": "Customer / Lead Name",
+   "label": "Customer Name",
    "read_only": 1
   },
   {
@@ -167,47 +188,9 @@
    "label": "Expected Closing Date"
   },
   {
-   "collapsible": 1,
-   "collapsible_depends_on": "contact_by",
-   "fieldname": "next_contact",
-   "fieldtype": "Section Break",
-   "label": "Follow Up"
-  },
-  {
-   "fieldname": "contact_by",
-   "fieldtype": "Link",
-   "in_standard_filter": 1,
-   "label": "Next Contact By",
-   "oldfieldname": "contact_by",
-   "oldfieldtype": "Link",
-   "options": "User",
-   "width": "75px"
-  },
-  {
-   "fieldname": "contact_date",
-   "fieldtype": "Datetime",
-   "label": "Next Contact Date",
-   "oldfieldname": "contact_date",
-   "oldfieldtype": "Date"
-  },
-  {
-   "fieldname": "column_break2",
-   "fieldtype": "Column Break",
-   "oldfieldtype": "Column Break",
-   "width": "50%"
-  },
-  {
-   "fieldname": "to_discuss",
-   "fieldtype": "Small Text",
-   "label": "To Discuss",
-   "no_copy": 1,
-   "oldfieldname": "to_discuss",
-   "oldfieldtype": "Small Text"
-  },
-  {
    "fieldname": "section_break_14",
    "fieldtype": "Section Break",
-   "label": "Sales"
+   "label": "Opportunity Value"
   },
   {
    "fieldname": "currency",
@@ -222,12 +205,6 @@
    "options": "currency"
   },
   {
-   "default": "0",
-   "fieldname": "with_items",
-   "fieldtype": "Check",
-   "label": "With Items"
-  },
-  {
    "fieldname": "column_break_17",
    "fieldtype": "Column Break"
   },
@@ -245,9 +222,8 @@
    "label": "Probability (%)"
   },
   {
-   "depends_on": "with_items",
    "fieldname": "items_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Items",
    "oldfieldtype": "Section Break",
    "options": "fa fa-shopping-cart"
@@ -262,18 +238,16 @@
    "options": "Opportunity Item"
   },
   {
-   "collapsible": 1,
-   "collapsible_depends_on": "next_contact_by",
-   "depends_on": "eval:doc.party_name",
    "fieldname": "contact_info",
-   "fieldtype": "Section Break",
-   "label": "Contact Info",
+   "fieldtype": "Tab Break",
+   "label": "Contacts",
    "options": "fa fa-bullhorn"
   },
   {
    "depends_on": "eval:doc.party_name",
    "fieldname": "customer_address",
    "fieldtype": "Link",
+   "hidden": 1,
    "label": "Customer / Lead Address",
    "options": "Address",
    "print_hide": 1
@@ -327,19 +301,16 @@
    "read_only": 1
   },
   {
-   "depends_on": "eval:doc.party_name",
    "fieldname": "contact_email",
    "fieldtype": "Data",
    "label": "Contact Email",
-   "options": "Email",
-   "read_only": 1
+   "options": "Email"
   },
   {
-   "depends_on": "eval:doc.party_name",
    "fieldname": "contact_mobile",
-   "fieldtype": "Small Text",
-   "label": "Contact Mobile No",
-   "read_only": 1
+   "fieldtype": "Data",
+   "label": "Contact Mobile",
+   "options": "Phone"
   },
   {
    "collapsible": 1,
@@ -417,12 +388,6 @@
    "read_only": 1
   },
   {
-   "fieldname": "converted_by",
-   "fieldtype": "Link",
-   "label": "Converted By",
-   "options": "User"
-  },
-  {
    "bold": 1,
    "fieldname": "first_response_time",
    "fieldtype": "Duration",
@@ -474,6 +439,7 @@
    "fieldtype": "Column Break"
   },
   {
+   "depends_on": "eval:doc.status===\"Lost\"",
    "fieldname": "lost_detail_section",
    "fieldtype": "Section Break",
    "label": "Lost Reasons"
@@ -488,12 +454,179 @@
    "label": "Competitors",
    "options": "Competitor Detail",
    "read_only": 1
+  },
+  {
+   "fieldname": "column_break_10",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "organization_details_section",
+   "fieldtype": "Section Break",
+   "label": "Organization"
+  },
+  {
+   "fieldname": "no_of_employees",
+   "fieldtype": "Int",
+   "label": "No of Employees"
+  },
+  {
+   "fieldname": "annual_revenue",
+   "fieldtype": "Currency",
+   "label": "Annual Revenue"
+  },
+  {
+   "fieldname": "industry",
+   "fieldtype": "Link",
+   "label": "Industry",
+   "options": "Industry Type"
+  },
+  {
+   "fieldname": "market_segment",
+   "fieldtype": "Link",
+   "label": "Market Segment",
+   "options": "Market Segment"
+  },
+  {
+   "fieldname": "column_break_23",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "address_contact_section",
+   "fieldtype": "Section Break",
+   "label": "Address & Contact"
+  },
+  {
+   "fieldname": "column_break_36",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "opportunity_owner",
+   "fieldtype": "Link",
+   "label": "Opportunity Owner",
+   "options": "User"
+  },
+  {
+   "fieldname": "website",
+   "fieldtype": "Data",
+   "label": "Website"
+  },
+  {
+   "fieldname": "column_break_22",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "whatsapp",
+   "fieldtype": "Data",
+   "label": "WhatsApp",
+   "options": "Phone"
+  },
+  {
+   "fieldname": "phone",
+   "fieldtype": "Data",
+   "label": "Phone",
+   "options": "Phone"
+  },
+  {
+   "fieldname": "phone_ext",
+   "fieldtype": "Data",
+   "label": "Phone Ext."
+  },
+  {
+   "fieldname": "column_break_31",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "primary_contact_section",
+   "fieldtype": "Section Break",
+   "label": "Primary Contact"
+  },
+  {
+   "fieldname": "column_break_54",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "dashboard_tab",
+   "fieldtype": "Tab Break",
+   "label": "Dashboard",
+   "show_dashboard": 1
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "notes_tab",
+   "fieldtype": "Tab Break",
+   "label": "Notes"
+  },
+  {
+   "fieldname": "notes_html",
+   "fieldtype": "HTML",
+   "label": "Notes HTML"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "activities_tab",
+   "fieldtype": "Tab Break",
+   "label": "Activities"
+  },
+  {
+   "fieldname": "job_title",
+   "fieldtype": "Data",
+   "label": "Job Title"
+  },
+  {
+   "fieldname": "address_html",
+   "fieldtype": "HTML",
+   "label": "Address HTML"
+  },
+  {
+   "fieldname": "contact_html",
+   "fieldtype": "HTML",
+   "label": "Contact HTML"
+  },
+  {
+   "fieldname": "open_activities_html",
+   "fieldtype": "HTML",
+   "label": "Open Activities HTML"
+  },
+  {
+   "fieldname": "all_activities_section",
+   "fieldtype": "Section Break",
+   "label": "All Activities"
+  },
+  {
+   "fieldname": "all_activities_html",
+   "fieldtype": "HTML",
+   "label": "All Activities HTML"
+  },
+  {
+   "fieldname": "notes",
+   "fieldtype": "Table",
+   "hidden": 1,
+   "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-01-29 19:32:26.382896",
+ "modified": "2022-06-27 18:44:32.858696",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Opportunity",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index c70a4f6..08eb472 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -6,53 +6,54 @@
 
 import frappe
 from frappe import _
+from frappe.contacts.address_and_contact import load_address_and_contact
 from frappe.email.inbox import link_communication_to_document
 from frappe.model.mapper import get_mapped_doc
 from frappe.query_builder import DocType, Interval
 from frappe.query_builder.functions import Now
-from frappe.utils import cint, flt, get_fullname
+from frappe.utils import flt, get_fullname
 
-from erpnext.crm.utils import add_link_in_communication, copy_comments
+from erpnext.crm.utils import (
+	CRMNote,
+	copy_comments,
+	link_communications,
+	link_open_events,
+	link_open_tasks,
+)
 from erpnext.setup.utils import get_exchange_rate
 from erpnext.utilities.transaction_base import TransactionBase
 
 
-class Opportunity(TransactionBase):
+class Opportunity(TransactionBase, CRMNote):
+	def onload(self):
+		ref_doc = frappe.get_doc(self.opportunity_from, self.party_name)
+		load_address_and_contact(ref_doc)
+		self.set("__onload", ref_doc.get("__onload"))
+
 	def after_insert(self):
 		if self.opportunity_from == "Lead":
 			frappe.get_doc("Lead", self.party_name).set_status(update=True)
+			self.disable_lead()
 
-		if self.opportunity_from in ["Lead", "Prospect"]:
+			link_open_tasks(self.opportunity_from, self.party_name, self)
+			link_open_events(self.opportunity_from, self.party_name, self)
 			if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"):
 				copy_comments(self.opportunity_from, self.party_name, self)
-				add_link_in_communication(self.opportunity_from, self.party_name, self)
+				link_communications(self.opportunity_from, self.party_name, self)
 
 	def validate(self):
-		self._prev = frappe._dict(
-			{
-				"contact_date": frappe.db.get_value("Opportunity", self.name, "contact_date")
-				if (not cint(self.get("__islocal")))
-				else None,
-				"contact_by": frappe.db.get_value("Opportunity", self.name, "contact_by")
-				if (not cint(self.get("__islocal")))
-				else None,
-			}
-		)
-
 		self.make_new_lead_if_required()
 		self.validate_item_details()
 		self.validate_uom_is_integer("uom", "qty")
 		self.validate_cust_name()
 		self.map_fields()
+		self.set_exchange_rate()
 
 		if not self.title:
 			self.title = self.customer_name
 
-		if not self.with_items:
-			self.items = []
-
-		else:
-			self.calculate_totals()
+		self.calculate_totals()
+		self.update_prospect()
 
 	def map_fields(self):
 		for field in self.meta.get_valid_columns():
@@ -63,18 +64,65 @@
 				except Exception:
 					continue
 
+	def set_exchange_rate(self):
+		company_currency = frappe.get_cached_value("Company", self.company, "default_currency")
+		if self.currency == company_currency:
+			self.conversion_rate = 1.0
+			return
+
+		if not self.conversion_rate or self.conversion_rate == 1.0:
+			self.conversion_rate = get_exchange_rate(self.currency, company_currency, self.transaction_date)
+
 	def calculate_totals(self):
 		total = base_total = 0
 		for item in self.get("items"):
 			item.amount = flt(item.rate) * flt(item.qty)
-			item.base_rate = flt(self.conversion_rate * item.rate)
-			item.base_amount = flt(self.conversion_rate * item.amount)
+			item.base_rate = flt(self.conversion_rate) * flt(item.rate)
+			item.base_amount = flt(self.conversion_rate) * flt(item.amount)
 			total += item.amount
 			base_total += item.base_amount
 
 		self.total = flt(total)
 		self.base_total = flt(base_total)
 
+	def update_prospect(self):
+		prospect_name = None
+		if self.opportunity_from == "Prospect" and self.party_name:
+			prospect_name = self.party_name
+		elif self.opportunity_from == "Lead":
+			prospect_name = frappe.db.get_value("Prospect Lead", {"lead": self.party_name}, "parent")
+
+		if prospect_name:
+			prospect = frappe.get_doc("Prospect", prospect_name)
+
+			opportunity_values = {
+				"opportunity": self.name,
+				"amount": self.opportunity_amount,
+				"stage": self.sales_stage,
+				"deal_owner": self.opportunity_owner,
+				"probability": self.probability,
+				"expected_closing": self.expected_closing,
+				"currency": self.currency,
+				"contact_person": self.contact_person,
+			}
+
+			opportunity_already_added = False
+			for d in prospect.get("opportunities", []):
+				if d.opportunity == self.name:
+					opportunity_already_added = True
+					d.update(opportunity_values)
+					d.db_update()
+
+			if not opportunity_already_added:
+				prospect.append("opportunities", opportunity_values)
+				prospect.flags.ignore_permissions = True
+				prospect.flags.ignore_mandatory = True
+				prospect.save()
+
+	def disable_lead(self):
+		if self.opportunity_from == "Lead":
+			frappe.db.set_value("Lead", self.party_name, {"disabled": 1, "docstatus": 1})
+
 	def make_new_lead_if_required(self):
 		"""Set lead against new opportunity"""
 		if (not self.get("party_name")) and self.contact_email:
@@ -144,11 +192,8 @@
 		else:
 			frappe.throw(_("Cannot declare as lost, because Quotation has been made."))
 
-	def on_trash(self):
-		self.delete_events()
-
 	def has_active_quotation(self):
-		if not self.with_items:
+		if not self.get("items", []):
 			return frappe.get_all(
 				"Quotation",
 				{"opportunity": self.name, "status": ("not in", ["Lost", "Closed"]), "docstatus": 1},
@@ -165,7 +210,7 @@
 			)
 
 	def has_ordered_quotation(self):
-		if not self.with_items:
+		if not self.get("items", []):
 			return frappe.get_all(
 				"Quotation", {"opportunity": self.name, "status": "Ordered", "docstatus": 1}, "name"
 			)
@@ -195,43 +240,20 @@
 			return True
 
 	def validate_cust_name(self):
-		if self.party_name and self.opportunity_from == "Customer":
-			self.customer_name = frappe.db.get_value("Customer", self.party_name, "customer_name")
-		elif self.party_name and self.opportunity_from == "Lead":
-			lead_name, company_name = frappe.db.get_value(
-				"Lead", self.party_name, ["lead_name", "company_name"]
-			)
-			self.customer_name = company_name or lead_name
+		if self.party_name:
+			if self.opportunity_from == "Customer":
+				self.customer_name = frappe.db.get_value("Customer", self.party_name, "customer_name")
+			elif self.opportunity_from == "Lead":
+				customer_name = frappe.db.get_value("Prospect Lead", {"lead": self.party_name}, "parent")
+				if not customer_name:
+					lead_name, company_name = frappe.db.get_value(
+						"Lead", self.party_name, ["lead_name", "company_name"]
+					)
+					customer_name = company_name or lead_name
 
-	def on_update(self):
-		self.add_calendar_event()
-
-	def add_calendar_event(self, opts=None, force=False):
-		if frappe.db.get_single_value("CRM Settings", "create_event_on_next_contact_date_opportunity"):
-			if not opts:
-				opts = frappe._dict()
-
-			opts.description = ""
-			opts.contact_date = self.contact_date
-
-			if self.party_name and self.opportunity_from == "Customer":
-				if self.contact_person:
-					opts.description = f"Contact {self.contact_person}"
-				else:
-					opts.description = f"Contact customer {self.party_name}"
-			elif self.party_name and self.opportunity_from == "Lead":
-				if self.contact_display:
-					opts.description = f"Contact {self.contact_display}"
-				else:
-					opts.description = f"Contact lead {self.party_name}"
-
-			opts.subject = opts.description
-			opts.description += f". By : {self.contact_by}"
-
-			if self.to_discuss:
-				opts.description += f" To Discuss : {frappe.render_template(self.to_discuss, {'doc': self})}"
-
-			super(Opportunity, self).add_calendar_event(opts, force)
+				self.customer_name = customer_name
+			elif self.opportunity_from == "Prospect":
+				self.customer_name = self.party_name
 
 	def validate_item_details(self):
 		if not self.get("items"):
@@ -295,7 +317,7 @@
 
 		quotation.run_method("set_missing_values")
 		quotation.run_method("calculate_taxes_and_totals")
-		if not source.with_items:
+		if not source.get("items", []):
 			quotation.opportunity = source.name
 
 	doclist = get_mapped_doc(
@@ -440,34 +462,3 @@
 	link_communication_to_document(doc, "Opportunity", opportunity.name, ignore_communication_links)
 
 	return opportunity.name
-
-
-@frappe.whitelist()
-def get_events(start, end, filters=None):
-	"""Returns events for Gantt / Calendar view rendering.
-	:param start: Start date-time.
-	:param end: End date-time.
-	:param filters: Filters (JSON).
-	"""
-	from frappe.desk.calendar import get_event_conditions
-
-	conditions = get_event_conditions("Opportunity", filters)
-
-	data = frappe.db.sql(
-		"""
-		select
-			distinct `tabOpportunity`.name, `tabOpportunity`.customer_name, `tabOpportunity`.opportunity_amount,
-			`tabOpportunity`.title, `tabOpportunity`.contact_date
-		from
-			`tabOpportunity`
-		where
-			(`tabOpportunity`.contact_date between %(start)s and %(end)s)
-			{conditions}
-		""".format(
-			conditions=conditions
-		),
-		{"start": start, "end": end},
-		as_dict=True,
-		update={"allDay": 0},
-	)
-	return data
diff --git a/erpnext/crm/doctype/opportunity/opportunity_calendar.js b/erpnext/crm/doctype/opportunity/opportunity_calendar.js
deleted file mode 100644
index 58fa2b8..0000000
--- a/erpnext/crm/doctype/opportunity/opportunity_calendar.js
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
-// License: GNU General Public License v3. See license.txt
-frappe.views.calendar["Opportunity"] = {
-	field_map: {
-		"start": "contact_date",
-		"end": "contact_date",
-		"id": "name",
-		"title": "customer_name",
-		"allDay": "allDay"
-    },
-	options: {
-		header: {
-			left: 'prev,next today',
-			center: 'title',
-			right: 'month'
-		}
-    },
-    get_events_method: 'erpnext.crm.doctype.opportunity.opportunity.get_events'
-}
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 4a18e94..1ff3267 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -77,42 +77,6 @@
 		create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email)
 		create_communication(opp_doc.doctype, opp_doc.name, opp_doc.contact_email)
 
-		quotation_doc = make_quotation(opp_doc.name)
-		quotation_doc.append("items", {"item_code": "_Test Item", "qty": 1})
-		quotation_doc.run_method("set_missing_values")
-		quotation_doc.run_method("calculate_taxes_and_totals")
-		quotation_doc.save()
-
-		quotation_comment_count = frappe.db.count(
-			"Comment",
-			{
-				"reference_doctype": quotation_doc.doctype,
-				"reference_name": quotation_doc.name,
-				"comment_type": "Comment",
-			},
-		)
-		quotation_communication_count = len(
-			get_linked_communication_list(quotation_doc.doctype, quotation_doc.name)
-		)
-		self.assertEqual(quotation_comment_count, 4)
-		self.assertEqual(quotation_communication_count, 4)
-
-	def test_render_template_for_to_discuss(self):
-		doc = make_opportunity(with_items=0, opportunity_from="Lead")
-		doc.contact_by = "test@example.com"
-		doc.contact_date = add_days(today(), days=2)
-		doc.to_discuss = "{{ doc.name }} test data"
-		doc.save()
-
-		event = frappe.get_all(
-			"Event Participants",
-			fields=["parent"],
-			filters={"reference_doctype": doc.doctype, "reference_docname": doc.name},
-		)
-
-		event_description = frappe.db.get_value("Event", event[0].parent, "description")
-		self.assertTrue(doc.name in event_description)
-
 
 def make_opportunity_from_lead():
 	new_lead_email_id = "new{}@example.com".format(random_string(5))
@@ -139,7 +103,6 @@
 			"opportunity_from": args.opportunity_from or "Customer",
 			"opportunity_type": "Sales",
 			"conversion_rate": 1.0,
-			"with_items": args.with_items or 0,
 			"transaction_date": today(),
 		}
 	)
diff --git a/erpnext/crm/doctype/opportunity/test_records.json b/erpnext/crm/doctype/opportunity/test_records.json
index a1e0ad9..f7e8350 100644
--- a/erpnext/crm/doctype/opportunity/test_records.json
+++ b/erpnext/crm/doctype/opportunity/test_records.json
@@ -8,7 +8,9 @@
 		"transaction_date": "2013-12-12",
 		"items": [{
 			"item_name": "Test Item",
-			"description": "Some description"
+			"description": "Some description",
+			"qty": 5,
+			"rate": 100
 		}]
 	}
 ]
diff --git a/erpnext/crm/doctype/prospect/prospect.js b/erpnext/crm/doctype/prospect/prospect.js
index 8721a5b..495ed29 100644
--- a/erpnext/crm/doctype/prospect/prospect.js
+++ b/erpnext/crm/doctype/prospect/prospect.js
@@ -27,5 +27,26 @@
 		} else {
 			frappe.contacts.clear_address_and_contact(frm);
 		}
+		frm.trigger("show_notes");
+		frm.trigger("show_activities");
+	},
+
+	show_notes (frm) {
+		const crm_notes = new erpnext.utils.CRMNotes({
+			frm: frm,
+			notes_wrapper: $(frm.fields_dict.notes_html.wrapper),
+		});
+		crm_notes.refresh();
+	},
+
+	show_activities (frm) {
+		const crm_activities = new erpnext.utils.CRMActivities({
+			frm: frm,
+			open_activities_wrapper: $(frm.fields_dict.open_activities_html.wrapper),
+			all_activities_wrapper: $(frm.fields_dict.all_activities_html.wrapper),
+			form_wrapper: $(frm.wrapper),
+		});
+		crm_activities.refresh();
 	}
+
 });
diff --git a/erpnext/crm/doctype/prospect/prospect.json b/erpnext/crm/doctype/prospect/prospect.json
index c9554ba..afc6c1d 100644
--- a/erpnext/crm/doctype/prospect/prospect.json
+++ b/erpnext/crm/doctype/prospect/prospect.json
@@ -1,33 +1,42 @@
 {
  "actions": [],
+ "allow_events_in_timeline": 1,
  "autoname": "field:company_name",
  "creation": "2021-08-19 00:21:06.995448",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "overview_tab",
   "company_name",
-  "industry",
-  "market_segment",
   "customer_group",
+  "no_of_employees",
+  "annual_revenue",
+  "column_break_4",
+  "market_segment",
+  "industry",
   "territory",
   "column_break_6",
-  "no_of_employees",
-  "currency",
-  "annual_revenue",
-  "more_details_section",
-  "fax",
-  "website",
-  "column_break_13",
   "prospect_owner",
+  "website",
+  "fax",
   "company",
-  "leads_section",
-  "prospect_lead",
   "address_and_contact_section",
+  "column_break_16",
+  "contacts_tab",
   "address_html",
-  "column_break_17",
+  "column_break_18",
   "contact_html",
+  "leads_section",
+  "leads",
+  "opportunities_tab",
+  "opportunities",
+  "activities_tab",
+  "open_activities_html",
+  "all_activities_section",
+  "all_activities_html",
   "notes_section",
+  "notes_html",
   "notes"
  ],
  "fields": [
@@ -71,15 +80,9 @@
   },
   {
    "fieldname": "no_of_employees",
-   "fieldtype": "Int",
-   "label": "No. of Employees"
-  },
-  {
-   "fieldname": "currency",
-   "fieldtype": "Link",
-   "in_list_view": 1,
-   "label": "Currency",
-   "options": "Currency"
+   "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+"
   },
   {
    "fieldname": "annual_revenue",
@@ -97,8 +100,7 @@
   {
    "fieldname": "website",
    "fieldtype": "Data",
-   "label": "Website",
-   "options": "URL"
+   "label": "Website"
   },
   {
    "fieldname": "prospect_owner",
@@ -108,52 +110,31 @@
   },
   {
    "fieldname": "leads_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Leads"
   },
   {
-   "fieldname": "prospect_lead",
-   "fieldtype": "Table",
-   "options": "Prospect Lead"
-  },
-  {
    "fieldname": "address_html",
    "fieldtype": "HTML",
    "label": "Address HTML"
   },
   {
-   "fieldname": "column_break_17",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "contact_html",
    "fieldtype": "HTML",
    "label": "Contact HTML"
   },
   {
    "collapsible": 1,
+   "depends_on": "eval:!doc.__islocal",
    "fieldname": "notes_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Notes"
   },
   {
-   "fieldname": "notes",
-   "fieldtype": "Text Editor"
-  },
-  {
-   "fieldname": "more_details_section",
-   "fieldtype": "Section Break",
-   "label": "More Details"
-  },
-  {
-   "fieldname": "column_break_13",
-   "fieldtype": "Column Break"
-  },
-  {
    "depends_on": "eval: !doc.__islocal",
    "fieldname": "address_and_contact_section",
    "fieldtype": "Section Break",
-   "label": "Address and Contact"
+   "label": "Address"
   },
   {
    "fieldname": "company",
@@ -161,11 +142,83 @@
    "label": "Company",
    "options": "Company",
    "reqd": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "opportunities_tab",
+   "fieldtype": "Tab Break",
+   "label": "Opportunities"
+  },
+  {
+   "depends_on": "eval:!doc.__islocal",
+   "fieldname": "activities_tab",
+   "fieldtype": "Tab Break",
+   "label": "Activities"
+  },
+  {
+   "fieldname": "notes_html",
+   "fieldtype": "HTML",
+   "label": "Notes HTML"
+  },
+  {
+   "fieldname": "opportunities",
+   "fieldtype": "Table",
+   "label": "Opportunities",
+   "options": "Prospect Opportunity"
+  },
+  {
+   "fieldname": "contacts_tab",
+   "fieldtype": "Tab Break",
+   "label": "Address & Contact"
+  },
+  {
+   "fieldname": "column_break_18",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "leads",
+   "fieldtype": "Table",
+   "options": "Prospect Lead"
+  },
+  {
+   "fieldname": "column_break_16",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "overview_tab",
+   "fieldtype": "Tab Break",
+   "label": "Overview"
+  },
+  {
+   "fieldname": "open_activities_html",
+   "fieldtype": "HTML",
+   "label": "Open Activities HTML"
+  },
+  {
+   "fieldname": "all_activities_section",
+   "fieldtype": "Section Break",
+   "label": "All Activities"
+  },
+  {
+   "fieldname": "all_activities_html",
+   "fieldtype": "HTML",
+   "label": "All Activities HTML"
+  },
+  {
+   "fieldname": "notes",
+   "fieldtype": "Table",
+   "hidden": 1,
+   "label": "Notes",
+   "no_copy": 1,
+   "options": "CRM Note"
   }
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2021-11-01 13:10:36.759249",
+ "modified": "2022-06-21 15:10:26.887502",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Prospect",
@@ -207,6 +260,7 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "title_field": "company_name",
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/prospect/prospect.py b/erpnext/crm/doctype/prospect/prospect.py
index 39436f5..fbb1158 100644
--- a/erpnext/crm/doctype/prospect/prospect.py
+++ b/erpnext/crm/doctype/prospect/prospect.py
@@ -3,19 +3,15 @@
 
 import frappe
 from frappe.contacts.address_and_contact import load_address_and_contact
-from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
 
-from erpnext.crm.utils import add_link_in_communication, copy_comments
+from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
 
 
-class Prospect(Document):
+class Prospect(CRMNote):
 	def onload(self):
 		load_address_and_contact(self)
 
-	def validate(self):
-		self.update_lead_details()
-
 	def on_update(self):
 		self.link_with_lead_contact_and_address()
 
@@ -23,23 +19,24 @@
 		self.unlink_dynamic_links()
 
 	def after_insert(self):
-		if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"):
-			for row in self.get("prospect_lead"):
-				copy_comments("Lead", row.lead, self)
-				add_link_in_communication("Lead", row.lead, self)
+		carry_forward_communication_and_comments = frappe.db.get_single_value(
+			"CRM Settings", "carry_forward_communication_and_comments"
+		)
 
-	def update_lead_details(self):
-		for row in self.get("prospect_lead"):
-			lead = frappe.get_value(
-				"Lead", row.lead, ["lead_name", "status", "email_id", "mobile_no"], as_dict=True
-			)
-			row.lead_name = lead.lead_name
-			row.status = lead.status
-			row.email = lead.email_id
-			row.mobile_no = lead.mobile_no
+		for row in self.get("leads"):
+			if carry_forward_communication_and_comments:
+				copy_comments("Lead", row.lead, self)
+				link_communications("Lead", row.lead, self)
+			link_open_events("Lead", row.lead, self)
+
+		for row in self.get("opportunities"):
+			if carry_forward_communication_and_comments:
+				copy_comments("Opportunity", row.opportunity, self)
+				link_communications("Opportunity", row.opportunity, self)
+			link_open_events("Opportunity", row.opportunity, self)
 
 	def link_with_lead_contact_and_address(self):
-		for row in self.prospect_lead:
+		for row in self.leads:
 			links = frappe.get_all(
 				"Dynamic Link",
 				filters={"link_doctype": "Lead", "link_name": row.lead},
@@ -116,9 +113,7 @@
 		{
 			"Prospect": {
 				"doctype": "Opportunity",
-				"field_map": {
-					"name": "party_name",
-				},
+				"field_map": {"name": "party_name", "prospect_owner": "opportunity_owner"},
 			}
 		},
 		target_doc,
@@ -127,3 +122,25 @@
 	)
 
 	return doclist
+
+
+@frappe.whitelist()
+def get_opportunities(prospect):
+	return frappe.get_all(
+		"Opportunity",
+		filters={"opportunity_from": "Prospect", "party_name": prospect},
+		fields=[
+			"opportunity_owner",
+			"sales_stage",
+			"status",
+			"expected_closing",
+			"probability",
+			"opportunity_amount",
+			"currency",
+			"contact_person",
+			"contact_email",
+			"contact_mobile",
+			"creation",
+			"name",
+		],
+	)
diff --git a/erpnext/crm/doctype/prospect/test_prospect.py b/erpnext/crm/doctype/prospect/test_prospect.py
index ddd7b93..874f84c 100644
--- a/erpnext/crm/doctype/prospect/test_prospect.py
+++ b/erpnext/crm/doctype/prospect/test_prospect.py
@@ -20,7 +20,7 @@
 		add_lead_to_prospect(lead_doc.name, prospect_doc.name)
 		prospect_doc.reload()
 		lead_exists_in_prosoect = False
-		for rec in prospect_doc.get("prospect_lead"):
+		for rec in prospect_doc.get("leads"):
 			if rec.lead == lead_doc.name:
 				lead_exists_in_prosoect = True
 		self.assertEqual(lead_exists_in_prosoect, True)
diff --git a/erpnext/crm/doctype/prospect_lead/prospect_lead.json b/erpnext/crm/doctype/prospect_lead/prospect_lead.json
index 3c160d9..075c0f9 100644
--- a/erpnext/crm/doctype/prospect_lead/prospect_lead.json
+++ b/erpnext/crm/doctype/prospect_lead/prospect_lead.json
@@ -7,12 +7,15 @@
  "field_order": [
   "lead",
   "lead_name",
-  "status",
   "email",
-  "mobile_no"
+  "column_break_4",
+  "mobile_no",
+  "lead_owner",
+  "status"
  ],
  "fields": [
   {
+   "columns": 2,
    "fieldname": "lead",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -21,6 +24,8 @@
    "reqd": 1
   },
   {
+   "columns": 2,
+   "fetch_from": "lead.lead_name",
    "fieldname": "lead_name",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -28,14 +33,17 @@
    "read_only": 1
   },
   {
+   "columns": 1,
+   "fetch_from": "lead.status",
    "fieldname": "status",
-   "fieldtype": "Select",
+   "fieldtype": "Data",
    "in_list_view": 1,
    "label": "Status",
-   "options": "Lead\nOpen\nReplied\nOpportunity\nQuotation\nLost Quotation\nInterested\nConverted\nDo Not Contact",
    "read_only": 1
   },
   {
+   "columns": 2,
+   "fetch_from": "lead.email_id",
    "fieldname": "email",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -44,18 +52,32 @@
    "read_only": 1
   },
   {
+   "columns": 2,
+   "fetch_from": "lead.mobile_no",
    "fieldname": "mobile_no",
    "fieldtype": "Data",
    "in_list_view": 1,
    "label": "Mobile No",
    "options": "Phone",
    "read_only": 1
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "columns": 1,
+   "fetch_from": "lead.lead_owner",
+   "fieldname": "lead_owner",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Lead Owner"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-08-25 12:58:24.638054",
+ "modified": "2022-04-28 20:27:58.805970",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Prospect Lead",
@@ -63,5 +85,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/crm/doctype/prospect_opportunity/__init__.py b/erpnext/crm/doctype/prospect_opportunity/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/doctype/prospect_opportunity/__init__.py
diff --git a/erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.json b/erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.json
new file mode 100644
index 0000000..d8c2520
--- /dev/null
+++ b/erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.json
@@ -0,0 +1,101 @@
+{
+ "actions": [],
+ "autoname": "autoincrement",
+ "creation": "2022-04-27 17:40:37.965161",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "opportunity",
+  "amount",
+  "stage",
+  "deal_owner",
+  "column_break_4",
+  "probability",
+  "expected_closing",
+  "currency",
+  "contact_person"
+ ],
+ "fields": [
+  {
+   "columns": 2,
+   "fieldname": "opportunity",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Opportunity",
+   "options": "Opportunity"
+  },
+  {
+   "columns": 2,
+   "fetch_from": "opportunity.opportunity_amount",
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Amount",
+   "options": "currency"
+  },
+  {
+   "columns": 2,
+   "fetch_from": "opportunity.sales_stage",
+   "fieldname": "stage",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Stage"
+  },
+  {
+   "columns": 1,
+   "fetch_from": "opportunity.probability",
+   "fieldname": "probability",
+   "fieldtype": "Percent",
+   "in_list_view": 1,
+   "label": "Probability"
+  },
+  {
+   "columns": 1,
+   "fetch_from": "opportunity.expected_closing",
+   "fieldname": "expected_closing",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Closing"
+  },
+  {
+   "fetch_from": "opportunity.currency",
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "label": "Currency",
+   "options": "Currency"
+  },
+  {
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
+  {
+   "columns": 2,
+   "fetch_from": "opportunity.opportunity_owner",
+   "fieldname": "deal_owner",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Deal Owner"
+  },
+  {
+   "fetch_from": "opportunity.contact_person",
+   "fieldname": "contact_person",
+   "fieldtype": "Link",
+   "label": "Contact Person",
+   "options": "Contact"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-04-28 10:05:38.730368",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Prospect Opportunity",
+ "naming_rule": "Autoincrement",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.py b/erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.py
new file mode 100644
index 0000000..8f5d19a
--- /dev/null
+++ b/erpnext/crm/doctype/prospect_opportunity/prospect_opportunity.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class ProspectOpportunity(Document):
+	pass
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.js b/erpnext/crm/report/lost_opportunity/lost_opportunity.js
index 97c56f8..927c54d 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.js
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.js
@@ -57,11 +57,5 @@
 			"fieldtype": "Dynamic Link",
 			"options": "opportunity_from"
 		},
-		{
-			"fieldname":"contact_by",
-			"label": __("Next Contact By"),
-			"fieldtype": "Link",
-			"options": "User"
-		},
 	]
 };
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.json b/erpnext/crm/report/lost_opportunity/lost_opportunity.json
index e7a8e12..f6f36bd 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.json
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.json
@@ -7,8 +7,8 @@
  "doctype": "Report",
  "idx": 0,
  "is_standard": "Yes",
- "json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"contact_by\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}",
- "modified": "2020-07-29 15:49:02.848845",
+ "json": "{\"order_by\": \"`tabOpportunity`.`modified` desc\", \"filters\": [[\"Opportunity\", \"status\", \"=\", \"Lost\"]], \"fields\": [[\"name\", \"Opportunity\"], [\"opportunity_from\", \"Opportunity\"], [\"party_name\", \"Opportunity\"], [\"customer_name\", \"Opportunity\"], [\"opportunity_type\", \"Opportunity\"], [\"status\", \"Opportunity\"], [\"docstatus\", \"Opportunity\"], [\"lost_reason\", \"Lost Reason Detail\"]], \"add_totals_row\": 0, \"add_total_row\": 0, \"page_length\": 20}",
+ "modified": "2022-06-04 15:49:02.848845",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Lost Opportunity",
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
index a57b44b..254511c 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
@@ -61,13 +61,6 @@
 			"options": "Territory",
 			"width": 150,
 		},
-		{
-			"label": _("Next Contact By"),
-			"fieldname": "contact_by",
-			"fieldtype": "Link",
-			"options": "User",
-			"width": 150,
-		},
 	]
 	return columns
 
@@ -81,7 +74,6 @@
 			`tabOpportunity`.party_name,
 			`tabOpportunity`.customer_name,
 			`tabOpportunity`.opportunity_type,
-			`tabOpportunity`.contact_by,
 			GROUP_CONCAT(`tabOpportunity Lost Reason Detail`.lost_reason separator ', ') lost_reason,
 			`tabOpportunity`.sales_stage,
 			`tabOpportunity`.territory
@@ -115,9 +107,6 @@
 	if filters.get("party_name"):
 		conditions.append(" and `tabOpportunity`.party_name=%(party_name)s")
 
-	if filters.get("contact_by"):
-		conditions.append(" and `tabOpportunity`.contact_by=%(contact_by)s")
-
 	return " ".join(conditions) if conditions else ""
 
 
diff --git a/erpnext/crm/utils.py b/erpnext/crm/utils.py
index 5783b2c..a2528c3 100644
--- a/erpnext/crm/utils.py
+++ b/erpnext/crm/utils.py
@@ -1,4 +1,7 @@
 import frappe
+from frappe.model.document import Document
+from frappe.utils import cstr, now, today
+from pypika import functions
 
 
 def update_lead_phone_numbers(contact, method):
@@ -41,7 +44,7 @@
 		comment.insert()
 
 
-def add_link_in_communication(doctype, docname, doc):
+def link_communications(doctype, docname, doc):
 	communication_list = get_linked_communication_list(doctype, docname)
 
 	for communication in communication_list:
@@ -60,3 +63,159 @@
 	)
 
 	return communications + communication_links
+
+
+def link_communications_with_prospect(communication, method):
+	prospect = get_linked_prospect(communication.reference_doctype, communication.reference_name)
+
+	if prospect:
+		already_linked = any(
+			[
+				d.name
+				for d in communication.get("timeline_links")
+				if d.link_doctype == "Prospect" and d.link_name == prospect
+			]
+		)
+		if not already_linked:
+			row = communication.append("timeline_links")
+			row.link_doctype = "Prospect"
+			row.link_name = prospect
+			row.db_update()
+
+
+def get_linked_prospect(reference_doctype, reference_name):
+	prospect = None
+	if reference_doctype == "Lead":
+		prospect = frappe.db.get_value("Prospect Lead", {"lead": reference_name}, "parent")
+
+	elif reference_doctype == "Opportunity":
+		opportunity_from, party_name = frappe.db.get_value(
+			"Opportunity", reference_name, ["opportunity_from", "party_name"]
+		)
+		if opportunity_from == "Lead":
+			prospect = frappe.db.get_value(
+				"Prospect Opportunity", {"opportunity": reference_name}, "parent"
+			)
+		if opportunity_from == "Prospect":
+			prospect = party_name
+
+	return prospect
+
+
+def link_events_with_prospect(event, method):
+	if event.event_participants:
+		ref_doctype = event.event_participants[0].reference_doctype
+		ref_docname = event.event_participants[0].reference_docname
+		prospect = get_linked_prospect(ref_doctype, ref_docname)
+		if prospect:
+			event.add_participant("Prospect", prospect)
+			event.save()
+
+
+def link_open_tasks(ref_doctype, ref_docname, doc):
+	todos = get_open_todos(ref_doctype, ref_docname)
+
+	for todo in todos:
+		todo_doc = frappe.get_doc("ToDo", todo.name)
+		todo_doc.reference_type = doc.doctype
+		todo_doc.reference_name = doc.name
+		todo_doc.db_update()
+
+
+def link_open_events(ref_doctype, ref_docname, doc):
+	events = get_open_events(ref_doctype, ref_docname)
+	for event in events:
+		event_doc = frappe.get_doc("Event", event.name)
+		event_doc.add_participant(doc.doctype, doc.name)
+		event_doc.save()
+
+
+@frappe.whitelist()
+def get_open_activities(ref_doctype, ref_docname):
+	tasks = get_open_todos(ref_doctype, ref_docname)
+	events = get_open_events(ref_doctype, ref_docname)
+
+	return {"tasks": tasks, "events": events}
+
+
+def get_open_todos(ref_doctype, ref_docname):
+	return frappe.get_all(
+		"ToDo",
+		filters={"reference_type": ref_doctype, "reference_name": ref_docname, "status": "Open"},
+		fields=[
+			"name",
+			"description",
+			"allocated_to",
+			"date",
+		],
+	)
+
+
+def get_open_events(ref_doctype, ref_docname):
+	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.name,
+			event.subject,
+			event.event_category,
+			event.starts_on,
+			event.ends_on,
+			event.description,
+		)
+		.where(
+			(event_link.reference_doctype == ref_doctype)
+			& (event_link.reference_docname == ref_docname)
+			& (event.status == "Open")
+		)
+	)
+	data = query.run(as_dict=True)
+
+	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):
+		self.append("notes", {"note": note, "added_by": frappe.session.user, "added_on": now()})
+		self.save()
+
+	@frappe.whitelist()
+	def edit_note(self, note, row_id):
+		for d in self.notes:
+			if cstr(d.name) == row_id:
+				d.note = note
+				d.db_update()
+
+	@frappe.whitelist()
+	def delete_note(self, row_id):
+		for d in self.notes:
+			if cstr(d.name) == row_id:
+				self.remove(d)
+				break
+		self.save()
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 7d7f65d..816cb62 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -299,7 +299,11 @@
 		"on_update": [
 			"erpnext.support.doctype.service_level_agreement.service_level_agreement.on_communication_update",
 			"erpnext.support.doctype.issue.issue.set_first_response_time",
-		]
+		],
+		"after_insert": "erpnext.crm.utils.link_communications_with_prospect",
+	},
+	"Event": {
+		"after_insert": "erpnext.crm.utils.link_events_with_prospect",
 	},
 	"Sales Taxes and Charges Template": {
 		"on_update": "erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings.validate_cart_settings"
@@ -398,6 +402,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",
@@ -407,7 +419,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",
@@ -418,7 +429,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",
@@ -453,7 +463,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.doctype.lead.lead.daily_open_lead",
+		"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/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 4c88eca..b29f671 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -445,6 +445,7 @@
 			and self.is_active
 		):
 			frappe.db.set(self, "is_default", 1)
+			frappe.db.set_value("Item", self.item, "default_bom", self.name)
 		else:
 			frappe.db.set(self, "is_default", 0)
 			item = frappe.get_doc("Item", self.item)
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 182a20c..860512c 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -559,6 +559,42 @@
 		bom.submit()
 		self.assertEqual(bom.items[0].rate, 42)
 
+	def test_set_default_bom_for_item_having_single_bom(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		fg_item = make_item(properties={"is_stock_item": 1})
+		bom_item = make_item(properties={"is_stock_item": 1})
+
+		# Step 1: Create BOM
+		bom = frappe.new_doc("BOM")
+		bom.item = fg_item.item_code
+		bom.quantity = 1
+		bom.append(
+			"items",
+			{
+				"item_code": bom_item.item_code,
+				"qty": 1,
+				"uom": bom_item.stock_uom,
+				"stock_uom": bom_item.stock_uom,
+				"rate": 100.0,
+			},
+		)
+		bom.save()
+		bom.submit()
+		self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
+
+		# Step 2: Uncheck is_active field
+		bom.is_active = 0
+		bom.save()
+		bom.reload()
+		self.assertIsNone(frappe.get_value("Item", fg_item.item_code, "default_bom"))
+
+		# Step 3: Check is_active field
+		bom.is_active = 1
+		bom.save()
+		bom.reload()
+		self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
+
 
 def get_default_bom(item_code="_Test FG Item 2"):
 	return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
index c32e383..a926e69 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.json
@@ -7,11 +7,11 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "current_bom",
-  "new_bom",
-  "column_break_3",
   "update_type",
   "status",
+  "column_break_3",
+  "current_bom",
+  "new_bom",
   "error_log",
   "progress_section",
   "current_level",
@@ -37,6 +37,7 @@
    "options": "BOM"
   },
   {
+   "depends_on": "eval:doc.update_type === \"Replace BOM\"",
    "fieldname": "column_break_3",
    "fieldtype": "Column Break"
   },
@@ -87,6 +88,7 @@
    "options": "BOM Update Batch"
   },
   {
+   "depends_on": "eval:doc.status !== \"Completed\"",
    "fieldname": "current_level",
    "fieldtype": "Int",
    "label": "Current Level"
@@ -96,7 +98,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-06-06 15:15:23.883251",
+ "modified": "2022-06-20 15:43:55.696388",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM Update Log",
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
index 9c9c240..c3f52d4 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
@@ -6,6 +6,8 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from frappe.query_builder import DocType, Interval
+from frappe.query_builder.functions import Now
 from frappe.utils import cint, cstr
 
 from erpnext.manufacturing.doctype.bom_update_log.bom_updation_utils import (
@@ -22,6 +24,17 @@
 
 
 class BOMUpdateLog(Document):
+	@staticmethod
+	def clear_old_logs(days=None):
+		days = days or 90
+		table = DocType("BOM Update Log")
+		frappe.db.delete(
+			table,
+			filters=(
+				(table.modified < (Now() - Interval(days=days))) & (table.update_type == "Update Cost")
+			),
+		)
+
 	def validate(self):
 		if self.update_type == "Replace BOM":
 			self.validate_boms_are_specified()
@@ -77,7 +90,11 @@
 				now=frappe.flags.in_test,
 			)
 		else:
-			process_boms_cost_level_wise(self)
+			frappe.enqueue(
+				method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
+				update_doc=self,
+				now=frappe.flags.in_test,
+			)
 
 
 def run_replace_bom_job(
@@ -112,28 +129,31 @@
 	current_boms = {}
 	values = {}
 
-	if update_doc.status == "Queued":
-		# First level yet to process. On Submit.
-		current_level = 0
-		current_boms = get_leaf_boms()
-		values = {
-			"processed_boms": json.dumps({}),
-			"status": "In Progress",
-			"current_level": current_level,
-		}
-	else:
-		# Resume next level. via Cron Job.
-		if not parent_boms:
-			return
+	try:
+		if update_doc.status == "Queued":
+			# First level yet to process. On Submit.
+			current_level = 0
+			current_boms = get_leaf_boms()
+			values = {
+				"processed_boms": json.dumps({}),
+				"status": "In Progress",
+				"current_level": current_level,
+			}
+		else:
+			# Resume next level. via Cron Job.
+			if not parent_boms:
+				return
 
-		current_level = cint(update_doc.current_level) + 1
+			current_level = cint(update_doc.current_level) + 1
 
-		# Process the next level BOMs. Stage parents as current BOMs.
-		current_boms = parent_boms.copy()
-		values = {"current_level": current_level}
+			# Process the next level BOMs. Stage parents as current BOMs.
+			current_boms = parent_boms.copy()
+			values = {"current_level": current_level}
 
-	set_values_in_log(update_doc.name, values, commit=True)
-	queue_bom_cost_jobs(current_boms, update_doc, current_level)
+		set_values_in_log(update_doc.name, values, commit=True)
+		queue_bom_cost_jobs(current_boms, update_doc, current_level)
+	except Exception:
+		handle_exception(update_doc)
 
 
 def queue_bom_cost_jobs(
@@ -199,16 +219,22 @@
 		current_boms, processed_boms = get_processed_current_boms(log, bom_batches)
 		parent_boms = get_next_higher_level_boms(child_boms=current_boms, processed_boms=processed_boms)
 
-		# Unset processed BOMs if log is complete, it is used for next level BOMs
+		# Unset processed BOMs (it is used for next level BOMs) & change status if log is complete
+		status = "Completed" if not parent_boms else "In Progress"
+		processed_boms = json.dumps([] if not parent_boms else processed_boms)
 		set_values_in_log(
 			log.name,
 			values={
-				"processed_boms": json.dumps([] if not parent_boms else processed_boms),
-				"status": "Completed" if not parent_boms else "In Progress",
+				"processed_boms": processed_boms,
+				"status": status,
 			},
 			commit=True,
 		)
 
+		# clear progress section
+		if status == "Completed":
+			frappe.db.delete("BOM Update Batch", {"parent": log.name})
+
 		if parent_boms:  # there is a next level to process
 			process_boms_cost_level_wise(
 				update_doc=frappe.get_doc("BOM Update Log", log.name), parent_boms=parent_boms
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js
index e39b563..bc709d8 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log_list.js
@@ -1,6 +1,6 @@
 frappe.listview_settings['BOM Update Log'] = {
 	add_fields: ["status"],
-	get_indicator: function(doc) {
+	get_indicator: (doc) => {
 		let status_map = {
 			"Queued": "orange",
 			"In Progress": "blue",
@@ -9,5 +9,22 @@
 		};
 
 		return [__(doc.status), status_map[doc.status], "status,=," + doc.status];
-	}
+	},
+	onload: () => {
+		if (!frappe.model.can_write("Log Settings")) {
+			return;
+		}
+
+		let sidebar_entry = $(
+			'<ul class="list-unstyled sidebar-menu log-retention-note"></ul>'
+		).appendTo(cur_list.page.sidebar);
+		let message = __("Note: Automatic log deletion only applies to logs of type <i>Update Cost</i>");
+		$(`<hr><div class='text-muted'>${message}</div>`).appendTo(sidebar_entry);
+
+		frappe.require("logtypes.bundle.js", () => {
+			frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+		});
+
+
+	},
 };
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 8a28454..a73b9bc 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -25,6 +25,7 @@
 from erpnext.manufacturing.doctype.bom.bom import validate_bom_no
 from erpnext.manufacturing.doctype.work_order.work_order import get_item_details
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
+from erpnext.utilities.transaction_base import validate_uom_is_integer
 
 
 class ProductionPlan(Document):
@@ -33,6 +34,7 @@
 		self.calculate_total_planned_qty()
 		self.set_status()
 		self._rename_temporary_references()
+		validate_uom_is_integer(self, "stock_uom", "planned_qty")
 
 	def set_pending_qty_in_row_without_reference(self):
 		"Set Pending Qty in independent rows (not from SO or MR)."
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index e88049d..040e791 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -679,15 +679,23 @@
 		self.assertFalse(pp.all_items_completed())
 
 	def test_production_plan_planned_qty(self):
-		pln = create_production_plan(item_code="_Test FG Item", planned_qty=0.55)
-		pln.make_work_order()
-		work_order = frappe.db.get_value("Work Order", {"production_plan": pln.name}, "name")
-		wo_doc = frappe.get_doc("Work Order", work_order)
-		wo_doc.update(
-			{"wip_warehouse": "Work In Progress - _TC", "fg_warehouse": "Finished Goods - _TC"}
+		# Case 1: When Planned Qty is non-integer and UOM is integer.
+		from erpnext.utilities.transaction_base import UOMMustBeIntegerError
+
+		self.assertRaises(
+			UOMMustBeIntegerError, create_production_plan, item_code="_Test FG Item", planned_qty=0.55
 		)
-		wo_doc.submit()
-		self.assertEqual(wo_doc.qty, 0.55)
+
+		# Case 2: When Planned Qty is non-integer and UOM is also non-integer.
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		fg_item = make_item(properties={"is_stock_item": 1, "stock_uom": "_Test UOM 1"}).name
+		bom_item = make_item().name
+
+		make_bom(item=fg_item, raw_materials=[bom_item], source_warehouse="_Test Warehouse - _TC")
+
+		pln = create_production_plan(item_code=fg_item, planned_qty=0.55, stock_uom="_Test UOM 1")
+		self.assertEqual(pln.po_items[0].planned_qty, 0.55)
 
 	def test_temporary_name_relinking(self):
 
@@ -751,6 +759,7 @@
 				"bom_no": frappe.db.get_value("Item", args.item_code, "default_bom"),
 				"planned_qty": args.planned_qty or 1,
 				"planned_start_date": args.planned_start_date or now_datetime(),
+				"stock_uom": args.stock_uom or "Nos",
 			},
 		)
 
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 27e7e24..6bb4cfc 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -1,6 +1,8 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 
+import copy
+
 import frappe
 from frappe.tests.utils import FrappeTestCase, change_settings, timeout
 from frappe.utils import add_days, add_months, cint, flt, now, today
@@ -19,6 +21,7 @@
 )
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.stock.doctype.item.test_item import create_item, make_item
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 from erpnext.stock.doctype.stock_entry import test_stock_entry
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
 from erpnext.stock.utils import get_bin
@@ -28,6 +31,7 @@
 	def setUp(self):
 		self.warehouse = "_Test Warehouse 2 - _TC"
 		self.item = "_Test Item"
+		prepare_data_for_backflush_based_on_materials_transferred()
 
 	def tearDown(self):
 		frappe.db.rollback()
@@ -527,6 +531,8 @@
 			work_order.cancel()
 
 	def test_work_order_with_non_transfer_item(self):
+		frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
+
 		items = {"Finished Good Transfer Item": 1, "_Test FG Item": 1, "_Test FG Item 1": 0}
 		for item, allow_transfer in items.items():
 			make_item(item, {"include_item_in_manufacturing": allow_transfer})
@@ -1071,7 +1077,7 @@
 		sm = frappe.get_doc(make_stock_entry(wo_order.name, "Material Transfer for Manufacture", 100))
 		for row in sm.get("items"):
 			if row.get("item_code") == "_Test Item":
-				row.qty = 110
+				row.qty = 120
 
 		sm.submit()
 		cancel_stock_entry.append(sm.name)
@@ -1079,21 +1085,21 @@
 		s = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 90))
 		for row in s.get("items"):
 			if row.get("item_code") == "_Test Item":
-				self.assertEqual(row.get("qty"), 100)
+				self.assertEqual(row.get("qty"), 108)
 		s.submit()
 		cancel_stock_entry.append(s.name)
 
 		s1 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
 		for row in s1.get("items"):
 			if row.get("item_code") == "_Test Item":
-				self.assertEqual(row.get("qty"), 5)
+				self.assertEqual(row.get("qty"), 6)
 		s1.submit()
 		cancel_stock_entry.append(s1.name)
 
 		s2 = frappe.get_doc(make_stock_entry(wo_order.name, "Manufacture", 5))
 		for row in s2.get("items"):
 			if row.get("item_code") == "_Test Item":
-				self.assertEqual(row.get("qty"), 5)
+				self.assertEqual(row.get("qty"), 6)
 
 		cancel_stock_entry.reverse()
 		for ste in cancel_stock_entry:
@@ -1203,6 +1209,269 @@
 		self.assertEqual(work_order.required_items[0].transferred_qty, 1)
 		self.assertEqual(work_order.required_items[1].transferred_qty, 2)
 
+	def test_backflushed_batch_raw_materials_based_on_transferred(self):
+		frappe.db.set_value(
+			"Manufacturing Settings",
+			None,
+			"backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture",
+		)
+
+		batch_item = "Test Batch MCC Keyboard"
+		fg_item = "Test FG Item with Batch Raw Materials"
+
+		ste_doc = test_stock_entry.make_stock_entry(
+			item_code=batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
+		)
+
+		ste_doc.append(
+			"items",
+			{
+				"item_code": batch_item,
+				"item_name": batch_item,
+				"description": batch_item,
+				"basic_rate": 100,
+				"t_warehouse": "Stores - _TC",
+				"qty": 2,
+				"uom": "Nos",
+				"stock_uom": "Nos",
+				"conversion_factor": 1,
+			},
+		)
+
+		# Inward raw materials in Stores warehouse
+		ste_doc.insert()
+		ste_doc.submit()
+
+		batch_list = [row.batch_no for row in ste_doc.items]
+
+		wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
+		transferred_ste_doc = frappe.get_doc(
+			make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
+		)
+
+		transferred_ste_doc.items[0].qty = 2
+		transferred_ste_doc.items[0].batch_no = batch_list[0]
+
+		new_row = copy.deepcopy(transferred_ste_doc.items[0])
+		new_row.name = ""
+		new_row.batch_no = batch_list[1]
+
+		# Transferred two batches from Stores to WIP Warehouse
+		transferred_ste_doc.append("items", new_row)
+		transferred_ste_doc.submit()
+
+		# First Manufacture stock entry
+		manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
+
+		# Batch no should be same as transferred Batch no
+		self.assertEqual(manufacture_ste_doc1.items[0].batch_no, batch_list[0])
+		self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
+
+		manufacture_ste_doc1.submit()
+
+		# Second Manufacture stock entry
+		manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
+
+		# Batch no should be same as transferred Batch no
+		self.assertEqual(manufacture_ste_doc2.items[0].batch_no, batch_list[0])
+		self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
+		self.assertEqual(manufacture_ste_doc2.items[1].batch_no, batch_list[1])
+		self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
+
+	def test_backflushed_serial_no_raw_materials_based_on_transferred(self):
+		frappe.db.set_value(
+			"Manufacturing Settings",
+			None,
+			"backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture",
+		)
+
+		sn_item = "Test Serial No BTT Headphone"
+		fg_item = "Test FG Item with Serial No Raw Materials"
+
+		ste_doc = test_stock_entry.make_stock_entry(
+			item_code=sn_item, target="Stores - _TC", qty=4, basic_rate=100, do_not_save=True
+		)
+
+		# Inward raw materials in Stores warehouse
+		ste_doc.submit()
+
+		serial_nos_list = sorted(get_serial_nos(ste_doc.items[0].serial_no))
+
+		wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
+		transferred_ste_doc = frappe.get_doc(
+			make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
+		)
+
+		transferred_ste_doc.items[0].serial_no = "\n".join(serial_nos_list)
+		transferred_ste_doc.submit()
+
+		# First Manufacture stock entry
+		manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
+
+		# Serial nos should be same as transferred Serial nos
+		self.assertEqual(get_serial_nos(manufacture_ste_doc1.items[0].serial_no), serial_nos_list[0:1])
+		self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
+
+		manufacture_ste_doc1.submit()
+
+		# Second Manufacture stock entry
+		manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
+
+		# Serial nos should be same as transferred Serial nos
+		self.assertEqual(get_serial_nos(manufacture_ste_doc2.items[0].serial_no), serial_nos_list[1:3])
+		self.assertEqual(manufacture_ste_doc2.items[0].qty, 2)
+
+	def test_backflushed_serial_no_batch_raw_materials_based_on_transferred(self):
+		frappe.db.set_value(
+			"Manufacturing Settings",
+			None,
+			"backflush_raw_materials_based_on",
+			"Material Transferred for Manufacture",
+		)
+
+		sn_batch_item = "Test Batch Serial No WebCam"
+		fg_item = "Test FG Item with Serial & Batch No Raw Materials"
+
+		ste_doc = test_stock_entry.make_stock_entry(
+			item_code=sn_batch_item, target="Stores - _TC", qty=2, basic_rate=100, do_not_save=True
+		)
+
+		ste_doc.append(
+			"items",
+			{
+				"item_code": sn_batch_item,
+				"item_name": sn_batch_item,
+				"description": sn_batch_item,
+				"basic_rate": 100,
+				"t_warehouse": "Stores - _TC",
+				"qty": 2,
+				"uom": "Nos",
+				"stock_uom": "Nos",
+				"conversion_factor": 1,
+			},
+		)
+
+		# Inward raw materials in Stores warehouse
+		ste_doc.insert()
+		ste_doc.submit()
+
+		batch_dict = {row.batch_no: get_serial_nos(row.serial_no) for row in ste_doc.items}
+		batches = list(batch_dict.keys())
+
+		wo_doc = make_wo_order_test_record(production_item=fg_item, qty=4)
+		transferred_ste_doc = frappe.get_doc(
+			make_stock_entry(wo_doc.name, "Material Transfer for Manufacture", 4)
+		)
+
+		transferred_ste_doc.items[0].qty = 2
+		transferred_ste_doc.items[0].batch_no = batches[0]
+		transferred_ste_doc.items[0].serial_no = "\n".join(batch_dict.get(batches[0]))
+
+		new_row = copy.deepcopy(transferred_ste_doc.items[0])
+		new_row.name = ""
+		new_row.batch_no = batches[1]
+		new_row.serial_no = "\n".join(batch_dict.get(batches[1]))
+
+		# Transferred two batches from Stores to WIP Warehouse
+		transferred_ste_doc.append("items", new_row)
+		transferred_ste_doc.submit()
+
+		# First Manufacture stock entry
+		manufacture_ste_doc1 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 1))
+
+		# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
+		batch_no = manufacture_ste_doc1.items[0].batch_no
+		self.assertEqual(
+			get_serial_nos(manufacture_ste_doc1.items[0].serial_no)[0], batch_dict.get(batch_no)[0]
+		)
+		self.assertEqual(manufacture_ste_doc1.items[0].qty, 1)
+
+		manufacture_ste_doc1.submit()
+
+		# Second Manufacture stock entry
+		manufacture_ste_doc2 = frappe.get_doc(make_stock_entry(wo_doc.name, "Manufacture", 2))
+
+		# Batch no & Serial Nos should be same as transferred Batch no & Serial Nos
+		batch_no = manufacture_ste_doc2.items[0].batch_no
+		self.assertEqual(
+			get_serial_nos(manufacture_ste_doc2.items[0].serial_no)[0], batch_dict.get(batch_no)[1]
+		)
+		self.assertEqual(manufacture_ste_doc2.items[0].qty, 1)
+
+		batch_no = manufacture_ste_doc2.items[1].batch_no
+		self.assertEqual(
+			get_serial_nos(manufacture_ste_doc2.items[1].serial_no)[0], batch_dict.get(batch_no)[0]
+		)
+		self.assertEqual(manufacture_ste_doc2.items[1].qty, 1)
+
+
+def prepare_data_for_backflush_based_on_materials_transferred():
+	batch_item_doc = make_item(
+		"Test Batch MCC Keyboard",
+		{
+			"is_stock_item": 1,
+			"has_batch_no": 1,
+			"create_new_batch": 1,
+			"batch_number_series": "TBMK.#####",
+			"valuation_rate": 100,
+			"stock_uom": "Nos",
+		},
+	)
+
+	item = make_item(
+		"Test FG Item with Batch Raw Materials",
+		{
+			"is_stock_item": 1,
+		},
+	)
+
+	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[batch_item_doc.name])
+
+	sn_item_doc = make_item(
+		"Test Serial No BTT Headphone",
+		{
+			"is_stock_item": 1,
+			"has_serial_no": 1,
+			"serial_no_series": "TSBH.#####",
+			"valuation_rate": 100,
+			"stock_uom": "Nos",
+		},
+	)
+
+	item = make_item(
+		"Test FG Item with Serial No Raw Materials",
+		{
+			"is_stock_item": 1,
+		},
+	)
+
+	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_item_doc.name])
+
+	sn_batch_item_doc = make_item(
+		"Test Batch Serial No WebCam",
+		{
+			"is_stock_item": 1,
+			"has_batch_no": 1,
+			"create_new_batch": 1,
+			"batch_number_series": "TBSW.#####",
+			"has_serial_no": 1,
+			"serial_no_series": "TBSWC.#####",
+			"valuation_rate": 100,
+			"stock_uom": "Nos",
+		},
+	)
+
+	item = make_item(
+		"Test FG Item with Serial & Batch No Raw Materials",
+		{
+			"is_stock_item": 1,
+		},
+	)
+
+	make_bom(item=item.name, source_warehouse="Stores - _TC", raw_materials=[sn_batch_item_doc.name])
+
 
 def update_job_card(job_card, jc_qty=None):
 	employee = frappe.db.get_value("Employee", {"status": "Active"}, "name")
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 318875d..2addf91 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -375,3 +375,4 @@
 erpnext.patches.v13_0.set_payroll_entry_status
 erpnext.patches.v13_0.job_card_status_on_hold
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
+erpnext.patches.v14_0.crm_ux_cleanup
diff --git a/erpnext/patches/v14_0/crm_ux_cleanup.py b/erpnext/patches/v14_0/crm_ux_cleanup.py
new file mode 100644
index 0000000..b2df36f
--- /dev/null
+++ b/erpnext/patches/v14_0/crm_ux_cleanup.py
@@ -0,0 +1,94 @@
+import frappe
+from frappe.model.utils.rename_field import rename_field
+from frappe.utils import add_months, cstr, today
+
+
+def execute():
+	for doctype in ("CRM Note", "Lead", "Opportunity", "Prospect", "Prospect Lead"):
+		frappe.reload_doc("crm", "doctype", doctype)
+
+	try:
+		rename_field("Lead", "designation", "job_title")
+		rename_field("Opportunity", "converted_by", "opportunity_owner")
+
+		frappe.db.sql(
+			"""
+			update `tabProspect Lead`
+			set parentfield='leads'
+			where parentfield='partner_lead'
+		"""
+		)
+	except Exception as e:
+		if e.args[0] != 1054:
+			raise
+
+	add_calendar_event_for_leads()
+	add_calendar_event_for_opportunities()
+
+
+def add_calendar_event_for_leads():
+	# create events based on next contact date
+	leads = frappe.db.sql(
+		"""
+		select name, contact_date, contact_by, ends_on, lead_name, lead_owner
+		from tabLead
+		where contact_date >= %s
+	""",
+		add_months(today(), -1),
+		as_dict=1,
+	)
+
+	for d in leads:
+		event = frappe.get_doc(
+			{
+				"doctype": "Event",
+				"owner": d.lead_owner,
+				"subject": ("Contact " + cstr(d.lead_name)),
+				"description": (
+					("Contact " + cstr(d.lead_name)) + (("<br>By: " + cstr(d.contact_by)) if d.contact_by else "")
+				),
+				"starts_on": d.contact_date,
+				"ends_on": d.ends_on,
+				"event_type": "Private",
+			}
+		)
+
+		event.append("event_participants", {"reference_doctype": "Lead", "reference_docname": d.name})
+
+		event.insert(ignore_permissions=True)
+
+
+def add_calendar_event_for_opportunities():
+	# create events based on next contact date
+	opportunities = frappe.db.sql(
+		"""
+		select name, contact_date, contact_by, to_discuss,
+			party_name, opportunity_owner, contact_person
+		from tabOpportunity
+		where contact_date >= %s
+	""",
+		add_months(today(), -1),
+		as_dict=1,
+	)
+
+	for d in opportunities:
+		event = frappe.get_doc(
+			{
+				"doctype": "Event",
+				"owner": d.opportunity_owner,
+				"subject": ("Contact " + cstr(d.contact_person or d.party_name)),
+				"description": (
+					("Contact " + cstr(d.contact_person or d.party_name))
+					+ (("<br>By: " + cstr(d.contact_by)) if d.contact_by else "")
+					+ (("<br>Agenda: " + cstr(d.to_discuss)) if d.to_discuss else "")
+				),
+				"starts_on": d.contact_date,
+				"event_type": "Private",
+			}
+		)
+
+		event.append(
+			"event_participants", {"reference_doctype": "Opportunity", "reference_docname": d.name}
+		)
+
+		event.insert(ignore_permissions=True)
diff --git a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
index 1e0d20d..e15aa4a 100644
--- a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
+++ b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
@@ -1,6 +1,6 @@
 import frappe
 from frappe import qb
-from frappe.query_builder import Case
+from frappe.query_builder import Case, CustomFunction
 from frappe.query_builder.custom import ConstantColumn
 from frappe.query_builder.functions import IfNull
 
@@ -87,6 +87,7 @@
 
 		gl = qb.DocType("GL Entry")
 		account = qb.DocType("Account")
+		ifelse = CustomFunction("IF", ["condition", "then", "else"])
 
 		gl_entries = (
 			qb.from_(gl)
@@ -96,8 +97,12 @@
 				gl.star,
 				ConstantColumn(1).as_("docstatus"),
 				account.account_type.as_("account_type"),
-				IfNull(gl.against_voucher_type, gl.voucher_type).as_("against_voucher_type"),
-				IfNull(gl.against_voucher, gl.voucher_no).as_("against_voucher_no"),
+				IfNull(
+					ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
+				).as_("against_voucher_type"),
+				IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
+					"against_voucher_no"
+				),
 				# convert debit/credit to amount
 				Case()
 				.when(account.account_type == "Receivable", gl.debit - gl.credit)
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/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 57bfd5b..7298c03 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -84,7 +84,9 @@
 		emp = make_employee("test_employee_6@salary.com")
 
 		timesheet = make_timesheet(emp, simulate=True, is_billable=1)
-		sales_invoice = make_sales_invoice(timesheet.name, "_Test Item", "_Test Customer")
+		sales_invoice = make_sales_invoice(
+			timesheet.name, "_Test Item", "_Test Customer", currency="INR"
+		)
 		sales_invoice.due_date = nowdate()
 		sales_invoice.submit()
 		timesheet = frappe.get_doc("Timesheet", timesheet.name)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 1f86718..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) {
diff --git a/erpnext/public/js/erpnext.bundle.js b/erpnext/public/js/erpnext.bundle.js
index 3dae6d4..d545929 100644
--- a/erpnext/public/js/erpnext.bundle.js
+++ b/erpnext/public/js/erpnext.bundle.js
@@ -22,5 +22,8 @@
 import "./telephony";
 import "./templates/call_link.html";
 import "./bulk_transaction_processing";
+import "./utils/crm_activities";
+import "./templates/crm_activities.html";
+import "./templates/crm_notes.html";
 
 // import { sum } from 'frappe/public/utils/util.js'
diff --git a/erpnext/public/js/templates/crm_activities.html b/erpnext/public/js/templates/crm_activities.html
new file mode 100644
index 0000000..4260319
--- /dev/null
+++ b/erpnext/public/js/templates/crm_activities.html
@@ -0,0 +1,176 @@
+<div class="open-activities">
+	<div class="new-btn pb-3">
+		<span>
+			<button class="btn btn-sm small new-task-btn mr-1">
+				<svg class="icon icon-sm">
+					<use href="#icon-small-message"></use>
+				</svg>
+				{{ __("New Task") }}
+			</button>
+			<button class="btn btn-sm small new-event-btn">
+				<svg class="icon icon-sm">
+					<use href="#icon-calendar"></use>
+				</svg>
+				{{ __("New Event") }}
+			</button>
+		</span>
+	</div>
+	<div class="section-body">
+		<div class="open-tasks pr-1">
+			<div class="open-section-head">
+				<span class="ml-2">{{ __("Open Tasks") }}</span>
+			</div>
+			{% if (tasks.length) { %}
+				{% for(var i=0, l=tasks.length; i<l; i++) { %}
+					<div class="single-activity">
+						<div class="flex justify-between mb-2">
+							<div class="row label-area font-md ml-1">
+								<span class="mr-2">
+									<svg class="icon icon-sm">
+										<use href="#icon-small-message"></use>
+									</svg>
+								</span>
+								<a href="/app/todo/{{ tasks[i].name }}" title="{{ __('Open Task') }}">
+									{%= tasks[i].description %}
+								</a>
+							</div>
+							<div class="checkbox">
+								<input type="checkbox" class="completion-checkbox"
+									name="{{tasks[i].name}}" title="{{ __('Mark As Closed') }}">
+							</div>
+						</div>
+						{% if(tasks[i].date) { %}
+							<div class="text-muted ml-1">
+								{%= frappe.datetime.global_date_format(tasks[i].date) %}
+							</div>
+						{% } %}
+						{% if(tasks[i].allocated_to) { %}
+							<div class="text-muted  ml-1">
+								{{ __("Allocated To:") }}
+								{%= tasks[i].allocated_to %}
+							</div>
+						{% } %}
+						</div>
+			    {% } %}
+            {% } else { %}
+                <div class="single-activity no-activity text-muted">
+                    {{ __("No open task") }}
+                </div>
+		    {% } %}
+		</div>
+		<div class="open-events pl-1">
+			<div class="open-section-head">
+				<span class="ml-2">{{ __("Open Events") }}</span>
+			</div>
+			{% if (events.length) { %}
+                {% let icon_set = {"Sent/Received Email": "mail", "Call": "call", "Meeting": "share-people"}; %}
+                {% for(var i=0, l=events.length; i<l; i++) { %}
+                    <div class="single-activity">
+                        <div class="flex justify-between mb-2">
+                            <div class="row label-area font-md ml-1 title">
+                                <span class="mr-2">
+                                    <svg class="icon icon-sm">
+                                        <use href="#icon-{{ icon_set[events[i].event_category] || 'calendar' }}"></use>
+                                    </svg>
+                                </span>
+                                <a href="/app/event/{{ events[i].name }}" title="{{ __('Open Event') }}">
+                                    {%= events[i].subject %}
+                                </a>
+                            </div>
+                            <div class="checkbox">
+                                <input type="checkbox" class="completion-checkbox"
+                                    name="{{ events[i].name }}" title="{{ __('Mark As Closed') }}">
+                            </div>
+                        </div>
+                        <div class="text-muted ml-1">
+                            {%= frappe.datetime.global_date_format(events[i].starts_on) %}
+
+                            {% if (events[i].ends_on) { %}
+                                {% if (frappe.datetime.obj_to_user(events[i].starts_on) != frappe.datetime.obj_to_user(events[i].ends_on)) %}
+                                    -
+                                    {%= frappe.datetime.global_date_format(frappe.datetime.obj_to_user(events[i].ends_on)) %}
+                                    {%= frappe.datetime.get_time(events[i].ends_on) %}
+                                {% } else if (events[i].ends_on) { %}
+                                    -
+                                    {%= frappe.datetime.get_time(events[i].ends_on) %}
+                                {% } %}
+                            {% } %}
+
+                        </div>
+                    </div>
+                {% } %}
+            {% } else { %}
+            <div class="single-activity no-activity text-muted">
+                {{ __("No open event") }}
+            </div>
+		    {% } %}
+		</div>
+	</div>
+</div>
+
+
+<style>
+.open-activities {
+	min-height: 50px;
+	padding-left: 0px;
+	padding-bottom: 15px !important;
+}
+
+.open-activities .new-btn {
+	text-align: right;
+}
+
+.single-activity {
+	min-height: 90px;
+	border: 1px solid var(--border-color);
+	padding: 10px;
+	border-bottom: 0;
+	padding-right: 0;
+}
+
+.single-activity:last-child {
+	border-bottom: 1px solid var(--border-color);
+}
+
+.single-activity:hover .completion-checkbox{
+	display: block;
+}
+
+.completion-checkbox {
+	vertical-align: middle;
+	display: none;
+}
+
+.checkbox {
+	min-width: 22px;
+}
+
+.open-tasks {
+	width: 50%;
+}
+
+.open-tasks:first-child {
+	border-right: 0;
+}
+
+.open-events {
+	width: 50%;
+}
+
+.open-section-head {
+	background-color: var(--bg-color);
+	min-height: 30px;
+	border-bottom: 1px solid var(--border-color);
+	padding: 10px;
+	font-weight: bold;
+}
+
+.no-activity {
+    text-align: center;
+    padding-top: 30px;
+}
+
+.form-footer {
+	background-color: var(--bg-color);
+}
+</style>
\ No newline at end of file
diff --git a/erpnext/public/js/templates/crm_notes.html b/erpnext/public/js/templates/crm_notes.html
new file mode 100644
index 0000000..fddeb1c
--- /dev/null
+++ b/erpnext/public/js/templates/crm_notes.html
@@ -0,0 +1,74 @@
+<div class="notes-section col-xs-12">
+	<div class="new-btn pb-3">
+		<button class="btn btn-sm small new-note-btn mr-1">
+			<svg class="icon icon-sm">
+				<use href="#icon-add"></use>
+			</svg>
+			{{ __("New Note") }}
+		</button>
+	</div>
+	<div class="all-notes">
+		{% if (notes.length) { %}
+			{% for(var i=0, l=notes.length; i<l; i++) { %}
+				<div class="comment-content p-3 row" name="{{ notes[i].name }}">
+					<div class="mb-2 head col-xs-3">
+						<div class="row">
+							<div class="col-xs-2">
+								{{ frappe.avatar(notes[i].added_by) }}
+							</div>
+							<div class="col-xs-10">
+								<div class="mr-2 title font-weight-bold">
+									{{ strip_html(notes[i].added_by) }}
+								</div>
+								<div class="time small text-muted">
+									{{ frappe.datetime.global_date_format(notes[i].added_on) }}
+								</div>
+							</div>
+						</div>
+					</div>
+					<div class="content col-xs-8">
+						{{ notes[i].note }}
+					</div>
+					<div class="col-xs-1 text-right">
+						<span class="edit-note-btn btn btn-link">
+							<svg class="icon icon-sm"><use xlink:href="#icon-edit"></use></svg>
+						</span>
+						<span class="delete-note-btn  btn btn-link pl-2">
+							<svg class="icon icon-xs"><use xlink:href="#icon-delete"></use></svg>
+						</span>
+					</div>
+				</div>
+			{% } %}
+		{% } else { %}
+            <div class="no-activity text-muted pt-6">
+                {{ __("No Notes") }}
+            </div>
+		    {% } %}
+	</div>
+</div>
+
+<style>
+
+.comment-content {
+    border: 1px solid var(--border-color);
+	border-bottom: none;
+}
+
+.comment-content:last-child {
+    border-bottom: 1px solid var(--border-color);
+}
+
+.new-btn {
+	text-align: right;
+}
+
+.notes-section .no-activity {
+	min-height: 100px;
+	text-align: center;
+}
+
+.notes-section .btn {
+	padding: 0.2rem 0.2rem;
+}
+
+</style>
\ No newline at end of file
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/public/js/utils/crm_activities.js b/erpnext/public/js/utils/crm_activities.js
new file mode 100644
index 0000000..bbd9ded
--- /dev/null
+++ b/erpnext/public/js/utils/crm_activities.js
@@ -0,0 +1,234 @@
+erpnext.utils.CRMActivities = class CRMActivities {
+	constructor(opts) {
+		$.extend(this, opts);
+	}
+
+	refresh() {
+		var me = this;
+		$(this.open_activities_wrapper).empty();
+		let cur_form_footer = this.form_wrapper.find('.form-footer');
+
+		// all activities
+		if (!$(this.all_activities_wrapper).find('.form-footer').length) {
+			this.all_activities_wrapper.empty();
+			$(cur_form_footer).appendTo(this.all_activities_wrapper);
+
+			// remove frappe-control class to avoid absolute position for action-btn
+			$(this.all_activities_wrapper).removeClass('frappe-control');
+			// hide new event button
+			$('.timeline-actions').find('.btn-default').hide();
+			// hide new comment box
+			$(".comment-box").hide();
+			// show only communications by default
+			$($('.timeline-content').find('.nav-link')[0]).tab('show');
+		}
+
+		// open activities
+		frappe.call({
+			method: "erpnext.crm.utils.get_open_activities",
+			args: {
+				ref_doctype: this.frm.doc.doctype,
+				ref_docname: this.frm.doc.name
+			},
+			callback: (r) => {
+				if (!r.exc) {
+					var activities_html = frappe.render_template('crm_activities', {
+						tasks: r.message.tasks,
+						events: r.message.events
+					});
+
+					$(activities_html).appendTo(me.open_activities_wrapper);
+
+					$(".open-tasks").find(".completion-checkbox").on("click", function() {
+						me.update_status(this, "ToDo");
+					});
+
+					$(".open-events").find(".completion-checkbox").on("click", function() {
+						me.update_status(this, "Event");
+					});
+
+					me.create_task();
+					me.create_event();
+				}
+			}
+		});
+	}
+
+	create_task () {
+		let me = this;
+		let _create_task = () => {
+			const args = {
+				doc: me.frm.doc,
+				frm: me.frm,
+				title: __("New Task")
+			};
+			let composer = new frappe.views.InteractionComposer(args);
+			composer.dialog.get_field('interaction_type').set_value("ToDo");
+			// hide column having interaction type field
+			$(composer.dialog.get_field('interaction_type').wrapper).closest('.form-column').hide();
+			// hide summary field
+			$(composer.dialog.get_field('summary').wrapper).closest('.form-section').hide();
+		};
+		$(".new-task-btn").click(_create_task);
+	}
+
+	create_event () {
+		let me = this;
+		let _create_event = () => {
+			const args = {
+				doc: me.frm.doc,
+				frm: me.frm,
+				title: __("New Event")
+			};
+			let composer = new frappe.views.InteractionComposer(args);
+			composer.dialog.get_field('interaction_type').set_value("Event");
+			$(composer.dialog.get_field('interaction_type').wrapper).hide();
+		};
+		$(".new-event-btn").click(_create_event);
+	}
+
+	async update_status (input_field, doctype) {
+		let completed = $(input_field).prop("checked") ? 1 : 0;
+		let docname = $(input_field).attr("name");
+		if (completed) {
+			await frappe.db.set_value(doctype, docname, "status", "Closed");
+			this.refresh();
+		}
+	}
+};
+
+erpnext.utils.CRMNotes = class CRMNotes {
+	constructor(opts) {
+		$.extend(this, opts);
+	}
+
+	refresh() {
+		var me = this;
+		this.notes_wrapper.find('.notes-section').remove();
+
+		let notes = this.frm.doc.notes || [];
+		notes.sort(
+			function(a, b) {
+				return new Date(b.added_on) - new Date(a.added_on);
+			}
+		);
+
+		let notes_html = frappe.render_template(
+			'crm_notes',
+			{
+				notes: notes
+			}
+		);
+		$(notes_html).appendTo(this.notes_wrapper);
+
+		this.add_note();
+
+		$(".notes-section").find(".edit-note-btn").on("click", function() {
+			me.edit_note(this);
+		});
+
+		$(".notes-section").find(".delete-note-btn").on("click", function() {
+			me.delete_note(this);
+		});
+	}
+
+
+	add_note () {
+		let me = this;
+		let _add_note = () => {
+			var d = new frappe.ui.Dialog({
+				title: __('Add a Note'),
+				fields: [
+					{
+						"label": "Note",
+						"fieldname": "note",
+						"fieldtype": "Text Editor",
+						"reqd": 1
+					}
+				],
+				primary_action: function() {
+					var data = d.get_values();
+					frappe.call({
+						method: "add_note",
+						doc: me.frm.doc,
+						args: {
+							note: data.note
+						},
+						freeze: true,
+						callback: function(r) {
+							if (!r.exc) {
+								me.frm.refresh_field("notes");
+								me.refresh();
+							}
+							d.hide();
+						}
+					});
+				},
+				primary_action_label: __('Add')
+			});
+			d.show();
+		};
+		$(".new-note-btn").click(_add_note);
+	}
+
+	edit_note (edit_btn) {
+		var me = this;
+		let row = $(edit_btn).closest('.comment-content');
+		let row_id = row.attr("name");
+		let row_content = $(row).find(".content").html();
+		if (row_content) {
+			var d = new frappe.ui.Dialog({
+				title: __('Edit Note'),
+				fields: [
+					{
+						"label": "Note",
+						"fieldname": "note",
+						"fieldtype": "Text Editor",
+						"default": row_content
+					}
+				],
+				primary_action: function() {
+					var data = d.get_values();
+					frappe.call({
+						method: "edit_note",
+						doc: me.frm.doc,
+						args: {
+							note: data.note,
+							row_id: row_id
+						},
+						freeze: true,
+						callback: function(r) {
+							if (!r.exc) {
+								me.frm.refresh_field("notes");
+								me.refresh();
+								d.hide();
+							}
+
+						}
+					});
+				},
+				primary_action_label: __('Done')
+			});
+			d.show();
+		}
+	}
+
+	delete_note (delete_btn) {
+		var me = this;
+		let row_id = $(delete_btn).closest('.comment-content').attr("name");
+		frappe.call({
+			method: "delete_note",
+			doc: me.frm.doc,
+			args: {
+				row_id: row_id
+			},
+			freeze: true,
+			callback: function(r) {
+				if (!r.exc) {
+					me.frm.refresh_field("notes");
+					me.refresh();
+				}
+			}
+		});
+	}
+};
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index 2458756..7dc3fab 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -375,6 +375,12 @@
 	if not allowed_to_interact_with:
 		allowed_to_interact_with = represents_company
 
+	exisiting_representative = frappe.db.get_value(
+		"Customer", {"represents_company": represents_company}
+	)
+	if exisiting_representative:
+		return exisiting_representative
+
 	if not frappe.db.exists("Customer", customer_name):
 		customer = frappe.get_doc(
 			{
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 4fa4515..863fbc4 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -8,7 +8,6 @@
 from frappe.utils import flt, getdate, nowdate
 
 from erpnext.controllers.selling_controller import SellingController
-from erpnext.crm.utils import add_link_in_communication, copy_comments
 
 form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
 
@@ -36,16 +35,6 @@
 
 		make_packing_list(self)
 
-	def after_insert(self):
-		if frappe.db.get_single_value("CRM Settings", "carry_forward_communication_and_comments"):
-			if self.opportunity:
-				copy_comments("Opportunity", self.opportunity, self)
-				add_link_in_communication("Opportunity", self.opportunity, self)
-
-			elif self.quotation_to == "Lead" and self.party_name:
-				copy_comments("Lead", self.party_name, self)
-				add_link_in_communication("Lead", self.party_name, self)
-
 	def validate_valid_till(self):
 		if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
 			frappe.throw(_("Valid till date cannot be before transaction date"))
@@ -218,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:
@@ -233,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
@@ -249,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/selling/report/sales_order_analysis/sales_order_analysis.js b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
index 76a5bb5..91748bc 100644
--- a/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
+++ b/erpnext/selling/report/sales_order_analysis/sales_order_analysis.js
@@ -55,6 +55,7 @@
 				for (let option of status){
 					options.push({
 						"value": option,
+						"label": __(option),
 						"description": ""
 					})
 				}
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/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index fffcdca..6bcab73 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1064,6 +1064,33 @@
 
 		self.assertEqual(dn.items[0].rate, rate)
 
+	def test_internal_transfer_precision_gle(self):
+		from erpnext.selling.doctype.customer.test_customer import create_internal_customer
+
+		item = make_item(properties={"valuation_method": "Moving Average"}).name
+		company = "_Test Company with perpetual inventory"
+		warehouse = "Stores - TCP1"
+		target = "Finished Goods - TCP1"
+		customer = create_internal_customer(represents_company=company)
+
+		# average rate = 128.015
+		rates = [101.45, 150.46, 138.25, 121.9]
+
+		for rate in rates:
+			make_stock_entry(item_code=item, target=warehouse, qty=1, rate=rate)
+
+		dn = create_delivery_note(
+			item_code=item,
+			company=company,
+			customer=customer,
+			qty=4,
+			warehouse=warehouse,
+			target_warehouse=target,
+		)
+		self.assertFalse(
+			frappe.db.exists("GL Entry", {"voucher_no": dn.name, "voucher_type": dn.doctype})
+		)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index e902d1e..4b2850e 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -596,21 +596,6 @@
 					title=_("Insufficient Stock"),
 				)
 
-	def set_serial_nos(self, work_order):
-		previous_se = frappe.db.get_value(
-			"Stock Entry",
-			{"work_order": work_order, "purpose": "Material Transfer for Manufacture"},
-			"name",
-		)
-
-		for d in self.get("items"):
-			transferred_serial_no = frappe.db.get_value(
-				"Stock Entry Detail", {"parent": previous_se, "item_code": d.item_code}, "serial_no"
-			)
-
-			if transferred_serial_no:
-				d.serial_no = transferred_serial_no
-
 	@frappe.whitelist()
 	def get_stock_and_rate(self):
 		"""
@@ -1321,7 +1306,7 @@
 					and not self.pro_doc.skip_transfer
 					and self.flags.backflush_based_on == "Material Transferred for Manufacture"
 				):
-					self.get_transfered_raw_materials()
+					self.add_transfered_raw_materials_in_items()
 
 				elif (
 					self.work_order
@@ -1365,7 +1350,6 @@
 
 			# fetch the serial_no of the first stock entry for the second stock entry
 			if self.work_order and self.purpose == "Manufacture":
-				self.set_serial_nos(self.work_order)
 				work_order = frappe.get_doc("Work Order", self.work_order)
 				add_additional_cost(self, work_order)
 
@@ -1655,119 +1639,78 @@
 					}
 				)
 
-	def get_transfered_raw_materials(self):
-		transferred_materials = frappe.db.sql(
-			"""
-			select
-				item_name, original_item, item_code, sum(qty) as qty, sed.t_warehouse as warehouse,
-				description, stock_uom, expense_account, cost_center
-			from `tabStock Entry` se,`tabStock Entry Detail` sed
-			where
-				se.name = sed.parent and se.docstatus=1 and se.purpose='Material Transfer for Manufacture'
-				and se.work_order= %s and ifnull(sed.t_warehouse, '') != ''
-			group by sed.item_code, sed.t_warehouse
-		""",
+	def add_transfered_raw_materials_in_items(self) -> None:
+		available_materials = get_available_materials(self.work_order)
+
+		wo_data = frappe.db.get_value(
+			"Work Order",
 			self.work_order,
+			["qty", "produced_qty", "material_transferred_for_manufacturing as trans_qty"],
 			as_dict=1,
 		)
 
-		materials_already_backflushed = frappe.db.sql(
-			"""
-			select
-				item_code, sed.s_warehouse as warehouse, sum(qty) as qty
-			from
-				`tabStock Entry` se, `tabStock Entry Detail` sed
-			where
-				se.name = sed.parent and se.docstatus=1
-				and (se.purpose='Manufacture' or se.purpose='Material Consumption for Manufacture')
-				and se.work_order= %s and ifnull(sed.s_warehouse, '') != ''
-			group by sed.item_code, sed.s_warehouse
-		""",
-			self.work_order,
-			as_dict=1,
-		)
+		for key, row in available_materials.items():
+			remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
+			if remaining_qty_to_produce <= 0:
+				continue
 
-		backflushed_materials = {}
-		for d in materials_already_backflushed:
-			backflushed_materials.setdefault(d.item_code, []).append({d.warehouse: d.qty})
+			qty = (flt(row.qty) * flt(self.fg_completed_qty)) / remaining_qty_to_produce
 
-		po_qty = frappe.db.sql(
-			"""select qty, produced_qty, material_transferred_for_manufacturing from
-			`tabWork Order` where name=%s""",
-			self.work_order,
-			as_dict=1,
-		)[0]
-
-		manufacturing_qty = flt(po_qty.qty) or 1
-		produced_qty = flt(po_qty.produced_qty)
-		trans_qty = flt(po_qty.material_transferred_for_manufacturing) or 1
-
-		for item in transferred_materials:
-			qty = item.qty
-			item_code = item.original_item or item.item_code
-			req_items = frappe.get_all(
-				"Work Order Item",
-				filters={"parent": self.work_order, "item_code": item_code},
-				fields=["required_qty", "consumed_qty"],
-			)
-
-			req_qty = flt(req_items[0].required_qty) if req_items else flt(4)
-			req_qty_each = flt(req_qty / manufacturing_qty)
-			consumed_qty = flt(req_items[0].consumed_qty) if req_items else 0
-
-			if trans_qty and manufacturing_qty > (produced_qty + flt(self.fg_completed_qty)):
-				if qty >= req_qty:
-					qty = (req_qty / trans_qty) * flt(self.fg_completed_qty)
-				else:
-					qty = qty - consumed_qty
-
-				if self.purpose == "Manufacture":
-					# If Material Consumption is booked, must pull only remaining components to finish product
-					if consumed_qty != 0:
-						remaining_qty = consumed_qty - (produced_qty * req_qty_each)
-						exhaust_qty = req_qty_each * produced_qty
-						if remaining_qty > exhaust_qty:
-							if (remaining_qty / (req_qty_each * flt(self.fg_completed_qty))) >= 1:
-								qty = 0
-							else:
-								qty = (req_qty_each * flt(self.fg_completed_qty)) - remaining_qty
-					else:
-						if self.flags.backflush_based_on == "Material Transferred for Manufacture":
-							qty = (item.qty / trans_qty) * flt(self.fg_completed_qty)
-						else:
-							qty = req_qty_each * flt(self.fg_completed_qty)
-
-			elif backflushed_materials.get(item.item_code):
-				precision = frappe.get_precision("Stock Entry Detail", "qty")
-				for d in backflushed_materials.get(item.item_code):
-					if d.get(item.warehouse) > 0:
-						if qty > req_qty:
-							qty = (
-								(flt(qty, precision) - flt(d.get(item.warehouse), precision))
-								/ (flt(trans_qty, precision) - flt(produced_qty, precision))
-							) * flt(self.fg_completed_qty)
-
-							d[item.warehouse] -= qty
-
+			item = row.item_details
 			if cint(frappe.get_cached_value("UOM", item.stock_uom, "must_be_whole_number")):
 				qty = frappe.utils.ceil(qty)
 
-			if qty > 0:
-				self.add_to_stock_entry_detail(
-					{
-						item.item_code: {
-							"from_warehouse": item.warehouse,
-							"to_warehouse": "",
-							"qty": qty,
-							"item_name": item.item_name,
-							"description": item.description,
-							"stock_uom": item.stock_uom,
-							"expense_account": item.expense_account,
-							"cost_center": item.buying_cost_center,
-							"original_item": item.original_item,
-						}
-					}
-				)
+			if row.batch_details:
+				for batch_no, batch_qty in row.batch_details.items():
+					if qty <= 0 or batch_qty <= 0:
+						continue
+
+					if batch_qty > qty:
+						batch_qty = qty
+
+					item.batch_no = batch_no
+					self.update_item_in_stock_entry_detail(row, item, batch_qty)
+
+					row.batch_details[batch_no] -= batch_qty
+					qty -= batch_qty
+			else:
+				self.update_item_in_stock_entry_detail(row, item, qty)
+
+	def update_item_in_stock_entry_detail(self, row, item, qty) -> None:
+		ste_item_details = {
+			"from_warehouse": item.warehouse,
+			"to_warehouse": "",
+			"qty": qty,
+			"item_name": item.item_name,
+			"batch_no": item.batch_no,
+			"description": item.description,
+			"stock_uom": item.stock_uom,
+			"expense_account": item.expense_account,
+			"cost_center": item.buying_cost_center,
+			"original_item": item.original_item,
+		}
+
+		if row.serial_nos:
+			serial_nos = row.serial_nos
+			if item.batch_no:
+				serial_nos = self.get_serial_nos_based_on_transferred_batch(item.batch_no, row.serial_nos)
+
+			serial_nos = serial_nos[0 : cint(qty)]
+			ste_item_details["serial_no"] = "\n".join(serial_nos)
+
+			# remove consumed serial nos from list
+			for sn in serial_nos:
+				row.serial_nos.remove(sn)
+
+		self.add_to_stock_entry_detail({item.item_code: ste_item_details})
+
+	@staticmethod
+	def get_serial_nos_based_on_transferred_batch(batch_no, serial_nos) -> list:
+		serial_nos = frappe.get_all(
+			"Serial No", filters={"batch_no": batch_no, "name": ("in", serial_nos)}, order_by="creation"
+		)
+
+		return [d.name for d in serial_nos]
 
 	def get_pending_raw_materials(self, backflush_based_on=None):
 		"""
@@ -2528,3 +2471,81 @@
 		)
 
 	return supplied_item_details
+
+
+def get_available_materials(work_order) -> dict:
+	data = get_stock_entry_data(work_order)
+
+	available_materials = {}
+	for row in data:
+		key = (row.item_code, row.warehouse)
+		if row.purpose != "Material Transfer for Manufacture":
+			key = (row.item_code, row.s_warehouse)
+
+		if key not in available_materials:
+			available_materials.setdefault(
+				key,
+				frappe._dict(
+					{"item_details": row, "batch_details": defaultdict(float), "qty": 0, "serial_nos": []}
+				),
+			)
+
+		item_data = available_materials[key]
+
+		if row.purpose == "Material Transfer for Manufacture":
+			item_data.qty += row.qty
+			if row.batch_no:
+				item_data.batch_details[row.batch_no] += row.qty
+
+			if row.serial_no:
+				item_data.serial_nos.extend(get_serial_nos(row.serial_no))
+				item_data.serial_nos.sort()
+		else:
+			# Consume raw material qty in case of 'Manufacture' or 'Material Consumption for Manufacture'
+
+			item_data.qty -= row.qty
+			if row.batch_no:
+				item_data.batch_details[row.batch_no] -= row.qty
+
+			if row.serial_no:
+				for serial_no in get_serial_nos(row.serial_no):
+					item_data.serial_nos.remove(serial_no)
+
+	return available_materials
+
+
+def get_stock_entry_data(work_order):
+	stock_entry = frappe.qb.DocType("Stock Entry")
+	stock_entry_detail = frappe.qb.DocType("Stock Entry Detail")
+
+	return (
+		frappe.qb.from_(stock_entry)
+		.from_(stock_entry_detail)
+		.select(
+			stock_entry_detail.item_name,
+			stock_entry_detail.original_item,
+			stock_entry_detail.item_code,
+			stock_entry_detail.qty,
+			(stock_entry_detail.t_warehouse).as_("warehouse"),
+			(stock_entry_detail.s_warehouse).as_("s_warehouse"),
+			stock_entry_detail.description,
+			stock_entry_detail.stock_uom,
+			stock_entry_detail.expense_account,
+			stock_entry_detail.cost_center,
+			stock_entry_detail.batch_no,
+			stock_entry_detail.serial_no,
+			stock_entry.purpose,
+		)
+		.where(
+			(stock_entry.name == stock_entry_detail.parent)
+			& (stock_entry.work_order == work_order)
+			& (stock_entry.docstatus == 1)
+			& (stock_entry_detail.s_warehouse.isnotnull())
+			& (
+				stock_entry.purpose.isin(
+					["Manufacture", "Material Consumption for Manufacture", "Material Transfer for Manufacture"]
+				)
+			)
+		)
+		.orderby(stock_entry.creation, stock_entry_detail.item_code, stock_entry_detail.idx)
+	).run(as_dict=1)
diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py
index 4295188..48b4480 100644
--- a/erpnext/templates/utils.py
+++ b/erpnext/templates/utils.py
@@ -34,7 +34,6 @@
 			status="Open",
 			title=subject,
 			contact_email=sender,
-			to_discuss=message,
 		)
 	)
 
diff --git a/erpnext/translations/ru.csv b/erpnext/translations/ru.csv
index 743b294..a4bfb86 100644
--- a/erpnext/translations/ru.csv
+++ b/erpnext/translations/ru.csv
@@ -44,7 +44,7 @@
 Account,Аккаунт,
 Account Number,Номер аккаунта,
 Account Number {0} already used in account {1},"Номер счета {0}, уже использованный в учетной записи {1}",
-Account Pay Only,Счет Оплатить только,
+Account Pay Only,Только оплатить счет,
 Account Type,Тип учетной записи,
 Account Type for {0} must be {1},Тип счета для {0} должен быть {1},
 "Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'","Баланс счета в Кредите, запрещена установка 'Баланс должен быть' как 'Дебет'",
@@ -117,7 +117,7 @@
 Add Items,Добавить продукты,
 Add Leads,Добавить лид,
 Add Multiple Tasks,Добавить несколько задач,
-Add Row,Добавить ряд,
+Add Row,Добавить строку,
 Add Sales Partners,Добавить партнеров по продажам,
 Add Serial No,Добавить серийный номер,
 Add Students,Добавить студентов,
@@ -692,7 +692,7 @@
 Creating Company and Importing Chart of Accounts,Создание компании и импорт плана счетов,
 Creating Fees,Создание сборов,
 Creating Payment Entries......,Создание платежных записей......,
-Creating Salary Slips...,Создание зарплатных листков...,
+Creating Salary Slips...,Создание зарплатных ведомостей...,
 Creating student groups,Создание групп студентов,
 Creating {0} Invoice,Создание {0} счета,
 Credit,Кредит,
@@ -995,7 +995,7 @@
 Expenses Included In Asset Valuation,"Расходы, включенные в оценку активов",
 Expenses Included In Valuation,"Затрат, включаемых в оценке",
 Expired Batches,Просроченные партии,
-Expires On,Годен до,
+Expires On,Актуален до,
 Expiring On,Срок действия,
 Expiry (In Days),Срок действия (в днях),
 Explore,Обзор,
@@ -1411,7 +1411,7 @@
 Lab Tests and Vital Signs,Лабораторные тесты и жизненные знаки,
 Lab result datetime cannot be before testing datetime,Лабораторный результат datetime не может быть до тестирования даты и времени,
 Lab testing datetime cannot be before collection datetime,Лабораторное тестирование datetime не может быть до даты сбора данных,
-Label,Ярлык,
+Label,Метка,
 Laboratory,Лаборатория,
 Language Name,Название языка,
 Large,Большой,
@@ -2874,7 +2874,7 @@
 Supplier Invoice Date cannot be greater than Posting Date,"Дата Поставщик Счет не может быть больше, чем Дата публикации",
 Supplier Invoice No,Поставщик Счет №,
 Supplier Invoice No exists in Purchase Invoice {0},Номер счета поставщика отсутствует в счете на покупку {0},
-Supplier Name,наименование поставщика,
+Supplier Name,Наименование поставщика,
 Supplier Part No,Деталь поставщика №,
 Supplier Quotation,Предложение поставщика,
 Supplier Scorecard,Оценочная карта поставщика,
@@ -3091,7 +3091,7 @@
 Total Payments,Всего платежей,
 Total Present,Итого Текущая,
 Total Qty,Общее количество,
-Total Quantity,Общая численность,
+Total Quantity,Общее количество,
 Total Revenue,Общий доход,
 Total Student,Всего учеников,
 Total Target,Всего целей,
@@ -3498,7 +3498,7 @@
 Postal Code,Почтовый индекс,
 Previous,Предыдущая,
 Provider,Поставщик,
-Read Only,Только чтения,
+Read Only,Только чтение,
 Recipient,Сторона-реципиент,
 Reviews,Отзывы,
 Sender,Отправитель,
@@ -3879,7 +3879,7 @@
 On Supplier Creation,Создание поставщика,
 On Customer Creation,Создание клиента,
 Only .csv and .xlsx files are supported currently,В настоящее время поддерживаются только файлы .csv и .xlsx,
-Only expired allocation can be cancelled,Только истекшее распределение может быть отменено,
+Only expired allocation can be cancelled,Отменить можно только просроченное распределение,
 Only users with the {0} role can create backdated leave applications,Только пользователи с ролью {0} могут создавать оставленные приложения с задним сроком действия,
 Open,Открыт,
 Open Contact,Открытый контакт,
@@ -4046,7 +4046,7 @@
 Service Level Agreement has been changed to {0}.,Соглашение об уровне обслуживания изменено на {0}.,
 Service Level Agreement was reset.,Соглашение об уровне обслуживания было сброшено.,
 Service Level Agreement with Entity Type {0} and Entity {1} already exists.,Соглашение об уровне обслуживания с типом объекта {0} и объектом {1} уже существует.,
-Set,Задать,
+Set,Комплект,
 Set Meta Tags,Установить метатеги,
 Set {0} in company {1},Установить {0} в компании {1},
 Setup,Настройки,
@@ -4059,7 +4059,7 @@
 Show Warehouse-wise Stock,Показать складской запас,
 Size,Размер,
 Something went wrong while evaluating the quiz.,Что-то пошло не так при оценке теста.,
-Sr,Sr,
+Sr,№,
 Start,Начать,
 Start Date cannot be before the current date,Дата начала не может быть раньше текущей даты,
 Start Time,Время начала,
@@ -4513,7 +4513,7 @@
 Accounting Period,Период учета,
 Period Name,Название периода,
 Closed Documents,Закрытые документы,
-Accounts Settings,Настройки аккаунта,
+Accounts Settings,Настройка счетов,
 Settings for Accounts,Настройки для счетов,
 Make Accounting Entry For Every Stock Movement,Создавать бухгалтерские проводки при каждом перемещении запасов,
 Users with this role are allowed to set frozen accounts and create / modify accounting entries against frozen accounts,"Пользователи с этой ролью могут замороживать счета, а также создавать / изменять бухгалтерские проводки замороженных счетов",
@@ -5084,8 +5084,8 @@
 Item Tax Rate,Ставка налогов на продукт,
 Tax detail table fetched from item master as a string and stored in this field.\nUsed for Taxes and Charges,Налоговый Подробная таблица выбирается из мастера элемента в виде строки и хранится в этой области.\n Используется по налогам и сборам,
 Purchase Order Item,Заказ товара,
-Purchase Receipt Detail,Деталь квитанции о покупке,
-Item Weight Details,Деталь Вес Подробности,
+Purchase Receipt Detail,Сведения о квитанции о покупке,
+Item Weight Details,Сведения о весе товара,
 Weight Per Unit,Вес на единицу,
 Total Weight,Общий вес,
 Weight UOM,Вес Единица измерения,
@@ -5198,7 +5198,7 @@
 Contact List,Список контактов,
 Hidden list maintaining the list of contacts linked to Shareholder,"Скрытый список, поддерживающий список контактов, связанных с Акционером",
 Specify conditions to calculate shipping amount,Укажите условия для расчета суммы доставки,
-Shipping Rule Label,Название правила доставки,
+Shipping Rule Label,Метка правила доставки,
 example: Next Day Shipping,Пример: доставка на следующий день,
 Shipping Rule Type,Тип правила доставки,
 Shipping Account,Счет доставки,
@@ -5236,7 +5236,7 @@
 Billing Interval Count,Счет интервала фактурирования,
 "Number of intervals for the interval field e.g if Interval is 'Days' and Billing Interval Count is 3, invoices will be generated every 3 days","Количество интервалов для поля интервалов, например, если Interval является «Days», а количество интервалов фактурирования - 3, счета-фактуры будут генерироваться каждые 3 дня",
 Payment Plan,Платежный план,
-Subscription Plan Detail,Деталь плана подписки,
+Subscription Plan Detail,Сведения о плана подписки,
 Plan,План,
 Subscription Settings,Настройки подписки,
 Grace Period,Льготный период,
@@ -5802,7 +5802,7 @@
 Skip User creation for new Student,Пропустить создание пользователя для нового студента,
 "By default, a new User is created for every new Student. If enabled, no new User will be created when a new Student is created.","По умолчанию для каждого нового Студента создается новый Пользователь. Если этот параметр включен, при создании нового Студента новый Пользователь не создается.",
 Instructor Records to be created by,Записи инструкторов должны быть созданы,
-Employee Number,Общее число сотрудников,
+Employee Number,Номер сотрудника,
 Fee Category,Категория платы,
 Fee Component,Компонент платы,
 Fees Category,Категория плат,
@@ -6196,7 +6196,7 @@
 Occupancy Status,Статус занятости,
 Vacant,Вакантно,
 Occupied,Занято,
-Item Details,Детальная информация о товаре,
+Item Details,Детальная информация о продукте,
 UOM Conversion in Hours,Преобразование UOM в часы,
 Rate / UOM,Скорость / UOM,
 Change in Item,Изменение продукта,
@@ -6868,8 +6868,8 @@
 Create Separate Payment Entry Against Benefit Claim,Создать отдельную заявку на подачу заявки на получение пособия,
 Condition and Formula,Состояние и формула,
 Amount based on formula,Сумма на основе формулы,
-Formula,формула,
-Salary Detail,Заработная плата: Подробности,
+Formula,Формула,
+Salary Detail,Подробно об заработной плате,
 Component,Компонент,
 Do not include in total,Не включать в общей сложности,
 Default Amount,По умолчанию количество,
@@ -6891,7 +6891,7 @@
 Total Interest Amount,Общая сумма процентов,
 Total Loan Repayment,Общая сумма погашения кредита,
 net pay info,Чистая информация платить,
-Gross Pay - Total Deduction - Loan Repayment,Gross Pay - Итого Вычет - Погашение кредита,
+Gross Pay - Total Deduction - Loan Repayment,Валовая заработная плата - Общий вычет - Погашение кредита,
 Total in words,Всего в словах,
 Net Pay (in words) will be visible once you save the Salary Slip.,"Чистая плата (прописью) будет видна, как только вы сохраните зарплатную ведомость.",
 Salary Component for timesheet based payroll.,Компонент заработной платы для расчета зарплаты на основе расписания.,
@@ -6961,7 +6961,7 @@
 Attendees,Присутствующие,
 Employee Emails,Электронные почты сотрудников,
 Training Event Employee,Обучение сотрудников Событие,
-Invited,приглашенный,
+Invited,Приглашенный,
 Feedback Submitted,Отзыв отправлен,
 Optional,Необязательный,
 Training Result Employee,Результат обучения сотрудника,
@@ -7185,7 +7185,7 @@
 Item to be manufactured or repacked,Продукт должен быть произведен или переупакован,
 Quantity of item obtained after manufacturing / repacking from given quantities of raw materials,Количество пункта получены после изготовления / переупаковка от заданных величин сырья,
 Set rate of sub-assembly item based on BOM,Установить скорость сборки на основе спецификации,
-Allow Alternative Item,Разрешить альтернативный элемент,
+Allow Alternative Item,Разрешить альтернативный продукт,
 Item UOM,Единиц продукта,
 Conversion Rate,Коэффициент конверсии,
 Rate Of Materials Based On,Оценить материалов на основе,
@@ -7600,7 +7600,7 @@
 Import Supplier Invoice,Импортная накладная поставщика,
 Invoice Series,Серия счетов,
 Upload XML Invoices,Загрузить XML-счета,
-Zip File,Zip-файл,
+Zip File,Zip файл,
 Import Invoices,Импорт счетов,
 Click on Import Invoices button once the zip file has been attached to the document. Any errors related to processing will be shown in the Error Log.,"Нажмите кнопку «Импортировать счета-фактуры», когда файл zip прикреплен к документу. Любые ошибки, связанные с обработкой, будут отображаться в журнале ошибок.",
 Lower Deduction Certificate,Свидетельство о нижнем удержании,
@@ -7635,7 +7635,7 @@
 Served,Подается,
 Restaurant Reservation,Бронирование ресторанов,
 Waitlisted,Лист ожидания,
-No Show,Нет шоу,
+No Show,Не показывать,
 No of People,Нет людей,
 Reservation Time,Время резервирования,
 Reservation End Time,Время окончания бронирования,
@@ -7873,8 +7873,8 @@
 "If disable, 'In Words' field will not be visible in any transaction","Если отключить, &quot;В словах&quot; поле не будет видно в любой сделке",
 Item Classification,Продуктовая классификация,
 General Settings,Основные настройки,
-Item Group Name,Пункт Название группы,
-Parent Item Group,Родитель Пункт Группа,
+Item Group Name,Название группы продуктов,
+Parent Item Group,Родительская группа продукта,
 Item Group Defaults,Элемент группы по умолчанию,
 Item Tax,Налог на продукт,
 Check this if you want to show in website,"Проверьте это, если вы хотите показать в веб-сайт",
@@ -7971,13 +7971,13 @@
 Tariff Number,Тарифный номер,
 Delivery To,Доставка,
 MAT-DN-.YYYY.-,MAT-DN-.YYYY.-,
-Is Return,Является Вернуться,
+Is Return,Возврат,
 Issue Credit Note,Кредитная кредитная карта,
-Return Against Delivery Note,Вернуться На накладной,
-Customer's Purchase Order No,Клиентам Заказ Нет,
+Return Against Delivery Note,Возврат по накладной,
+Customer's Purchase Order No,Заказ клиента №,
 Billing Address Name,Название адреса для выставления счета,
 Required only for sample item.,Требуется только для образца пункта.,
-"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в шаблонах Налоги с налогами и сбором платежей, выберите его и нажмите кнопку ниже.",
+"If you have created a standard template in Sales Taxes and Charges Template, select one and click on the button below.","Если вы создали стандартный шаблон в Шаблоне налогов и сборов с продаж, выберите его и нажмите кнопку ниже.",
 In Words will be visible once you save the Delivery Note.,По словам будет виден только вы сохраните накладной.,
 In Words (Export) will be visible once you save the Delivery Note.,В Слов (Экспорт) будут видны только вы сохраните накладной.,
 Transporter Info,Информация для транспортировки,
@@ -7991,8 +7991,8 @@
 Excise Page Number,Количество Акцизный Страница,
 Instructions,Инструкции,
 From Warehouse,Со склада,
-Against Sales Order,По Сделке,
-Against Sales Order Item,По Продукту Сделки,
+Against Sales Order,По сделке,
+Against Sales Order Item,По позиции сделки,
 Against Sales Invoice,Повторная накладная,
 Against Sales Invoice Item,Счет на продажу продукта,
 Available Batch Qty at From Warehouse,Доступные Пакетная Кол-во на со склада,
@@ -8008,7 +8008,7 @@
 Lock,Заблокировано,
 Visited,Посещен,
 Order Information,запросить информацию,
-Contact Information,Контакты,
+Contact Information,Контактная информация,
 Email sent to,Письмо отправлено,
 Dispatch Information,Информация о доставке,
 Estimated Arrival,Ожидаемое прибытие,
@@ -8121,7 +8121,7 @@
 Alternative Item Name,Альтернативное название продукта,
 Attribute Name,Название атрибута,
 Numeric Values,Числовые значения,
-From Range,От хребта,
+From Range,Из диапазона,
 Increment,Приращение,
 To Range,В диапазоне,
 Item Attribute Values,Пункт значений атрибутов,
@@ -8143,7 +8143,7 @@
 Default Expense Account,Счет учета затрат по умолчанию,
 Sales Defaults,По умолчанию,
 Default Selling Cost Center,По умолчанию Продажа Стоимость центр,
-Item Manufacturer,Пункт Производитель,
+Item Manufacturer,Производитель товара,
 Item Price,Цена продукта,
 Packing Unit,Упаковочный блок,
 Quantity  that must be bought or sold per UOM,"Количество, которое необходимо купить или продать за UOM",
@@ -8177,7 +8177,7 @@
 Purchase Receipt Items,Покупка продуктов,
 Get Items From Purchase Receipts,Получить продукты из покупки.,
 Distribute Charges Based On,Распределите платежи на основе,
-Landed Cost Help,Земельные Стоимость Помощь,
+Landed Cost Help,Справка по стоимости доставки,
 Manufacturers used in Items,Производители использовали в пунктах,
 Limited to 12 characters,Ограничено до 12 символов,
 MAT-MR-.YYYY.-,МАТ-MR-.YYYY.-,
@@ -8186,13 +8186,13 @@
 % Ordered,% заказано,
 Terms and Conditions Content,Условия Содержимое,
 Quantity and Warehouse,Количество и Склад,
-Lead Time Date,Время и Дата Лида,
-Min Order Qty,Минимальный заказ Кол-во,
+Lead Time Date,Дата выполнения заказа,
+Min Order Qty,Минимальное количество для заказа,
 Packed Item,Упаковано,
 To Warehouse (Optional),На склад (Необязательно),
 Actual Batch Quantity,Фактическое количество партий,
 Prevdoc DocType,Prevdoc DocType,
-Parent Detail docname,Родитель Деталь DOCNAME,
+Parent Detail docname,Сведения о родителе docname,
 "Generate packing slips for packages to be delivered. Used to notify package number, package contents and its weight.","Создаёт упаковочные листы к упаковкам для доставки. Содержит номер упаковки, перечень содержимого и вес.",
 Indicates that the package is a part of this delivery (Only Draft),"Указывает, что пакет является частью этой поставки (только проект)",
 MAT-PAC-.YYYY.-,MAT-PAC-.YYYY.-,
@@ -8353,7 +8353,7 @@
 Auto Material Request,Автоматический запрос материалов,
 Inter Warehouse Transfer Settings,Настройки передачи между складами,
 Freeze Stock Entries,Замораживание поступления запасов,
-Stock Frozen Upto,остатки заморожены до,
+Stock Frozen Upto,Остатки заморожены до,
 Batch Identification,Идентификация партии,
 Use Naming Series,Использовать серийный номер,
 Naming Series Prefix,Префикс Идентификации по Имени,
@@ -8372,7 +8372,7 @@
 Service Level,Уровень обслуживания,
 Response By,Ответ от,
 Response By Variance,Ответ по отклонениям,
-Ongoing,постоянный,
+Ongoing,Постоянный,
 Resolution By,Разрешение по,
 Resolution By Variance,Разрешение по отклонениям,
 Service Level Agreement Creation,Создание соглашения об уровне обслуживания,
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/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 73cbcd4..cd1bf9f 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -5,7 +5,7 @@
 import frappe
 import frappe.share
 from frappe import _
-from frappe.utils import cint, cstr, flt, get_time, now_datetime
+from frappe.utils import cint, flt, get_time, now_datetime
 
 from erpnext.controllers.status_updater import StatusUpdater
 
@@ -30,64 +30,6 @@
 			except ValueError:
 				frappe.throw(_("Invalid Posting Time"))
 
-	def add_calendar_event(self, opts, force=False):
-		if (
-			cstr(self.contact_by) != cstr(self._prev.contact_by)
-			or cstr(self.contact_date) != cstr(self._prev.contact_date)
-			or force
-			or (hasattr(self, "ends_on") and cstr(self.ends_on) != cstr(self._prev.ends_on))
-		):
-
-			self.delete_events()
-			self._add_calendar_event(opts)
-
-	def delete_events(self):
-		participations = frappe.get_all(
-			"Event Participants",
-			filters={
-				"reference_doctype": self.doctype,
-				"reference_docname": self.name,
-				"parenttype": "Event",
-			},
-			fields=["name", "parent"],
-		)
-
-		if participations:
-			for participation in participations:
-				total_participants = frappe.get_all(
-					"Event Participants", filters={"parenttype": "Event", "parent": participation.parent}
-				)
-
-				if len(total_participants) <= 1:
-					frappe.db.sql("delete from `tabEvent` where name='%s'" % participation.parent)
-
-				frappe.db.sql("delete from `tabEvent Participants` where name='%s'" % participation.name)
-
-	def _add_calendar_event(self, opts):
-		opts = frappe._dict(opts)
-
-		if self.contact_date:
-			event = frappe.get_doc(
-				{
-					"doctype": "Event",
-					"owner": opts.owner or self.owner,
-					"subject": opts.subject,
-					"description": opts.description,
-					"starts_on": self.contact_date,
-					"ends_on": opts.ends_on,
-					"event_type": "Private",
-				}
-			)
-
-			event.append(
-				"event_participants", {"reference_doctype": self.doctype, "reference_docname": self.name}
-			)
-
-			event.insert(ignore_permissions=True)
-
-			if frappe.db.exists("User", self.contact_by):
-				frappe.share.add("Event", event.name, self.contact_by, flags={"ignore_share_permission": True})
-
 	def validate_uom_is_integer(self, uom_field, qty_fields):
 		validate_uom_is_integer(self, uom_field, qty_fields)
 
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()