Merge branch 'develop' of https://github.com/frappe/erpnext into tds_validity
diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py
index 91983d3..378983e 100644
--- a/.github/helper/documentation.py
+++ b/.github/helper/documentation.py
@@ -24,6 +24,8 @@
 					parts = parsed_url.path.split('/')
 					if len(parts) == 5 and parts[1] == "frappe" and parts[2] in docs_repos:
 						return True
+				elif parsed_url.netloc == "docs.erpnext.com":
+					return True
 
 
 if __name__ == "__main__":
diff --git a/erpnext/accounts/report/utils.py b/erpnext/accounts/report/utils.py
index 57ff9b0..2f9e957 100644
--- a/erpnext/accounts/report/utils.py
+++ b/erpnext/accounts/report/utils.py
@@ -100,15 +100,15 @@
 			if entry.get('credit'):
 				entry['credit'] = credit_in_account_currency
 		else:
-			value = debit or credit
 			date = currency_info['report_date']
-			converted_value = convert(value, presentation_currency, company_currency, date)
+			converted_debit_value = convert(debit, presentation_currency, company_currency, date)
+			converted_credit_value = convert(credit, presentation_currency, company_currency, date)
 
 			if entry.get('debit'):
-				entry['debit'] = converted_value
+				entry['debit'] = converted_debit_value
 
 			if entry.get('credit'):
-				entry['credit'] = converted_value
+				entry['credit'] = converted_credit_value
 
 		converted_gl_list.append(entry)
 
diff --git a/erpnext/accounts/test/test_utils.py b/erpnext/accounts/test/test_utils.py
index d7b60da..c3f6d27 100644
--- a/erpnext/accounts/test/test_utils.py
+++ b/erpnext/accounts/test/test_utils.py
@@ -5,21 +5,48 @@
 from frappe.test_runner import make_test_objects
 
 from erpnext.accounts.party import get_party_shipping_address
+from erpnext.accounts.utils import get_future_stock_vouchers, get_voucherwise_gl_entries
+from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 
 
 class TestUtils(unittest.TestCase):
 	@classmethod
 	def setUpClass(cls):
 		super(TestUtils, cls).setUpClass()
-		make_test_objects('Address', ADDRESS_RECORDS)
+		make_test_objects("Address", ADDRESS_RECORDS)
 
 	def test_get_party_shipping_address(self):
-		address = get_party_shipping_address('Customer', '_Test Customer 1')
-		self.assertEqual(address, '_Test Billing Address 2 Title-Billing')
+		address = get_party_shipping_address("Customer", "_Test Customer 1")
+		self.assertEqual(address, "_Test Billing Address 2 Title-Billing")
 
 	def test_get_party_shipping_address2(self):
-		address = get_party_shipping_address('Customer', '_Test Customer 2')
-		self.assertEqual(address, '_Test Shipping Address 2 Title-Shipping')
+		address = get_party_shipping_address("Customer", "_Test Customer 2")
+		self.assertEqual(address, "_Test Shipping Address 2 Title-Shipping")
+
+	def test_get_voucher_wise_gl_entry(self):
+
+		pr = make_purchase_receipt(
+			item_code="_Test Item",
+			posting_date="2021-02-01",
+			rate=100,
+			qty=1,
+			warehouse="Stores - TCP1",
+			company="_Test Company with perpetual inventory",
+		)
+
+		future_vouchers = get_future_stock_vouchers("2021-01-01", "00:00:00", for_items=["_Test Item"])
+
+		voucher_type_and_no = ("Purchase Receipt", pr.name)
+		self.assertTrue(
+			voucher_type_and_no in future_vouchers,
+			msg="get_future_stock_vouchers not returning correct value",
+		)
+
+		posting_date = "2021-01-01"
+		gl_entries = get_voucherwise_gl_entries(future_vouchers, posting_date)
+		self.assertTrue(
+			voucher_type_and_no in gl_entries, msg="get_voucherwise_gl_entries not returning expected GLes",
+		)
 
 
 ADDRESS_RECORDS = [
@@ -31,12 +58,8 @@
 		"city": "Lagos",
 		"country": "Nigeria",
 		"links": [
-			{
-				"link_doctype": "Customer",
-				"link_name": "_Test Customer 2",
-				"doctype": "Dynamic Link"
-			}
-		]
+			{"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"}
+		],
 	},
 	{
 		"doctype": "Address",
@@ -46,12 +69,8 @@
 		"city": "Lagos",
 		"country": "Nigeria",
 		"links": [
-			{
-				"link_doctype": "Customer",
-				"link_name": "_Test Customer 2",
-				"doctype": "Dynamic Link"
-			}
-		]
+			{"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"}
+		],
 	},
 	{
 		"doctype": "Address",
@@ -62,12 +81,8 @@
 		"country": "Nigeria",
 		"is_shipping_address": "1",
 		"links": [
-			{
-				"link_doctype": "Customer",
-				"link_name": "_Test Customer 2",
-				"doctype": "Dynamic Link"
-			}
-		]
+			{"link_doctype": "Customer", "link_name": "_Test Customer 2", "doctype": "Dynamic Link"}
+		],
 	},
 	{
 		"doctype": "Address",
@@ -78,11 +93,7 @@
 		"country": "Nigeria",
 		"is_shipping_address": "1",
 		"links": [
-			{
-				"link_doctype": "Customer",
-				"link_name": "_Test Customer 1",
-				"doctype": "Dynamic Link"
-			}
-		]
-	}
+			{"link_doctype": "Customer", "link_name": "_Test Customer 1", "doctype": "Dynamic Link"}
+		],
+	},
 ]
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 4692869..fbad171 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -963,6 +963,9 @@
 
 	Only fetches GLE fields required for comparing with new GLE.
 	Check compare_existing_and_expected_gle function below.
+
+	returns:
+		Dict[Tuple[voucher_type, voucher_no], List[GL Entries]]
 	"""
 	gl_entries = {}
 	if not future_stock_vouchers:
@@ -971,7 +974,7 @@
 	voucher_nos = [d[1] for d in future_stock_vouchers]
 
 	gles = frappe.db.sql("""
