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",
|
|
},
|
|
]
|