Merge branch 'develop' into inpatient-visits-billing
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 7830cfd..3863768 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -498,7 +498,7 @@
 frappe.ui.form.on("Purchase Invoice", {
 	setup: function(frm) {
 		frm.custom_make_buttons = {
-			'Purchase Invoice': 'Debit Note',
+			'Purchase Invoice': 'Return / Debit Note',
 			'Payment Entry': 'Payment'
 		}
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 5efc32e..89b716c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -592,7 +592,7 @@
 
 		frm.custom_make_buttons = {
 			'Delivery Note': 'Delivery',
-			'Sales Invoice': 'Sales Return',
+			'Sales Invoice': 'Return / Credit Note',
 			'Payment Request': 'Payment Request',
 			'Payment Entry': 'Payment'
 		},
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 552a5d4..e023b47 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -446,7 +446,7 @@
 		if not self.generate_invoice_at_period_start:
 			return False
 
-		if self.is_new_subscription():
+		if self.is_new_subscription() and getdate() >= getdate(self.current_invoice_start):
 			return True
 
 		# Check invoice dates and make sure it doesn't have outstanding invoices
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 47483c9..38532d1 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -58,8 +58,8 @@
 erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend({
 	setup: function() {
 		this.frm.custom_make_buttons = {
-			'Purchase Receipt': 'Receipt',
-			'Purchase Invoice': 'Invoice',
+			'Purchase Receipt': 'Purchase Receipt',
+			'Purchase Invoice': 'Purchase Invoice',
 			'Stock Entry': 'Material to Supplier',
 			'Payment Entry': 'Payment',
 		}
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index 6fbcd8a..886a7d8 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -124,21 +124,24 @@
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_program_courses(doctype, txt, searchfield, start, page_len, filters):
-	if filters.get('program'):
-		return frappe.db.sql("""select course, course_name from `tabProgram Course`
-			where  parent = %(program)s and course like %(txt)s {match_cond}
-			order by
-				if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
-				idx desc,
-				`tabProgram Course`.course asc
-			limit {start}, {page_len}""".format(
-				match_cond=get_match_cond(doctype),
-				start=start,
-				page_len=page_len), {
-					"txt": "%{0}%".format(txt),
-					"_txt": txt.replace('%', ''),
-					"program": filters['program']
-				})
+	if not filters.get('program'):
+		frappe.msgprint(_("Please select a Program first."))
+		return []
+
+	return frappe.db.sql("""select course, course_name from `tabProgram Course`
+		where  parent = %(program)s and course like %(txt)s {match_cond}
+		order by
+			if(locate(%(_txt)s, course), locate(%(_txt)s, course), 99999),
+			idx desc,
+			`tabProgram Course`.course asc
+		limit {start}, {page_len}""".format(
+			match_cond=get_match_cond(doctype),
+			start=start,
+			page_len=page_len), {
+				"txt": "%{0}%".format(txt),
+				"_txt": txt.replace('%', ''),
+				"program": filters['program']
+			})
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
diff --git a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
index b63cc8e..c6f6b99 100644
--- a/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
+++ b/erpnext/loan_management/report/loan_repayment_and_closure/loan_repayment_and_closure.py
@@ -103,7 +103,7 @@
 
 	loan_repayments = frappe.get_all("Loan Repayment",
 		filters = query_filters,
-		fields=["posting_date", "applicant", "name", "against_loan", "payment_type", "payable_amount",
+		fields=["posting_date", "applicant", "name", "against_loan", "payable_amount",
 			"pending_principal_amount", "interest_payable", "penalty_amount", "amount_paid"]
 	)
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 1c4b7a1..15affd8 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -411,7 +411,7 @@
 
 cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
 
-cur_frm.cscript.bom_no	= function(doc, cdt, cdn) {
+cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
 	get_bom_material_detail(doc, cdt, cdn, false);
 };
 
@@ -419,17 +419,22 @@
 	if (doc.is_default) cur_frm.set_value("is_active", 1);
 };
 
-var get_bom_material_detail= function(doc, cdt, cdn, scrap_items) {
+var get_bom_material_detail = function(doc, cdt, cdn, scrap_items) {
+	if (!doc.company) {
+		frappe.throw({message: __("Please select a Company first."), title: __("Mandatory")});
+	}
+
 	var d = locals[cdt][cdn];
 	if (d.item_code) {
 		return frappe.call({
 			doc: doc,
 			method: "get_bom_material_detail",
 			args: {
-				'item_code': d.item_code,
-				'bom_no': d.bom_no != null ? d.bom_no: '',
+				"company": doc.company,
+				"item_code": d.item_code,
+				"bom_no": d.bom_no != null ? d.bom_no: '',
 				"scrap_items": scrap_items,
-				'qty': d.qty,
+				"qty": d.qty,
 				"stock_qty": d.stock_qty,
 				"include_item_in_manufacturing": d.include_item_in_manufacturing,
 				"uom": d.uom,
@@ -468,7 +473,7 @@
 	}
 
 	if (d.bom_no) {
-		frappe.msgprint(__("You can not change rate if BOM mentioned agianst any item"));
+		frappe.msgprint(__("You cannot change the rate if BOM is mentioned against any Item."));
 		get_bom_material_detail(doc, cdt, cdn, scrap_items);
 	} else {
 		erpnext.bom.calculate_rm_cost(doc);
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 6363242..03beedb 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -65,6 +65,10 @@
 
 	def validate(self):
 		self.route = frappe.scrub(self.name).replace('_', '-')
+
+		if not self.company:
+			frappe.throw(_("Please select a Company first."), title=_("Mandatory"))
+
 		self.clear_operations()
 		self.validate_main_item()
 		self.validate_currency()
@@ -125,6 +129,7 @@
 			self.validate_bom_currecny(item)
 
 			ret = self.get_bom_material_detail({
+				"company": self.company,
 				"item_code": item.item_code,
 				"item_name": item.item_name,
 				"bom_no": item.bom_no,
@@ -213,6 +218,7 @@
 
 		for d in self.get("items"):
 			rate = self.get_rm_rate({
+				"company": self.company,
 				"item_code": d.item_code,
 				"bom_no": d.bom_no,
 				"qty": d.qty,
@@ -611,10 +617,20 @@
 	""" Get weighted average of valuation rate from all warehouses """
 
 	total_qty, total_value, valuation_rate = 0.0, 0.0, 0.0
-	for d in frappe.db.sql("""select actual_qty, stock_value from `tabBin`
-		where item_code=%s""", args['item_code'], as_dict=1):
-			total_qty += flt(d.actual_qty)
-			total_value += flt(d.stock_value)
+	item_bins = frappe.db.sql("""
+		select
+			bin.actual_qty, bin.stock_value
+		from
+			`tabBin` bin, `tabWarehouse` warehouse
+		where
+			bin.item_code=%(item)s
+			and bin.warehouse = warehouse.name
+			and warehouse.company=%(company)s""",
+		{"item": args['item_code'], "company": args['company']}, as_dict=1)
+
+	for d in item_bins:
+		total_qty += flt(d.actual_qty)
+		total_value += flt(d.stock_value)
 
 	if total_qty:
 		valuation_rate =  total_value / total_qty
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index cc93bf9..ca530bb 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -456,10 +456,10 @@
 
 			if data and len(data):
 				dates = [d.posting_datetime for d in data]
-				self.actual_start_date = min(dates)
+				self.db_set('actual_start_date', min(dates))
 
 				if self.status == "Completed":
-					self.actual_end_date = max(dates)
+					self.db_set('actual_end_date', max(dates))
 
 		self.set_lead_time()
 
@@ -725,6 +725,7 @@
 		args.update(item_data)
 
 		args["rate"] = get_bom_item_rate({
+			"company": wo_doc.company,
 			"item_code": args.get("item_code"),
 			"qty": args.get("required_qty"),
 			"uom": args.get("stock_uom"),
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index 75ebcbc..1c6758e 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -20,6 +20,7 @@
 		_("Item") + ":Link/Item:150",
 		_("Description") + "::300",
 		_("BOM Qty") + ":Float:160",
+		_("BOM UoM") + "::160",
 		_("Required Qty") + ":Float:120",
 		_("In Stock Qty") + ":Float:120",
 		_("Enough Parts to Build") + ":Float:200",
@@ -32,7 +33,7 @@
 	bom = filters.get("bom")
 
 	table = "`tabBOM Item`"
-	qty_field = "qty"
+	qty_field = "stock_qty"
 
 	qty_to_produce = filters.get("qty_to_produce", 1)
 	if  int(qty_to_produce) <= 0:
@@ -40,7 +41,6 @@
 
 	if filters.get("show_exploded_view"):
 		table = "`tabBOM Explosion Item`"
-		qty_field = "stock_qty"
 
 	if filters.get("warehouse"):
 		warehouse_details = frappe.db.get_value("Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1)
@@ -59,6 +59,7 @@
 				bom_item.item_code,
 				bom_item.description ,
 				bom_item.{qty_field},
+				bom_item.stock_uom,
 				bom_item.{qty_field} * {qty_to_produce} / bom.quantity,
 				sum(ledger.actual_qty) as actual_qty,
 				sum(FLOOR(ledger.actual_qty / (bom_item.{qty_field} * {qty_to_produce} / bom.quantity)))
diff --git a/erpnext/payroll/doctype/payroll_period/payroll_period.py b/erpnext/payroll/doctype/payroll_period/payroll_period.py
index d7893d0..46f6cd8 100644
--- a/erpnext/payroll/doctype/payroll_period/payroll_period.py
+++ b/erpnext/payroll/doctype/payroll_period/payroll_period.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt
+from frappe.utils import date_diff, getdate, formatdate, cint, month_diff, flt, add_months
 from frappe.model.document import Document
 from erpnext.hr.utils import get_holidays_for_employee
 
@@ -88,6 +88,8 @@
 		period_start = joining_date
 	if relieving_date and getdate(relieving_date) < getdate(period_end):
 		period_end = relieving_date
+		if month_diff(period_end, start_date) > 1:
+			start_date = add_months(start_date, - (month_diff(period_end, start_date)+1))
 
 	total_sub_periods, remaining_sub_periods = 0.0, 0.0
 
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
index 5c1c79d..3034370 100644
--- a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
@@ -24,9 +24,8 @@
   },
   {
    "fieldname": "reference_invoice",
-   "fieldtype": "Link",
-   "label": "Reference Invoice",
-   "options": "Sales Invoice"
+   "fieldtype": "Data",
+   "label": "Reference Invoice"
   },
   {
    "fieldname": "headers",
@@ -64,7 +63,7 @@
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-12-24 21:09:38.882866",
+ "modified": "2021-01-13 12:06:57.253111",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "E Invoice Request Log",
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
index 4dcb22a..db8bda7 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -7,6 +7,7 @@
  "field_order": [
   "enable",
   "section_break_2",
+  "sandbox_mode",
   "credentials",
   "auth_token",
   "token_expiry"
@@ -41,12 +42,18 @@
    "label": "Credentials",
    "mandatory_depends_on": "enable",
    "options": "E Invoice User"
+  },
+  {
+   "default": "0",
+   "fieldname": "sandbox_mode",
+   "fieldtype": "Check",
+   "label": "Sandbox Mode"
   }
  ],
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2020-12-22 15:34:57.280044",
+ "modified": "2021-01-13 12:04:49.449199",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "E Invoice Settings",
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index abe1504..d0cac90 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -218,8 +218,8 @@
 def get_invoice_value_details(invoice):
 	invoice_value_details = frappe._dict(dict())
 	invoice_value_details.base_total = abs(invoice.base_total)
-	invoice_value_details.invoice_discount_amt = invoice.discount_amount
-	invoice_value_details.round_off = invoice.rounding_adjustment
+	invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
+	invoice_value_details.round_off = invoice.base_rounding_adjustment
 	invoice_value_details.base_grand_total = abs(invoice.base_rounded_total) or abs(invoice.base_grand_total)
 	invoice_value_details.grand_total = abs(invoice.rounded_total) or abs(invoice.grand_total)
 	
@@ -322,7 +322,10 @@
 	
 	shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
 	if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
-		shipping_details = get_party_details(invoice.shipping_address_name)
+		if invoice.gst_category == 'Overseas':
+			shipping_details = get_overseas_address_details(invoice.shipping_address_name)
+		else:
+			shipping_details = get_party_details(invoice.shipping_address_name)
 	
 	if invoice.is_pos and invoice.base_paid_amount:
 		payment_details = get_payment_details(invoice)
@@ -414,15 +417,19 @@
 class GSPConnector():
 	def __init__(self, doctype=None, docname=None):
 		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+		sandbox_mode = self.e_invoice_settings.sandbox_mode
+
 		self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
 		self.credentials = self.get_credentials()
 
-		self.base_url = 'https://gsp.adaequare.com'
-		self.authenticate_url = self.base_url + '/gsp/authenticate?grant_type=token'
-		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
-		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
-		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+		# authenticate url is same for sandbox & live
+		self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
+		self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test'
+
 		self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
+		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
+		self.generate_irn_url = self.base_url + '/enriched/ei/api/invoice'
+		self.gstin_details_url = self.base_url + '/enriched/ei/api/master/gstin'
 		self.cancel_ewaybill_url = self.base_url + '/enriched/ei/api/ewayapi'
 		self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
 
@@ -758,7 +765,7 @@
 
 		_file = frappe.new_doc('File')
 		_file.update({
-			'file_name': f'QRCode_{docname}.png',
+			'file_name': 'QRCode_{}.png'.format(docname.replace('/', '-')),
 			'attached_to_doctype': doctype,
 			'attached_to_name': docname,
 			'content': 'qrcode',
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index f256a66..0d82638 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -48,6 +48,9 @@
 		validate_gstin_check_digit(doc.gstin)
 		set_gst_state_and_state_number(doc)
 
+		if not doc.gst_state:
+			frappe.throw(_("Please Enter GST state"))
+
 		if doc.gst_state_number != doc.gstin[:2]:
 			frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
 				.format(doc.gst_state_number))
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 661e107..5a0d9c9 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -7,7 +7,7 @@
 frappe.ui.form.on('Quotation', {
 	setup: function(frm) {
 		frm.custom_make_buttons = {
-			'Sales Order': 'Make Sales Order'
+			'Sales Order': 'Sales Order'
 		},
 
 		frm.set_query("quotation_to", function() {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 5f2658c..cb1e31b 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -13,7 +13,7 @@
 		frm.custom_make_buttons = {
 			'Packing Slip': 'Packing Slip',
 			'Installation Note': 'Installation Note',
-			'Sales Invoice': 'Invoice',
+			'Sales Invoice': 'Sales Invoice',
 			'Stock Entry': 'Return',
 			'Shipment': 'Shipment'
 		},
diff --git a/erpnext/support/desk_page/support/support.json b/erpnext/support/desk_page/support/support.json
index 28410f3..18cf87a 100644
--- a/erpnext/support/desk_page/support/support.json
+++ b/erpnext/support/desk_page/support/support.json
@@ -28,7 +28,7 @@
   {
    "hidden": 0,
    "label": "Reports",
-   "links": "[\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"First Response Time for Issues\",\n        \"name\": \"First Response Time for Issues\",\n        \"type\": \"report\"\n    }\n]"
+   "links": "[\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"First Response Time for Issues\",\n        \"name\": \"First Response Time for Issues\",\n        \"type\": \"report\"\n    },\n    {\n        \"dependencies\": [\n            \"Issue\"\n        ],\n        \"doctype\": \"Issue\",\n        \"is_query_report\": true,\n        \"label\": \"Issue Summary\",\n        \"name\": \"Issue Summary\",\n        \"type\": \"report\"\n    }\n]"
   }
  ],
  "category": "Modules",
@@ -43,7 +43,7 @@
  "idx": 0,
  "is_standard": 1,
  "label": "Support",
- "modified": "2020-08-11 15:49:34.307341",
+ "modified": "2020-10-12 18:40:22.252915",
  "modified_by": "Administrator",
  "module": "Support",
  "name": "Support",
diff --git a/erpnext/support/report/issue_summary/__init__.py b/erpnext/support/report/issue_summary/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/support/report/issue_summary/__init__.py
diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js
new file mode 100644
index 0000000..684482a
--- /dev/null
+++ b/erpnext/support/report/issue_summary/issue_summary.js
@@ -0,0 +1,73 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["Issue Summary"] = {
+	"filters": [
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			default: frappe.defaults.get_user_default("Company"),
+			reqd: 1
+		},
+		{
+			fieldname: "based_on",
+			label: __("Based On"),
+			fieldtype: "Select",
+			options: ["Customer", "Issue Type", "Issue Priority", "Assigned To"],
+			default: "Customer",
+			reqd: 1
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+			default: frappe.defaults.get_global_default("year_start_date"),
+			reqd: 1
+		},
+		{
+			fieldname:"to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+			default: frappe.defaults.get_global_default("year_end_date"),
+			reqd: 1
+		},
+		{
+			fieldname: "status",
+			label: __("Status"),
+			fieldtype: "Select",
+			options:[
+				{label: __('Open'), value: 'Open'},
+				{label: __('Replied'), value: 'Replied'},
+				{label: __('Resolved'), value: 'Resolved'},
+				{label: __('Closed'), value: 'Closed'}
+			]
+		},
+		{
+			fieldname: "priority",
+			label: __("Issue Priority"),
+			fieldtype: "Link",
+			options: "Issue Priority"
+		},
+		{
+			fieldname: "customer",
+			label: __("Customer"),
+			fieldtype: "Link",
+			options: "Customer"
+		},
+		{
+			fieldname: "project",
+			label: __("Project"),
+			fieldtype: "Link",
+			options: "Project"
+		},
+		{
+			fieldname: "assigned_to",
+			label: __("Assigned To"),
+			fieldtype: "Link",
+			options: "User"
+		}
+	]
+};
\ No newline at end of file
diff --git a/erpnext/support/report/issue_summary/issue_summary.json b/erpnext/support/report/issue_summary/issue_summary.json
new file mode 100644
index 0000000..b8a580c
--- /dev/null
+++ b/erpnext/support/report/issue_summary/issue_summary.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2020-10-12 01:01:55.181777",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "modified": "2020-10-12 14:54:55.655920",
+ "modified_by": "Administrator",
+ "module": "Support",
+ "name": "Issue Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Issue",
+ "report_name": "Issue Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Support Team"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
new file mode 100644
index 0000000..3d73531
--- /dev/null
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -0,0 +1,353 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+import json
+from six import iteritems
+from frappe import _, scrub
+from frappe.utils import flt
+
+def execute(filters=None):
+	return IssueSummary(filters).run()
+
+class IssueSummary(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()
+		self.get_report_summary()
+
+		return self.columns, self.data, None, self.chart, self.report_summary
+
+	def get_columns(self):
+		self.columns = []
+
+		if self.filters.based_on == 'Customer':
+			self.columns.append({
+				'label': _('Customer'),
+				'options': 'Customer',
+				'fieldname': 'customer',
+				'fieldtype': 'Link',
+				'width': 200
+			})
+
+		elif self.filters.based_on == 'Assigned To':
+			self.columns.append({
+				'label': _('User'),
+				'fieldname': 'user',
+				'fieldtype': 'Link',
+				'options': 'User',
+				'width': 200
+			})
+
+		elif self.filters.based_on == 'Issue Type':
+			self.columns.append({
+				'label': _('Issue Type'),
+				'fieldname': 'issue_type',
+				'fieldtype': 'Link',
+				'options': 'Issue Type',
+				'width': 200
+			})
+
+		elif self.filters.based_on == 'Issue Priority':
+			self.columns.append({
+				'label': _('Issue Priority'),
+				'fieldname': 'priority',
+				'fieldtype': 'Link',
+				'options': 'Issue Priority',
+				'width': 200
+			})
+
+		self.statuses = ['Open', 'Replied', 'Resolved', 'Closed']
+		for status in self.statuses:
+			self.columns.append({
+				'label': _(status),
+				'fieldname': scrub(status),
+				'fieldtype': 'Int',
+				'width': 80
+			})
+
+		self.columns.append({
+			'label': _('Total Issues'),
+			'fieldname': 'total_issues',
+			'fieldtype': 'Int',
+			'width': 100
+		})
+
+		self.sla_status_map = {
+			'SLA Failed': 'failed',
+			'SLA Fulfilled': 'fulfilled',
+			'SLA Ongoing': 'ongoing'
+		}
+
+		for label, fieldname in self.sla_status_map.items():
+			self.columns.append({
+				'label': _(label),
+				'fieldname': fieldname,
+				'fieldtype': 'Int',
+				'width': 100
+			})
+
+		self.metrics = ['Avg First Response Time', 'Avg Response Time', 'Avg Hold Time',
+			'Avg Resolution Time', 'Avg User Resolution Time']
+
+		for metric in self.metrics:
+			self.columns.append({
+				'label': _(metric),
+				'fieldname': scrub(metric),
+				'fieldtype': 'Duration',
+				'width': 170
+			})
+
+	def get_data(self):
+		self.get_issues()
+		self.get_rows()
+
+	def get_issues(self):
+		filters = self.get_common_filters()
+		self.field_map = {
+			'Customer': 'customer',
+			'Issue Type': 'issue_type',
+			'Issue Priority': 'priority',
+			'Assigned To': '_assign'
+		}
+
+		self.entries = frappe.db.get_all('Issue',
+			fields=[self.field_map.get(self.filters.based_on), 'name', 'opening_date', 'status', 'avg_response_time',
+				'first_response_time', 'total_hold_time', 'user_resolution_time', 'resolution_time', 'agreement_status'],
+			filters=filters
+		)
+
+	def get_common_filters(self):
+		filters = {}
+		filters['opening_date'] = ('between', [self.filters.from_date, self.filters.to_date])
+
+		if self.filters.get('assigned_to'):
+			filters['_assign'] = ('like', '%' + self.filters.get('assigned_to') + '%')
+
+		for entry in ['company', 'status', 'priority', 'customer', 'project']:
+			if self.filters.get(entry):
+				filters[entry] = self.filters.get(entry)
+
+		return filters
+
+	def get_rows(self):
+		self.data = []
+		self.get_summary_data()
+
+		for entity, data in iteritems(self.issue_summary_data):
+			if self.filters.based_on == 'Customer':
+				row = {'customer': entity}
+			elif self.filters.based_on == 'Assigned To':
+				row = {'user': entity}
+			elif self.filters.based_on == 'Issue Type':
+				row = {'issue_type': entity}
+			elif self.filters.based_on == 'Issue Priority':
+				row = {'priority': entity}
+
+			for status in self.statuses:
+				count = flt(data.get(status, 0.0))
+				row[scrub(status)] = count
+
+			row['total_issues'] = data.get('total_issues', 0.0)
+
+			for sla_status in self.sla_status_map.values():
+				value = flt(data.get(sla_status), 0.0)
+				row[sla_status] = value
+
+			for metric in self.metrics:
+				value = flt(data.get(scrub(metric)), 0.0)
+				row[scrub(metric)] = value
+
+			self.data.append(row)
+
+	def get_summary_data(self):
+		self.issue_summary_data = frappe._dict()
+
+		for d in self.entries:
+			status = d.status
+			agreement_status = scrub(d.agreement_status)
+
+			if self.filters.based_on == 'Assigned To':
+				if d._assign:
+					for entry in json.loads(d._assign):
+						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(status, 0.0)
+						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(agreement_status, 0.0)
+						self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault('total_issues', 0.0)
+						self.issue_summary_data[entry][status] += 1
+						self.issue_summary_data[entry][agreement_status] += 1
+						self.issue_summary_data[entry]['total_issues'] += 1
+
+			else:
+				field = self.field_map.get(self.filters.based_on)
+				value = d.get(field)
+				if not value:
+					value = _('Not Specified')
+
+				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(status, 0.0)
+				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(agreement_status, 0.0)
+				self.issue_summary_data.setdefault(value, frappe._dict()).setdefault('total_issues', 0.0)
+				self.issue_summary_data[value][status] += 1
+				self.issue_summary_data[value][agreement_status] += 1
+				self.issue_summary_data[value]['total_issues'] += 1
+
+		self.get_metrics_data()
+
+	def get_metrics_data(self):
+		issues = []
+
+		metrics_list = ['avg_response_time', 'avg_first_response_time', 'avg_hold_time',
+			'avg_resolution_time', 'avg_user_resolution_time']
+
+		for entry in self.entries:
+			issues.append(entry.name)
+
+		field = self.field_map.get(self.filters.based_on)
+
+		if issues:
+			if self.filters.based_on == 'Assigned To':
+				assignment_map = frappe._dict()
+				for d in self.entries:
+					if d._assign:
+						for entry in json.loads(d._assign):
+							for metric in metrics_list:
+								self.issue_summary_data.setdefault(entry, frappe._dict()).setdefault(metric, 0.0)
+
+							self.issue_summary_data[entry]['avg_response_time'] += d.get('avg_response_time') or 0.0
+							self.issue_summary_data[entry]['avg_first_response_time'] += d.get('first_response_time') or 0.0
+							self.issue_summary_data[entry]['avg_hold_time'] += d.get('total_hold_time') or 0.0
+							self.issue_summary_data[entry]['avg_resolution_time'] += d.get('resolution_time') or 0.0
+							self.issue_summary_data[entry]['avg_user_resolution_time'] += d.get('user_resolution_time') or 0.0
+
+							if not assignment_map.get(entry):
+								assignment_map[entry] = 0
+							assignment_map[entry] += 1
+
+				for entry in assignment_map:
+					for metric in metrics_list:
+						self.issue_summary_data[entry][metric] /= flt(assignment_map.get(entry))
+
+			else:
+				data = frappe.db.sql("""
+					SELECT
+						{0}, AVG(first_response_time) as avg_frt,
+						AVG(avg_response_time) as avg_resp_time,
+						AVG(total_hold_time) as avg_hold_time,
+						AVG(resolution_time) as avg_resolution_time,
+						AVG(user_resolution_time) as avg_user_resolution_time
+					FROM `tabIssue`
+					WHERE
+						name IN %(issues)s
+					GROUP BY {0}
+				""".format(field), {'issues': issues}, as_dict=1)
+
+				for entry in data:
+					value = entry.get(field)
+					if not value:
+						value = _('Not Specified')
+
+					for metric in metrics_list:
+						self.issue_summary_data.setdefault(value, frappe._dict()).setdefault(metric, 0.0)
+
+					self.issue_summary_data[value]['avg_response_time'] = entry.get('avg_resp_time') or 0.0
+					self.issue_summary_data[value]['avg_first_response_time'] = entry.get('avg_frt') or 0.0
+					self.issue_summary_data[value]['avg_hold_time'] = entry.get('avg_hold_time') or 0.0
+					self.issue_summary_data[value]['avg_resolution_time'] = entry.get('avg_resolution_time') or 0.0
+					self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0
+
+	def get_chart_data(self):
+		if not self.data:
+			return None
+
+		labels = []
+		open_issues = []
+		replied_issues = []
+		resolved_issues = []
+		closed_issues = []
+
+		entity = self.filters.based_on
+		entity_field = self.field_map.get(entity)
+		if entity == 'Assigned To':
+			entity_field = 'user'
+
+		for entry in self.data:
+			labels.append(entry.get(entity_field))
+			open_issues.append(entry.get('open'))
+			replied_issues.append(entry.get('replied'))
+			resolved_issues.append(entry.get('resolved'))
+			closed_issues.append(entry.get('closed'))
+
+		self.chart = {
+			'data': {
+				'labels': labels[:30],
+				'datasets': [
+					{
+						'name': 'Open',
+						'values': open_issues[:30]
+					},
+					{
+						'name': 'Replied',
+						'values': replied_issues[:30]
+					},
+					{
+						'name': 'Resolved',
+						'values': resolved_issues[:30]
+					},
+					{
+						'name': 'Closed',
+						'values': closed_issues[:30]
+					}
+				]
+			},
+			'type': 'bar',
+			'barOptions': {
+				'stacked': True
+			}
+		}
+
+	def get_report_summary(self):
+		if not self.data:
+			return None
+
+		open_issues = 0
+		replied = 0
+		resolved = 0
+		closed = 0
+
+		for entry in self.data:
+			open_issues += entry.get('open')
+			replied += entry.get('replied')
+			resolved += entry.get('resolved')
+			closed += entry.get('closed')
+
+		self.report_summary = [
+			{
+				'value': open_issues,
+				'indicator': 'Red',
+				'label': _('Open'),
+				'datatype': 'Int',
+			},
+			{
+				'value': replied,
+				'indicator': 'Grey',
+				'label': _('Replied'),
+				'datatype': 'Int',
+			},
+			{
+				'value': resolved,
+				'indicator': 'Green',
+				'label': _('Resolved'),
+				'datatype': 'Int',
+			},
+			{
+				'value': closed,
+				'indicator': 'Green',
+				'label': _('Closed'),
+				'datatype': 'Int',
+			}
+		]
+