-		select name, account, credit, debit, cost_center, project
+		select name, account, credit, debit, cost_center, project, voucher_type, voucher_no
 			from `tabGL Entry`
 		where
 			posting_date >= %s and voucher_no in (%s)""" %
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 3866fc2..f8376e6 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -132,10 +132,43 @@
 		}
 	},
 
+	currency: function(frm) {
+		let company_currency = erpnext.get_currency(frm.doc.company);
+		if (company_currency != frm.doc.company) {
+			frappe.call({
+				method: "erpnext.setup.utils.get_exchange_rate",
+				args: {
+					from_currency: frm.doc.currency,
+					to_currency: company_currency
+				},
+				callback: function(r) {
+					if (r.message) {
+						frm.set_value('conversion_rate', flt(r.message));
+						frm.set_df_property('conversion_rate', 'description', '1 ' + frm.doc.currency
+						+ ' = [?] ' + company_currency);
+					}
+				}
+			});
+		} else {
+			frm.set_value('conversion_rate', 1.0);
+			frm.set_df_property('conversion_rate', 'hidden', 1);
+			frm.set_df_property('conversion_rate', 'description', '');
+		}
+
+		frm.trigger('opportunity_amount');
+		frm.trigger('set_dynamic_field_label');
+	},
+
+	opportunity_amount: function(frm) {
+		frm.set_value('base_opportunity_amount', flt(frm.doc.opportunity_amount) * flt(frm.doc.conversion_rate));
+	},
+
 	set_dynamic_field_label: function(frm){
 		if (frm.doc.opportunity_from) {
 			frm.set_df_property("party_name", "label", frm.doc.opportunity_from);
 		}
+		frm.trigger('change_grid_labels');
+		frm.trigger('change_form_labels');
 	},
 
 	make_supplier_quotation: function(frm) {
@@ -152,6 +185,62 @@
 		})
 	},
 
+	change_form_labels: function(frm) {
+		let company_currency = erpnext.get_currency(frm.doc.company);
+		frm.set_currency_labels(["base_opportunity_amount", "base_total", "base_grand_total"], company_currency);
+		frm.set_currency_labels(["opportunity_amount", "total", "grand_total"], frm.doc.currency);
+
+		// toggle fields
+		frm.toggle_display(["conversion_rate", "base_opportunity_amount", "base_total", "base_grand_total"],
+			frm.doc.currency != company_currency);
+	},
+
+	change_grid_labels: function(frm) {
+		let company_currency = erpnext.get_currency(frm.doc.company);
+		frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "items");
+		frm.set_currency_labels(["rate", "amount"], frm.doc.currency, "items");
+
+		let item_grid = frm.fields_dict.items.grid;
+		$.each(["base_rate", "base_amount"], function(i, fname) {
+			if(frappe.meta.get_docfield(item_grid.doctype, fname))
+				item_grid.set_column_disp(fname, frm.doc.currency != company_currency);
+		});
+		frm.refresh_fields();
+	},
+
+	calculate_total: function(frm) {
+		let total = 0, base_total = 0, grand_total = 0, base_grand_total = 0;
+		frm.doc.items.forEach(item => {
+			total += item.amount;
+			base_total += item.base_amount;
+		})
+
+		base_grand_total = base_total + frm.doc.base_opportunity_amount;
+		grand_total = total + frm.doc.opportunity_amount;
+
+		frm.set_value({
+			'total': flt(total),
+			'base_total': flt(base_total),
+			'grand_total': flt(grand_total),
+			'base_grand_total': flt(base_grand_total)
+		});
+	}
+
+});
+frappe.ui.form.on("Opportunity Item", {
+	calculate: function(frm, cdt, cdn) {
+		let row = frappe.get_doc(cdt, cdn);
+		frappe.model.set_value(cdt, cdn, "amount", flt(row.qty) * flt(row.rate));
+		frappe.model.set_value(cdt, cdn, "base_rate", flt(frm.doc.conversion_rate) * flt(row.rate));
+		frappe.model.set_value(cdt, cdn, "base_amount", flt(frm.doc.conversion_rate) * flt(row.amount));
+		frm.trigger("calculate_total");
+	},
+	qty: function(frm, cdt, cdn) {
+		frm.trigger("calculate", cdt, cdn);
+	},
+	rate: function(frm, cdt, cdn) {
+		frm.trigger("calculate", cdt, cdn);
+	}
 })
 
 // TODO commonify this code
@@ -169,6 +258,7 @@
 		}
 
 		this.setup_queries();
+		this.frm.trigger('currency');
 	}
 
 	setup_queries() {
diff --git a/erpnext/crm/doctype/opportunity/opportunity.json b/erpnext/crm/doctype/opportunity/opportunity.json
index 12a564a9..dc886b5 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.json
+++ b/erpnext/crm/doctype/opportunity/opportunity.json
@@ -33,12 +33,20 @@
   "to_discuss",
   "section_break_14",
   "currency",
-  "opportunity_amount",
+  "conversion_rate",
+  "base_opportunity_amount",
   "with_items",
   "column_break_17",
   "probability",
+  "opportunity_amount",
   "items_section",
   "items",
+  "section_break_32",
+  "base_total",
+  "base_grand_total",
+  "column_break_33",
+  "total",
+  "grand_total",
   "contact_info",
   "customer_address",
   "address_display",
@@ -425,12 +433,65 @@
    "fieldtype": "Link",
    "label": "Print Language",
    "options": "Language"
+  },
+  {
+   "fieldname": "base_opportunity_amount",
+   "fieldtype": "Currency",
+   "label": "Opportunity Amount (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "depends_on": "with_items",
+   "fieldname": "section_break_32",
+   "fieldtype": "Section Break",
+   "hide_border": 1
+  },
+  {
+   "fieldname": "base_total",
+   "fieldtype": "Currency",
+   "label": "Total (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "total",
+   "fieldtype": "Currency",
+   "label": "Total",
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "conversion_rate",
+   "fieldtype": "Float",
+   "label": "Exchange Rate"
+  },
+  {
+   "fieldname": "column_break_33",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "base_grand_total",
+   "fieldtype": "Currency",
+   "label": "Grand Total (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "grand_total",
+   "fieldtype": "Currency",
+   "label": "Grand Total",
+   "options": "currency",
+   "read_only": 1
   }
  ],
  "icon": "fa fa-info-sign",
  "idx": 195,
  "links": [],
- "modified": "2021-08-25 10:28:24.923543",
+ "modified": "2021-09-06 10:02:18.609136",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Opportunity",
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 0b3f508..be843a3 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -9,9 +9,8 @@
 from frappe import _
 from frappe.email.inbox import link_communication_to_document
 from frappe.model.mapper import get_mapped_doc
-from frappe.utils import cint, cstr, get_fullname
+from frappe.utils import cint, cstr, flt, get_fullname
 
-from erpnext.accounts.party import get_party_account_currency
 from erpnext.setup.utils import get_exchange_rate
 from erpnext.utilities.transaction_base import TransactionBase
 
@@ -41,6 +40,23 @@
 		if not self.with_items:
 			self.items = []
 
+		else:
+			self.calculate_totals()
+
+	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)
+			total += item.amount
+			base_total += item.base_amount
+
+		self.total = flt(total)
+		self.base_total = flt(base_total)
+		self.grand_total = flt(self.total) + flt(self.opportunity_amount)
+		self.base_grand_total = flt(self.base_total) + flt(self.base_opportunity_amount)
+
 	def make_new_lead_if_required(self):
 		"""Set lead against new opportunity"""
 		if (not self.get("party_name")) and self.contact_email:
@@ -224,13 +240,6 @@
 
 		company_currency = frappe.get_cached_value('Company',  quotation.company,  "default_currency")
 
-		if quotation.quotation_to == 'Customer' and quotation.party_name:
-			party_account_currency = get_party_account_currency("Customer", quotation.party_name, quotation.company)
-		else:
-			party_account_currency = company_currency
-
-		quotation.currency = party_account_currency or company_currency
-
 		if company_currency == quotation.currency:
 			exchange_rate = 1
 		else:
@@ -254,7 +263,7 @@
 			"doctype": "Quotation",
 			"field_map": {
 				"opportunity_from": "quotation_to",
-				"name": "enq_no",
+				"name": "enq_no"
 			}
 		},
 		"Opportunity Item": {
diff --git a/erpnext/crm/doctype/opportunity/test_opportunity.py b/erpnext/crm/doctype/opportunity/test_opportunity.py
index 347bf63..65d6cb3 100644
--- a/erpnext/crm/doctype/opportunity/test_opportunity.py
+++ b/erpnext/crm/doctype/opportunity/test_opportunity.py
@@ -63,6 +63,10 @@
 		self.assertEqual(opp_doc.opportunity_from, "Customer")
 		self.assertEqual(opp_doc.party_name, customer.name)
 
+	def test_opportunity_item(self):
+		opportunity_doc = make_opportunity(with_items=1, rate=1100, qty=2)
+		self.assertEqual(opportunity_doc.total, 2200)
+
 def make_opportunity(**args):
 	args = frappe._dict(args)
 
@@ -71,6 +75,7 @@
 		"company": args.company or "_Test Company",
 		"opportunity_from": args.opportunity_from or "Customer",
 		"opportunity_type": "Sales",
+		"conversion_rate": 1.0,
 		"with_items": args.with_items or 0,
 		"transaction_date": today()
 	})
@@ -85,6 +90,7 @@
 		opp_doc.append('items', {
 			"item_code": args.item_code or "_Test Item",
 			"qty": args.qty or 1,
+			"rate": args.rate or 1000,
 			"uom": "_Test UOM"
 		})
 
diff --git a/erpnext/crm/doctype/opportunity_item/opportunity_item.json b/erpnext/crm/doctype/opportunity_item/opportunity_item.json
index 65e8433..1b4973c 100644
--- a/erpnext/crm/doctype/opportunity_item/opportunity_item.json
+++ b/erpnext/crm/doctype/opportunity_item/opportunity_item.json
@@ -1,469 +1,177 @@
 {
- "allow_copy": 0, 
- "allow_events_in_timeline": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2013-02-22 01:27:51", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2013-02-22 01:27:51",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_code",
+  "item_name",
+  "col_break1",
+  "uom",
+  "qty",
+  "section_break_6",
+  "brand",
+  "item_group",
+  "description",
+  "column_break_8",
+  "image",
+  "image_view",
+  "quantity_and_rate_section",
+  "base_rate",
+  "base_amount",
+  "column_break_16",
+  "rate",
+  "amount"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "item_code", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Item Code", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "item_code", 
-   "oldfieldtype": "Link", 
-   "options": "Item", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item Code",
+   "oldfieldname": "item_code",
+   "oldfieldtype": "Link",
+   "options": "Item"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "col_break1", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "col_break1",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "qty", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Qty", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "qty", 
-   "oldfieldtype": "Currency", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "default": "1",
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Qty",
+   "oldfieldname": "qty",
+   "oldfieldtype": "Currency"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "description": "", 
-   "fieldname": "item_group", 
-   "fieldtype": "Link", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Item Group", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "item_group", 
-   "oldfieldtype": "Link", 
-   "options": "Item Group", 
-   "permlevel": 0, 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Item Group",
+   "oldfieldname": "item_group",
+   "oldfieldtype": "Link",
+   "options": "Item Group",
+   "print_hide": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "brand", 
-   "fieldtype": "Link", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Brand", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "brand", 
-   "oldfieldtype": "Link", 
-   "options": "Brand", 
-   "permlevel": 0, 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "brand",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Brand",
+   "oldfieldname": "brand",
+   "oldfieldtype": "Link",
+   "options": "Brand",
+   "print_hide": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_6", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "collapsible": 1,
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break",
+   "label": "Description"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "uom", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "UOM", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "uom", 
-   "oldfieldtype": "Link", 
-   "options": "UOM", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "label": "UOM",
+   "oldfieldname": "uom",
+   "oldfieldtype": "Link",
+   "options": "UOM"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "item_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 1, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Item Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "item_name", 
-   "oldfieldtype": "Data", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "in_global_search": 1,
+   "label": "Item Name",
+   "oldfieldname": "item_name",
+   "oldfieldtype": "Data"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "description", 
-   "oldfieldtype": "Text", 
-   "permlevel": 0, 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "print_width": "300px", 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0, 
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Description",
+   "oldfieldname": "description",
+   "oldfieldtype": "Text",
+   "print_width": "300px",
    "width": "300px"
-  }, 
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_8", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_8",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "image", 
-   "fieldtype": "Attach", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Image", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "image",
+   "fieldtype": "Attach",
+   "hidden": 1,
+   "label": "Image"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "image_view", 
-   "fieldtype": "Image", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Image View", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "image", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "image_view",
+   "fieldtype": "Image",
+   "label": "Image View",
+   "options": "image",
+   "print_hide": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "basic_rate", 
-   "fieldtype": "Currency", 
-   "hidden": 1, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Basic Rate", 
-   "length": 0, 
-   "no_copy": 0, 
-   "oldfieldname": "basic_rate", 
-   "oldfieldtype": "Currency", 
-   "options": "Company:company:default_currency", 
-   "permlevel": 0, 
-   "print_hide": 1, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "fieldname": "rate",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Rate",
+   "options": "currency",
+   "reqd": 1
+  },
+  {
+   "fieldname": "quantity_and_rate_section",
+   "fieldtype": "Section Break",
+   "label": "Quantity and Rate"
+  },
+  {
+   "fieldname": "base_amount",
+   "fieldtype": "Currency",
+   "label": "Amount (Company Currency)",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "column_break_16",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Amount",
+   "options": "currency",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "base_rate",
+   "fieldtype": "Currency",
+   "label": "Rate (Company Currency)",
+   "oldfieldname": "basic_rate",
+   "oldfieldtype": "Currency",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1,
+   "reqd": 1
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 1, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-12-28 15:43:09.382012", 
- "modified_by": "Administrator", 
- "module": "CRM", 
- "name": "Opportunity Item", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 0, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-07-30 16:39:09.775720",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Opportunity Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/__init__.py
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
new file mode 100644
index 0000000..116db2f
--- /dev/null
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.js
@@ -0,0 +1,65 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Opportunity Summary by Sales Stage"] = {
+	"filters": [
+		{
+			fieldname: "based_on",
+			label: __("Based On"),
+			fieldtype: "Select",
+			options: "Opportunity Owner\nSource\nOpportunity Type",
+			default: "Opportunity Owner"
+		},
+		{
+			fieldname: "data_based_on",
+			label: __("Data Based On"),
+			fieldtype: "Select",
+			options: "Number\nAmount",
+			default: "Number"
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+
+		},
+		{
+			fieldname: "to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+		},
+		{
+			fieldname: "status",
+			label: __("Status"),
+			fieldtype: "MultiSelectList",
+			get_data: function() {
+				return [
+					{value: "Open", description: "Status"},
+					{value: "Converted", description: "Status"},
+					{value: "Quotation", description: "Status"},
+					{value: "Replied", description: "Status"}
+				]
+			}
+		},
+		{
+			fieldname: "opportunity_source",
+			label: __("Oppoturnity Source"),
+			fieldtype: "Link",
+			options: "Lead Source",
+		},
+		{
+			fieldname: "opportunity_type",
+			label: __("Opportunity Type"),
+			fieldtype: "Link",
+			options: "Opportunity Type",
+		},
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company")
+		}
+	]
+};
\ No newline at end of file
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json
new file mode 100644
index 0000000..3605aec
--- /dev/null
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-07-28 12:18:24.028737",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-07-28 12:18:24.028737",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Opportunity Summary by Sales Stage",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Opportunity",
+ "report_name": "Opportunity Summary by Sales Stage ",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Sales User"
+  },
+  {
+   "role": "Sales Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
new file mode 100644
index 0000000..4cff13f
--- /dev/null
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/opportunity_summary_by_sales_stage.py
@@ -0,0 +1,254 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+import json
+
+import frappe
+import pandas
+from frappe import _
+from frappe.utils import flt
+from six import iteritems
+
+from erpnext.setup.utils import get_exchange_rate
+
+
+def execute(filters=None):
+	return OpportunitySummaryBySalesStage(filters).run()
+
+class OpportunitySummaryBySalesStage(object):
+	def __init__(self,filters=None):
+		self.filters = frappe._dict(filters or {})
+
+	def run(self):
+		self.get_columns()
+		self.get_data()
+		self.get_chart_data()
+		return self.columns, self.data, None, self.chart
+
+	def get_columns(self):
+		self.columns = []
+
+		if self.filters.get('based_on') == 'Opportunity Owner':
+			self.columns.append({
+				'label': _('Opportunity Owner'),
+				'fieldname': 'opportunity_owner',
+				'width': 200
+			})
+
+		if self.filters.get('based_on') == 'Source':
+			self.columns.append({
+				'label': _('Source'),
+				'fieldname': 'source',
+				'fieldtype': 'Link',
+				'options': 'Lead Source',
+				'width': 200
+			})
+
+		if self.filters.get('based_on') == 'Opportunity Type':
+			self.columns.append({
+				'label': _('Opportunity Type'),
+				'fieldname': 'opportunity_type',
+				'width': 200
+			})
+
+		self.set_sales_stage_columns()
+
+	def set_sales_stage_columns(self):
+		self.sales_stage_list = frappe.db.get_list('Sales Stage', pluck='name')
+
+		for sales_stage in self.sales_stage_list:
+			if self.filters.get('data_based_on') == 'Number':
+				self.columns.append({
+					'label': _(sales_stage),
+					'fieldname': sales_stage,
+					'fieldtype': 'Int',
+					'width': 150
+				})
+
+			elif self.filters.get('data_based_on') == 'Amount':
+				self.columns.append({
+					'label': _(sales_stage),
+					'fieldname': sales_stage,
+					'fieldtype': 'Currency',
+					'width': 150
+				})
+
+	def get_data(self):
+		self.data = []
+
+		based_on = {
+			'Opportunity Owner': '_assign',
+			'Source': 'source',
+			'Opportunity Type': 'opportunity_type'
+		}[self.filters.get('based_on')]
+
+		data_based_on = {
+			'Number': 'count(name) as count',
+			'Amount': 'opportunity_amount as amount',
+		}[self.filters.get('data_based_on')]
+
+		self.get_data_query(based_on, data_based_on)
+
+		self.get_rows()
+
+	def get_data_query(self, based_on, data_based_on):
+		if self.filters.get('data_based_on') == 'Number':
+			group_by = '{},{}'.format('sales_stage', based_on)
+			self.query_result = frappe.db.get_list('Opportunity',
+				filters=self.get_conditions(),
+				fields=['sales_stage', data_based_on, based_on],
+				group_by=group_by
+			)
+
+		elif self.filters.get('data_based_on') == 'Amount':
+			self.query_result = frappe.db.get_list('Opportunity',
+				filters=self.get_conditions(),
+				fields=['sales_stage', based_on, data_based_on, 'currency']
+			)
+
+			self.convert_to_base_currency()
+
+			dataframe = pandas.DataFrame.from_records(self.query_result)
+			dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True)
+			result = dataframe.groupby(['sales_stage', based_on], as_index=False)['amount'].sum()
+
+			self.grouped_data = []
+
+			for i in range(len(result['amount'])):
+				self.grouped_data.append({
+					'sales_stage': result['sales_stage'][i],
+					based_on : result[based_on][i],
+					'amount': result['amount'][i]
+				})
+
+			self.query_result = self.grouped_data
+
+	def get_rows(self):
+		self.data = []
+		self.get_formatted_data()
+
+		for based_on,data in iteritems(self.formatted_data):
+			row_based_on={
+				'Opportunity Owner': 'opportunity_owner',
+				'Source': 'source',
+				'Opportunity Type': 'opportunity_type'
+			}[self.filters.get('based_on')]
+
+			row = {row_based_on: based_on}
+
+			for d in self.query_result:
+				sales_stage = d.get('sales_stage')
+				row[sales_stage] = data.get(sales_stage)
+
+			self.data.append(row)
+
+	def get_formatted_data(self):
+		self.formatted_data = frappe._dict()
+
+		for d in self.query_result:
+			data_based_on ={
+				'Number': 'count',
+				'Amount': 'amount'
+			}[self.filters.get('data_based_on')]
+
+			based_on ={
+				'Opportunity Owner': '_assign',
+				'Source': 'source',
+				'Opportunity Type': 'opportunity_type'
+			}[self.filters.get('based_on')]
+
+			if self.filters.get('based_on') == 'Opportunity Owner':
+				if d.get(based_on) == '[]' or d.get(based_on) is None or d.get(based_on) == 'Not Assigned':
+					assignments = ['Not Assigned']
+				else:
+					assignments = json.loads(d.get(based_on))
+
+				sales_stage = d.get('sales_stage')
+				count = d.get(data_based_on)
+
+				if assignments:
+					if len(assignments) > 1:
+						for assigned_to in assignments:
+							self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count)
+					else:
+						assigned_to = assignments[0]
+						self.set_formatted_data_based_on_sales_stage(assigned_to, sales_stage, count)
+			else:
+				value = d.get(based_on)
+				sales_stage = d.get('sales_stage')
+				count = d.get(data_based_on)
+				self.set_formatted_data_based_on_sales_stage(value, sales_stage, count)
+
+	def set_formatted_data_based_on_sales_stage(self, based_on, sales_stage, count):
+		self.formatted_data.setdefault(based_on, frappe._dict()).setdefault(sales_stage, 0)
+		self.formatted_data[based_on][sales_stage] += count
+
+	def get_conditions(self):
+		filters = []
+
+		if self.filters.get('company'):
+			filters.append({'company': self.filters.get('company')})
+
+		if self.filters.get('opportunity_type'):
+			filters.append({'opportunity_type': self.filters.get('opportunity_type')})
+
+		if self.filters.get('opportunity_source'):
+			filters.append({'source': self.filters.get('opportunity_source')})
+
+		if self.filters.get('status'):
+			filters.append({'status': ('in',self.filters.get('status'))})
+
+		if self.filters.get('from_date') and self.filters.get('to_date'):
+			filters.append(['transaction_date', 'between', [self.filters.get('from_date'), self.filters.get('to_date')]])
+
+		return filters
+
+	def get_chart_data(self):
+		labels = []
+		datasets = []
+		values = [0] * 8
+
+		for sales_stage in self.sales_stage_list:
+			labels.append(sales_stage)
+
+		options = {
+			'Number': 'count',
+			'Amount': 'amount'
+		}[self.filters.get('data_based_on')]
+
+		for data in self.query_result:
+			for count in range(len(values)):
+				if data['sales_stage'] == labels[count]:
+					values[count] = values[count] + data[options]
+
+		datasets.append({'name':options, 'values':values})
+
+		self.chart = {
+			'data':{
+				'labels': labels,
+				'datasets': datasets
+			},
+			'type':'line'
+		}
+
+	def currency_conversion(self,from_currency,to_currency):
+		cacheobj = frappe.cache()
+
+		if cacheobj.get(from_currency):
+			return flt(str(cacheobj.get(from_currency),'UTF-8'))
+
+		else:
+			value = get_exchange_rate(from_currency,to_currency)
+			cacheobj.set(from_currency,value)
+			return flt(str(cacheobj.get(from_currency),'UTF-8'))
+
+	def get_default_currency(self):
+		company = self.filters.get('company')
+		return frappe.db.get_value('Company', company, 'default_currency')
+
+	def convert_to_base_currency(self):
+		default_currency = self.get_default_currency()
+		for data in self.query_result:
+			if data.get('currency') != default_currency:
+				opportunity_currency = data.get('currency')
+				value = self.currency_conversion(opportunity_currency,default_currency)
+				data['amount'] = data['amount'] * value
\ No newline at end of file
diff --git a/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py
new file mode 100644
index 0000000..13859d9
--- /dev/null
+++ b/erpnext/crm/report/opportunity_summary_by_sales_stage/test_opportunity_summary_by_sales_stage.py
@@ -0,0 +1,94 @@
+import unittest
+
+import frappe
+
+from erpnext.crm.report.opportunity_summary_by_sales_stage.opportunity_summary_by_sales_stage import (
+	execute,
+)
+from erpnext.crm.report.sales_pipeline_analytics.test_sales_pipeline_analytics import (
+	create_company,
+	create_customer,
+	create_opportunity,
+)
+
+
+class TestOpportunitySummaryBySalesStage(unittest.TestCase):
+	@classmethod
+	def setUpClass(self):
+		frappe.db.delete("Opportunity")
+		create_company()
+		create_customer()
+		create_opportunity()
+
+	def test_opportunity_summary_by_sales_stage(self):
+		self.check_for_opportunity_owner()
+		self.check_for_source()
+		self.check_for_opportunity_type()
+		self.check_all_filters()
+
+	def check_for_opportunity_owner(self):
+		filters = {
+			'based_on': "Opportunity Owner",
+			'data_based_on': "Number",
+			'company': "Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [{
+			'opportunity_owner': "Not Assigned",
+			'Prospecting': 1
+		}]
+
+		self.assertEqual(expected_data, report[1])
+
+	def check_for_source(self):
+		filters = {
+			'based_on': "Source",
+			'data_based_on': "Number",
+			'company': "Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [{
+			'source': 'Cold Calling',
+			'Prospecting': 1
+		}]
+
+		self.assertEqual(expected_data, report[1])
+
+	def check_for_opportunity_type(self):
+		filters = {
+			'based_on': "Opportunity Type",
+			'data_based_on': "Number",
+			'company': "Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [{
+			'opportunity_type': 'Sales',
+			'Prospecting': 1
+		}]
+
+		self.assertEqual(expected_data, report[1])
+
+	def check_all_filters(self):
+		filters = {
+			'based_on': "Opportunity Type",
+			'data_based_on': "Number",
+			'company': "Best Test",
+			'opportunity_source': "Cold Calling",
+			'opportunity_type': "Sales",
+			'status': ["Open"]
+		}
+
+		report = execute(filters)
+
+		expected_data = [{
+			'opportunity_type': 'Sales',
+			'Prospecting': 1
+		}]
+
+		self.assertEqual(expected_data, report[1])
\ No newline at end of file
diff --git a/erpnext/crm/report/sales_pipeline_analytics/__init__.py b/erpnext/crm/report/sales_pipeline_analytics/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/crm/report/sales_pipeline_analytics/__init__.py
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js
new file mode 100644
index 0000000..1426f4b
--- /dev/null
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.js
@@ -0,0 +1,70 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Sales Pipeline Analytics"] = {
+	"filters": [
+		{
+			fieldname: "pipeline_by",
+			label: __("Pipeline By"),
+			fieldtype: "Select",
+			options: "Owner\nSales Stage",
+			default: "Owner"
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date"
+		},
+		{
+			fieldname: "to_date",
+			label: __("To Date"),
+			fieldtype: "Date"
+		},
+		{
+			fieldname: "range",
+			label: __("Range"),
+			fieldtype: "Select",
+			options: "Monthly\nQuarterly",
+			default: "Monthly"
+		},
+		{
+			fieldname: "assigned_to",
+			label: __("Assigned To"),
+			fieldtype: "Link",
+			options: "User"
+		},
+		{
+			fieldname: "status",
+			label: __("Status"),
+			fieldtype: "Select",
+			options: "Open\nQuotation\nConverted\nReplied"
+		},
+		{
+			fieldname: "based_on",
+			label: __("Based On"),
+			fieldtype: "Select",
+			options: "Number\nAmount",
+			default: "Number"
+		},
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company")
+		},
+		{
+			fieldname: "opportunity_source",
+			label: __("Opportunity Source"),
+			fieldtype: "Link",
+			options: "Lead Source"
+		},
+		{
+			fieldname: "opportunity_type",
+			label: __("Opportunity Type"),
+			fieldtype: "Link",
+			options: "Opportunity Type"
+		},
+	]
+};
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json
new file mode 100644
index 0000000..cffdddf
--- /dev/null
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.json
@@ -0,0 +1,29 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-07-01 17:29:09.530787",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2021-07-01 17:45:17.612861",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Sales Pipeline Analytics",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Opportunity",
+ "report_name": "Sales Pipeline Analytics",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Sales User"
+  },
+  {
+   "role": "Sales Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
new file mode 100644
index 0000000..7466982
--- /dev/null
+++ b/erpnext/crm/report/sales_pipeline_analytics/sales_pipeline_analytics.py
@@ -0,0 +1,333 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import json
+from datetime import date
+
+import frappe
+import pandas
+from dateutil.relativedelta import relativedelta
+from frappe import _
+from frappe.utils import cint, flt
+from six import iteritems
+
+from erpnext.setup.utils import get_exchange_rate
+
+
+def execute(filters=None):
+	return SalesPipelineAnalytics(filters).run()
+
+class SalesPipelineAnalytics(object):
+	def __init__(self, filters=None):
+		self.filters = frappe._dict(filters or {})
+
+	def run(self):
+		self.get_columns()
+		self.get_data()
+		self.get_chart_data()
+
+		return self.columns, self.data, None, self.chart
+
+	def get_columns(self):
+		self.columns = []
+
+		self.set_range_columns()
+		self.set_pipeline_based_on_column()
+
+	def set_range_columns(self):
+		based_on = {
+			'Number': 'Int',
+			'Amount': 'Currency'
+		}[self.filters.get('based_on')]
+
+		if self.filters.get('range') == 'Monthly':
+			month_list = self.get_month_list()
+
+			for month in month_list:
+				self.columns.append({
+					'fieldname': month,
+					'fieldtype': based_on,
+					'label': month,
+					'width': 200
+				})
+
+		elif self.filters.get('range') == 'Quarterly':
+			for quarter in range(1, 5):
+				self.columns.append({
+					'fieldname': f'Q{quarter}',
+					'fieldtype': based_on,
+					'label': f'Q{quarter}',
+					'width': 200
+				})
+
+	def set_pipeline_based_on_column(self):
+		if self.filters.get('pipeline_by') == 'Owner':
+			self.columns.insert(0, {
+				'fieldname': 'opportunity_owner',
+				'label': _('Opportunity Owner'),
+				'width': 200
+			})
+
+		elif self.filters.get('pipeline_by') == 'Sales Stage':
+			self.columns.insert(0, {
+				'fieldname': 'sales_stage',
+				'label': _('Sales Stage'),
+				'width': 200
+			})
+
+	def get_fields(self):
+		self.based_on ={
+			'Owner': '_assign as opportunity_owner',
+			'Sales Stage': 'sales_stage'
+		}[self.filters.get('pipeline_by')]
+
+		self.data_based_on ={
+			'Number': 'count(name) as count',
+			'Amount': 'opportunity_amount as amount'
+		}[self.filters.get('based_on')]
+
+		self.group_by_based_on = {
+			'Owner': '_assign',
+			'Sales Stage': 'sales_stage'
+		}[self.filters.get('pipeline_by')]
+
+		self.group_by_period = {
+			'Monthly': 'month(expected_closing)',
+			'Quarterly': 'QUARTER(expected_closing)'
+		}[self.filters.get('range')]
+
+		self.pipeline_by = {
+			'Owner': 'opportunity_owner',
+			'Sales Stage': 'sales_stage'
+		}[self.filters.get('pipeline_by')]
+
+		self.duration = {
+			'Monthly': 'monthname(expected_closing) as month',
+			'Quarterly': 'QUARTER(expected_closing) as quarter'
+		}[self.filters.get('range')]
+
+		self.period_by = {
+			'Monthly': 'month',
+			'Quarterly': 'quarter'
+		}[self.filters.get('range')]
+
+	def get_data(self):
+		self.get_fields()
+
+		if self.filters.get('based_on') == 'Number':
+			self.query_result = frappe.db.get_list('Opportunity',
+				filters=self.get_conditions(),
+				fields=[self.based_on, self.data_based_on, self.duration],
+				group_by='{},{}'.format(self.group_by_based_on, self.group_by_period),
+				order_by=self.group_by_period
+			)
+
+		if self.filters.get('based_on') == 'Amount':
+			self.query_result = frappe.db.get_list('Opportunity',
+				filters=self.get_conditions(),
+				fields=[self.based_on, self.data_based_on, self.duration, 'currency']
+			)
+
+			self.convert_to_base_currency()
+
+			dataframe = pandas.DataFrame.from_records(self.query_result)
+			dataframe.replace(to_replace=[None], value='Not Assigned', inplace=True)
+			result = dataframe.groupby([self.pipeline_by, self.period_by], as_index=False)['amount'].sum()
+
+			self.grouped_data = []
+
+			for i in range(len(result['amount'])):
+				self.grouped_data.append({
+					self.pipeline_by : result[self.pipeline_by][i],
+					self.period_by : result[self.period_by][i],
+					'amount': result['amount'][i]
+				})
+
+			self.query_result = self.grouped_data
+
+		self.get_periodic_data()
+		self.append_data(self.pipeline_by, self.period_by)
+
+	def get_conditions(self):
+		conditions = []
+
+		if self.filters.get('opportunity_source'):
+			conditions.append({'source': self.filters.get('opportunity_source')})
+
+		if self.filters.get('opportunity_type'):
+			conditions.append({'opportunity_type': self.filters.get('opportunity_type')})
+
+		if self.filters.get('status'):
+			conditions.append({'status': self.filters.get('status')})
+
+		if self.filters.get('company'):
+			conditions.append({'company': self.filters.get('company')})
+
+		if self.filters.get('from_date') and self.filters.get('to_date'):
+			conditions.append(['expected_closing', 'between',
+				[self.filters.get('from_date'), self.filters.get('to_date')]])
+
+		return conditions
+
+	def get_chart_data(self):
+		labels = []
+		datasets = []
+
+		self.append_to_dataset(datasets)
+
+		for column in self.columns:
+			if column['fieldname'] != 'opportunity_owner' and column['fieldname'] != 'sales_stage':
+				labels.append(column['fieldname'])
+
+		self.chart = {
+			'data':{
+				'labels': labels,
+				'datasets': datasets
+			},
+			'type':'line'
+		}
+
+		return self.chart
+
+	def get_periodic_data(self):
+		self.periodic_data = frappe._dict()
+
+		based_on = {
+			'Number': 'count',
+			'Amount': 'amount'
+		}[self.filters.get('based_on')]
+
+		pipeline_by = {
+			'Owner': 'opportunity_owner',
+			'Sales Stage': 'sales_stage'
+		}[self.filters.get('pipeline_by')]
+
+		frequency = {
+			'Monthly': 'month',
+			'Quarterly': 'quarter'
+		}[self.filters.get('range')]
+
+		for info in self.query_result:
+			if self.filters.get('range') == 'Monthly':
+				period = info.get(frequency)
+			if self.filters.get('range') == 'Quarterly':
+				period = f'Q{cint(info.get("quarter"))}'
+
+			value = info.get(pipeline_by)
+			count_or_amount = info.get(based_on)
+
+			if self.filters.get('pipeline_by') == 'Owner':
+				if value == 'Not Assigned' or value == '[]' or value is None:
+					assigned_to = ['Not Assigned']
+				else:
+					assigned_to = json.loads(value)
+				self.check_for_assigned_to(period, value, count_or_amount, assigned_to, info)
+
+			else:
+				self.set_formatted_data(period, value, count_or_amount, None)
+
+	def set_formatted_data(self, period, value, count_or_amount, assigned_to):
+		if assigned_to:
+			if len(assigned_to) > 1:
+				if self.filters.get('assigned_to'):
+					for user in assigned_to:
+						if self.filters.get('assigned_to') == user:
+							value = user
+							self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0)
+							self.periodic_data[value][period] += count_or_amount
+				else:
+					for user in assigned_to:
+						value = user
+						self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0)
+						self.periodic_data[value][period] += count_or_amount
+			else:
+				value = assigned_to[0]
+				self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0)
+				self.periodic_data[value][period] += count_or_amount
+
+		else:
+			self.periodic_data.setdefault(value, frappe._dict()).setdefault(period, 0)
+			self.periodic_data[value][period] += count_or_amount
+
+	def check_for_assigned_to(self, period, value, count_or_amount, assigned_to, info):
+		if self.filters.get('assigned_to'):
+			for data in json.loads(info.get('opportunity_owner')):
+				if data == self.filters.get('assigned_to'):
+					self.set_formatted_data(period, data, count_or_amount, assigned_to)
+		else:
+			self.set_formatted_data(period, value, count_or_amount, assigned_to)
+
+	def get_month_list(self):
+		month_list= []
+		current_date = date.today()
+		month_number = date.today().month
+
+		for month in range(month_number,13):
+			month_list.append(current_date.strftime('%B'))
+			current_date = current_date + relativedelta(months=1)
+
+		return month_list
+
+	def append_to_dataset(self, datasets):
+		range_by = {
+			'Monthly': 'month',
+			'Quarterly': 'quarter'
+		}[self.filters.get('range')]
+
+		based_on = {
+			'Amount': 'amount',
+			'Number': 'count'
+		}[self.filters.get('based_on')]
+
+		if self.filters.get('range') == 'Quarterly':
+			frequency_list = [1,2,3,4]
+			count = [0] * 4
+
+		if self.filters.get('range') == 'Monthly':
+			frequency_list = self.get_month_list()
+			count = [0] * 12
+
+		for info in self.query_result:
+			for i in range(len(frequency_list)):
+				if info[range_by] == frequency_list[i]:
+					count[i] = count[i] + info[based_on]
+		datasets.append({'name': based_on, 'values': count})
+
+	def append_data(self, pipeline_by, period_by):
+		self.data = []
+		for pipeline,period_data in iteritems(self.periodic_data):
+			row = {pipeline_by : pipeline}
+			for info in self.query_result:
+				if self.filters.get('range') == 'Monthly':
+					period = info.get(period_by)
+
+				if self.filters.get('range') == 'Quarterly':
+					period = f'Q{cint(info.get(period_by))}'
+
+				count = period_data.get(period,0.0)
+				row[period] = count
+
+			self.data.append(row)
+
+	def get_default_currency(self):
+		company = self.filters.get('company')
+		return frappe.db.get_value('Company',company,['default_currency'])
+
+	def get_currency_rate(self, from_currency, to_currency):
+		cacheobj = frappe.cache()
+
+		if cacheobj.get(from_currency):
+			return flt(str(cacheobj.get(from_currency),'UTF-8'))
+
+		else:
+			value = get_exchange_rate(from_currency, to_currency)
+			cacheobj.set(from_currency, value)
+			return flt(str(cacheobj.get(from_currency),'UTF-8'))
+
+	def convert_to_base_currency(self):
+		default_currency = self.get_default_currency()
+		for data in self.query_result:
+			if data.get('currency') != default_currency:
+				opportunity_currency = data.get('currency')
+				value = self.get_currency_rate(opportunity_currency,default_currency)
+				data['amount'] = data['amount'] * value
\ No newline at end of file
diff --git a/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py
new file mode 100644
index 0000000..24c3839
--- /dev/null
+++ b/erpnext/crm/report/sales_pipeline_analytics/test_sales_pipeline_analytics.py
@@ -0,0 +1,238 @@
+import unittest
+
+import frappe
+
+from erpnext.crm.report.sales_pipeline_analytics.sales_pipeline_analytics import execute
+
+
+class TestSalesPipelineAnalytics(unittest.TestCase):
+	@classmethod
+	def setUpClass(self):
+		frappe.db.delete("Opportunity")
+		create_company()
+		create_customer()
+		create_opportunity()
+
+	def test_sales_pipeline_analytics(self):
+		self.check_for_monthly_and_number()
+		self.check_for_monthly_and_amount()
+		self.check_for_quarterly_and_number()
+		self.check_for_quarterly_and_amount()
+		self.check_for_all_filters()
+
+	def check_for_monthly_and_number(self):
+		filters = {
+			'pipeline_by':"Owner",
+			'range':"Monthly",
+			'based_on':"Number",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'opportunity_owner':'Not Assigned',
+				'August':1
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+		filters = {
+			'pipeline_by':"Sales Stage",
+			'range':"Monthly",
+			'based_on':"Number",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'sales_stage':'Prospecting',
+				'August':1
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+	def check_for_monthly_and_amount(self):
+		filters = {
+			'pipeline_by':"Owner",
+			'range':"Monthly",
+			'based_on':"Amount",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'opportunity_owner':'Not Assigned',
+				'August':150000
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+		filters = {
+			'pipeline_by':"Sales Stage",
+			'range':"Monthly",
+			'based_on':"Amount",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'sales_stage':'Prospecting',
+				'August':150000
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+	def check_for_quarterly_and_number(self):
+		filters = {
+			'pipeline_by':"Owner",
+			'range':"Quarterly",
+			'based_on':"Number",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'opportunity_owner':'Not Assigned',
+				'Q3':1
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+		filters = {
+			'pipeline_by':"Sales Stage",
+			'range':"Quarterly",
+			'based_on':"Number",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'sales_stage':'Prospecting',
+				'Q3':1
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+	def check_for_quarterly_and_amount(self):
+		filters = {
+			'pipeline_by':"Owner",
+			'range':"Quarterly",
+			'based_on':"Amount",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'opportunity_owner':'Not Assigned',
+				'Q3':150000
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+		filters = {
+			'pipeline_by':"Sales Stage",
+			'range':"Quarterly",
+			'based_on':"Amount",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test"
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'sales_stage':'Prospecting',
+				'Q3':150000
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+	def check_for_all_filters(self):
+		filters = {
+			'pipeline_by':"Owner",
+			'range':"Monthly",
+			'based_on':"Number",
+			'status':"Open",
+			'opportunity_type':"Sales",
+			'company':"Best Test",
+			'opportunity_source':'Cold Calling',
+			'from_date': '2021-08-01',
+			'to_date':'2021-08-31'
+		}
+
+		report = execute(filters)
+
+		expected_data = [
+			{
+				'opportunity_owner':'Not Assigned',
+				'August': 1
+			}
+		]
+
+		self.assertEqual(expected_data,report[1])
+
+def create_company():
+	doc = frappe.db.exists('Company','Best Test')
+	if not doc:
+		doc = frappe.new_doc('Company')
+		doc.company_name = 'Best Test'
+		doc.default_currency = "INR"
+		doc.insert()
+
+def create_customer():
+	doc = frappe.db.exists("Customer","_Test NC")
+	if not doc:
+		doc = frappe.new_doc("Customer")
+		doc.customer_name = '_Test NC'
+		doc.insert()
+
+def create_opportunity():
+	doc = frappe.db.exists({"doctype":"Opportunity","party_name":"_Test NC"})
+	if not doc:
+		doc = frappe.new_doc("Opportunity")
+		doc.opportunity_from = "Customer"
+		customer_name = frappe.db.get_value("Customer",{"customer_name":'_Test NC'},['customer_name'])
+		doc.party_name = customer_name
+		doc.opportunity_amount = 150000
+		doc.source = "Cold Calling"
+		doc.currency = "INR"
+		doc.expected_closing = "2021-08-31"
+		doc.company = 'Best Test'
+		doc.insert()
\ No newline at end of file
diff --git a/erpnext/crm/workspace/crm/crm.json b/erpnext/crm/workspace/crm/crm.json
index c363395..a661b62 100644
--- a/erpnext/crm/workspace/crm/crm.json
+++ b/erpnext/crm/workspace/crm/crm.json
@@ -148,6 +148,24 @@
    "type": "Link"
   },
   {
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Sales Pipeline Analytics",
+   "link_to": "Sales Pipeline Analytics",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Opportunity Summary by Sales Stage",
+   "link_to": "Opportunity Summary by Sales Stage",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
@@ -403,7 +421,7 @@
    "type": "Link"
   }
  ],
- "modified": "2021-08-05 12:15:56.913091",
+ "modified": "2021-08-19 19:08:08.728876",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "CRM",
diff --git a/erpnext/hr/doctype/employee/employee_reminders.py b/erpnext/hr/doctype/employee/employee_reminders.py
index fc01866..ba086dc 100644
--- a/erpnext/hr/doctype/employee/employee_reminders.py
+++ b/erpnext/hr/doctype/employee/employee_reminders.py
@@ -13,7 +13,7 @@
 # HOLIDAY REMINDERS
 # -----------------
 def send_reminders_in_advance_weekly():
-	to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1)
+	to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders"))
 	frequency = frappe.db.get_single_value("HR Settings", "frequency")
 	if not (to_send_in_advance and frequency == "Weekly"):
 		return
@@ -21,7 +21,7 @@
 	send_advance_holiday_reminders("Weekly")
 
 def send_reminders_in_advance_monthly():
-	to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders") or 1)
+	to_send_in_advance = int(frappe.db.get_single_value("HR Settings", "send_holiday_reminders"))
 	frequency = frappe.db.get_single_value("HR Settings", "frequency")
 	if not (to_send_in_advance and frequency == "Monthly"):
 		return
@@ -79,7 +79,7 @@
 # ------------------
 def send_birthday_reminders():
 	"""Send Employee birthday reminders if no 'Stop Birthday Reminders' is not set."""
-	to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders") or 1)
+	to_send = int(frappe.db.get_single_value("HR Settings", "send_birthday_reminders"))
 	if not to_send:
 		return
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index c82579a..15b196f 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -305,3 +305,4 @@
 erpnext.patches.v13_0.create_gst_payment_entry_fields
 erpnext.patches.v14_0.delete_shopify_doctypes
 erpnext.patches.v13_0.update_dates_in_tax_withholding_category
+erpnext.patches.v14_0.update_opportunity_currency_fields
diff --git a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
index 96aa547..b34b5c1 100644
--- a/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
+++ b/erpnext/patches/v13_0/add_custom_field_for_south_africa.py
@@ -12,6 +12,7 @@
 		return
 
 	frappe.reload_doc('regional', 'doctype', 'south_africa_vat_settings')
+	frappe.reload_doc('regional', 'report', 'vat_audit_report')
 	frappe.reload_doc('accounts', 'doctype', 'south_africa_vat_account')
 
 	make_custom_fields()
diff --git a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
index 334c9d2..7e6d67c 100644
--- a/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
+++ b/erpnext/patches/v13_0/create_gst_payment_entry_fields.py
@@ -4,6 +4,7 @@
 import frappe
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
 
+
 def execute():
 	frappe.reload_doc('accounts', 'doctype', 'advance_taxes_and_charges')
 	frappe.reload_doc('accounts', 'doctype', 'payment_entry')
diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
new file mode 100644
index 0000000..223be7f
--- /dev/null
+++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
@@ -0,0 +1,36 @@
+from __future__ import unicode_literals
+
+import frappe
+from frappe.utils import flt
+
+import erpnext
+from erpnext.setup.utils import get_exchange_rate
+
+
+def execute():
+	frappe.reload_doc('crm', 'doctype', 'opportunity')
+	frappe.reload_doc('crm', 'doctype', 'opportunity_item')
+
+	opportunities = frappe.db.get_list('Opportunity', filters={
+		'opportunity_amount': ['>', 0]
+	}, fields=['name', 'company', 'currency', 'opportunity_amount'])
+
+	for opportunity in opportunities:
+		company_currency = erpnext.get_company_currency(opportunity.company)
+
+		# base total and total will be 0 only since item table did not have amount field earlier
+		if opportunity.currency != company_currency:
+			conversion_rate = get_exchange_rate(opportunity.currency, company_currency)
+			base_opportunity_amount = flt(conversion_rate) * flt(opportunity.opportunity_amount)
+			grand_total = flt(opportunity.opportunity_amount)
+			base_grand_total = flt(conversion_rate) * flt(opportunity.opportunity_amount)
+		else:
+			conversion_rate = 1
+			base_opportunity_amount = grand_total = base_grand_total = flt(opportunity.opportunity_amount)
+
+		frappe.db.set_value('Opportunity', opportunity.name, {
+			'conversion_rate': conversion_rate,
+			'base_opportunity_amount': base_opportunity_amount,
+			'grand_total': grand_total,
+			'base_grand_total': base_grand_total
+		}, update_modified=False)
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 5f8966f..fd3d472 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -487,6 +487,10 @@
 		var me = this;
 		var item = frappe.get_doc(cdt, cdn);
 		var update_stock = 0, show_batch_dialog = 0;
+
+		item.weight_per_unit = 0;
+		item.weight_uom = '';
+
 		if(['Sales Invoice'].includes(this.frm.doc.doctype)) {
 			update_stock = cint(me.frm.doc.update_stock);
 			show_batch_dialog = update_stock;
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index e3e09ef..cf4850e 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -54,7 +54,7 @@
 			self.get_invoice_items()
 			self.get_items_based_on_tax_rate()
 			self.invoice_fields = [d["fieldname"] for d in self.invoice_columns]
-		
+
 		self.get_data()
 
 		return self.columns, self.data
@@ -96,35 +96,36 @@
 	def get_b2c_data(self):
 		b2cs_output = {}
 
-		for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
-			invoice_details = self.invoices.get(inv)
-			for rate, items in items_based_on_rate.items():
-				place_of_supply = invoice_details.get("place_of_supply")
-				ecommerce_gstin =  invoice_details.get("ecommerce_gstin")
+		if self.invoices:
+			for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
+				invoice_details = self.invoices.get(inv)
+				for rate, items in items_based_on_rate.items():
+					place_of_supply = invoice_details.get("place_of_supply")
+					ecommerce_gstin =  invoice_details.get("ecommerce_gstin")
 
-				b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{
-					"place_of_supply": "",
-					"ecommerce_gstin": "",
-					"rate": "",
-					"taxable_value": 0,
-					"cess_amount": 0,
-					"type": "",
-					"invoice_number": invoice_details.get("invoice_number"),
-					"posting_date": invoice_details.get("posting_date"),
-					"invoice_value": invoice_details.get("base_grand_total"),
-				})
+					b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin), {
+						"place_of_supply": "",
+						"ecommerce_gstin": "",
+						"rate": "",
+						"taxable_value": 0,
+						"cess_amount": 0,
+						"type": "",
+						"invoice_number": invoice_details.get("invoice_number"),
+						"posting_date": invoice_details.get("posting_date"),
+						"invoice_value": invoice_details.get("base_grand_total"),
+					})
 
-				row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
-				row["place_of_supply"] = place_of_supply
-				row["ecommerce_gstin"] = ecommerce_gstin
-				row["rate"] = rate
-				row["taxable_value"] += sum([abs(net_amount)
-					for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items])
-				row["cess_amount"] += flt(self.invoice_cess.get(inv), 2)
-				row["type"] = "E" if ecommerce_gstin else "OE"
+					row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
+					row["place_of_supply"] = place_of_supply
+					row["ecommerce_gstin"] = ecommerce_gstin
+					row["rate"] = rate
+					row["taxable_value"] += sum([abs(net_amount)
+						for item_code, net_amount in self.invoice_items.get(inv).items() if item_code in items])
+					row["cess_amount"] += flt(self.invoice_cess.get(inv), 2)
+					row["type"] = "E" if ecommerce_gstin else "OE"
 
-		for key, value in iteritems(b2cs_output):
-			self.data.append(value)
+			for key, value in iteritems(b2cs_output):
+				self.data.append(value)
 
 	def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
 		row = []
@@ -173,9 +174,10 @@
 
 		company_gstins = get_company_gstin_number(self.filters.get('company'), all_gstins=True)
 
-		self.filters.update({
-			'company_gstins': company_gstins
-		})
+		if company_gstins:
+			self.filters.update({
+				'company_gstins': company_gstins
+			})
 
 		invoice_data = frappe.db.sql("""
 			select
