367 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			367 lines
		
	
	
		
			9.9 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',
 | |
| 			}
 | |
| 		]
 |