376 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
 | |
| # For license information, please see license.txt
 | |
| 
 | |
| 
 | |
| import json
 | |
| 
 | |
| import frappe
 | |
| 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", "On Hold", "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",
 | |
| 			"First Response Due": "first_response_due",
 | |
| 			"Resolution Due": "resolution_due",
 | |
| 		}
 | |
| 
 | |
| 		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 self.issue_summary_data.items():
 | |
| 			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):
 | |
| 		self.chart = []
 | |
| 
 | |
| 		labels = []
 | |
| 		open_issues = []
 | |
| 		replied_issues = []
 | |
| 		on_hold_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"))
 | |
| 			on_hold_issues.append(entry.get("on_hold"))
 | |
| 			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": "On Hold", "values": on_hold_issues[:30]},
 | |
| 					{"name": "Resolved", "values": resolved_issues[:30]},
 | |
| 					{"name": "Closed", "values": closed_issues[:30]},
 | |
| 				],
 | |
| 			},
 | |
| 			"type": "bar",
 | |
| 			"barOptions": {"stacked": True},
 | |
| 		}
 | |
| 
 | |
| 	def get_report_summary(self):
 | |
| 		self.report_summary = []
 | |
| 
 | |
| 		open_issues = 0
 | |
| 		replied = 0
 | |
| 		on_hold = 0
 | |
| 		resolved = 0
 | |
| 		closed = 0
 | |
| 
 | |
| 		for entry in self.data:
 | |
| 			open_issues += entry.get("open")
 | |
| 			replied += entry.get("replied")
 | |
| 			on_hold += entry.get("on_hold")
 | |
| 			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": on_hold,
 | |
| 				"indicator": "Grey",
 | |
| 				"label": _("On Hold"),
 | |
| 				"datatype": "Int",
 | |
| 			},
 | |
| 			{
 | |
| 				"value": resolved,
 | |
| 				"indicator": "Green",
 | |
| 				"label": _("Resolved"),
 | |
| 				"datatype": "Int",
 | |
| 			},
 | |
| 			{
 | |
| 				"value": closed,
 | |
| 				"indicator": "Green",
 | |
| 				"label": _("Closed"),
 | |
| 				"datatype": "Int",
 | |
| 			},
 | |
| 		]
 |