@@ -713,7 +715,7 @@
 						"width": 100
 				}
 			]
-			
+
 		self.columns = self.invoice_columns + self.tax_columns + self.other_columns
 
 @frappe.whitelist()
@@ -1050,6 +1052,7 @@
 			["Dynamic Link", "link_doctype", "=", "Company"],
 			["Dynamic Link", "link_name", "=", company],
 			["Dynamic Link", "parenttype", "=", "Address"],
+			["gstin", "!=", '']
 		]
 		gstin = frappe.get_all("Address", filters=filters, pluck="gstin", order_by="is_primary_address desc")
 		if gstin and not all_gstins:
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 908020d..c819d49 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -278,7 +278,7 @@
 					get_query_filters: {
 						docstatus: 1,
 						material_request_type: ["in", allowed_request_types],
-						status: ["not in", ["Transferred", "Issued"]]
+						status: ["not in", ["Transferred", "Issued", "Cancelled", "Stopped"]]
 					}
 				})
 			}, __("Get Items From"));
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 41ca830..4ccfa62 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1503,7 +1503,8 @@
 					qty_to_reserve -= reserved_qty[0][0]
 				if qty_to_reserve > 0:
 					for item in self.items:
-						if item.item_code == item_code:
+						has_serial_no = frappe.get_cached_value("Item", item.item_code, "has_serial_no")
+						if item.item_code == item_code and has_serial_no:
 							serial_nos = (item.serial_no).split("\n")
 							for serial_no in serial_nos:
 								if qty_to_reserve > 0:
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index d578e6a..19597c3 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -321,8 +321,8 @@
 		"transaction_date": args.get("transaction_date"),
 		"against_blanket_order": args.get("against_blanket_order"),
 		"bom_no": item.get("default_bom"),
-		"weight_per_unit": item.get("weight_per_unit"),
-		"weight_uom": item.get("weight_uom")
+		"weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
+		"weight_uom": args.get("weight_uom") or item.get("weight_uom")
 	})
 
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):