brotherton-erpnext/erpnext/support/report/issue_summary/issue_summary.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

376 lines
10 KiB
Python
Raw Normal View History

# 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()
2022-03-28 13:22:46 +00:00
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,
}
)
2022-03-28 13:22:46 +00:00
elif self.filters.based_on == "Assigned To":
self.columns.append(
{"label": _("User"), "fieldname": "user", "fieldtype": "Link", "options": "User", "width": 200}
)
2022-03-28 13:22:46 +00:00
elif self.filters.based_on == "Issue Type":
self.columns.append(
{
"label": _("Issue Type"),
"fieldname": "issue_type",
"fieldtype": "Link",
"options": "Issue Type",
"width": 200,
}
)
2022-03-28 13:22:46 +00:00
elif self.filters.based_on == "Issue Priority":
self.columns.append(
{
"label": _("Issue Priority"),
"fieldname": "priority",
"fieldtype": "Link",
"options": "Issue Priority",
"width": 200,
}
)
2022-03-28 13:22:46 +00:00
2021-05-26 05:39:48 +00:00
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}
)
2022-03-28 13:22:46 +00:00
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}
)
2022-03-28 13:22:46 +00:00
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
2022-03-28 13:22:46 +00:00
)
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
2022-03-28 13:22:46 +00:00
)
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
2022-03-28 13:22:46 +00:00
)
def get_chart_data(self):
self.chart = []
labels = []
open_issues = []
replied_issues = []
2021-05-26 05:39:48 +00:00
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"))
2021-05-26 05:39:48 +00:00
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]},
2021-05-26 05:39:48 +00:00
{"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
2021-05-26 05:39:48 +00:00
on_hold = 0
resolved = 0
closed = 0
for entry in self.data:
open_issues += entry.get("open")
replied += entry.get("replied")
2021-05-26 05:39:48 +00:00
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",
},
{
2021-05-26 05:39:48 +00:00
"value": on_hold,
"indicator": "Grey",
2021-05-26 05:39:48 +00:00
"label": _("On Hold"),
"datatype": "Int",
},
{
"value": resolved,
"indicator": "Green",
"label": _("Resolved"),
"datatype": "Int",
},
{
"value": closed,
"indicator": "Green",
"label": _("Closed"),
"datatype": "Int",
},
]