diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 5beaea70f9..f5595974e6 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -242,7 +242,8 @@ scheduler_events = { "erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details", "erpnext.accounts.doctype.gl_entry.gl_entry.rename_gle_sle_docs", "erpnext.projects.doctype.project.project.hourly_reminder", - "erpnext.projects.doctype.project.project.collect_project_status" + "erpnext.projects.doctype.project.project.collect_project_status", + "erpnext.support.doctype.issue.issue.set_service_level_agreement_variance" ], "daily": [ "erpnext.stock.reorder_item.reorder_item", diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js index 147505012a..e2e699758a 100644 --- a/erpnext/support/doctype/issue/issue.js +++ b/erpnext/support/doctype/issue/issue.js @@ -5,7 +5,7 @@ frappe.ui.form.on("Issue", { refresh: function (frm) { - if (frm.doc.status !== "Closed") { + if (frm.doc.status !== "Closed" && frm.doc.agreement_fulfilled === "Ongoing") { if (frm.doc.service_level_agreement) { set_time_to_resolve_and_response(frm); } @@ -25,14 +25,14 @@ frappe.ui.form.on("Issue", { if (frm.doc.service_level_agreement) { frm.dashboard.clear_headline(); - let agreement_status = (frm.doc.agreement_status == "Fulfilled") ? + let agreement_fulfilled = (frm.doc.agreement_fulfilled == "Fulfilled") ? {"indicator": "green", "msg": "Service Level Agreement has been fulfilled"} : {"indicator": "red", "msg": "Service Level Agreement Failed"}; frm.dashboard.set_headline_alert( '
' + '
' + - ' ' + + ' ' + '
' + '
' ); @@ -112,8 +112,8 @@ frappe.ui.form.on("Issue", { function set_time_to_resolve_and_response(frm) { frm.dashboard.clear_headline(); - var time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_status); - var time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_status); + var time_to_respond = get_time_left(frm.doc.response_by, frm.doc.agreement_fulfilled); + var time_to_resolve = get_time_left(frm.doc.resolution_by, frm.doc.agreement_fulfilled); frm.dashboard.set_headline_alert( '
' + @@ -127,9 +127,9 @@ function set_time_to_resolve_and_response(frm) { ); } -function get_time_left(timestamp, agreement_status) { +function get_time_left(timestamp, agreement_fulfilled) { const diff = moment(timestamp).diff(moment()); const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : moment(0, 'seconds').format('HH:mm'); - let indicator = (diff_display == '00:00' && agreement_status != "Fulfilled") ? "red" : "green"; + let indicator = (diff_display == '00:00' && agreement_fulfilled != "Fulfilled") ? "red" : "green"; return {"diff_display": diff_display, "indicator": indicator}; } diff --git a/erpnext/support/doctype/issue/issue.json b/erpnext/support/doctype/issue/issue.json index 9639702e24..29cad627b4 100644 --- a/erpnext/support/doctype/issue/issue.json +++ b/erpnext/support/doctype/issue/issue.json @@ -21,9 +21,11 @@ "service_level_section", "service_level_agreement", "response_by", + "response_by_variance", "cb", - "agreement_status", + "agreement_fulfilled", "resolution_by", + "resolution_by_variance", "response", "mins_to_first_response", "first_responded_on", @@ -166,14 +168,6 @@ "options": "fa fa-pushpin", "read_only": 1 }, - { - "default": "Ongoing", - "fieldname": "agreement_status", - "fieldtype": "Select", - "label": "Agreement Status", - "options": "Ongoing\nFulfilled\nFailed", - "read_only": 1 - }, { "fieldname": "resolution_by", "fieldtype": "Datetime", @@ -317,11 +311,33 @@ "fieldname": "via_customer_portal", "fieldtype": "Check", "label": "Via Customer Portal" + }, + { + "default": "Ongoing", + "fieldname": "agreement_fulfilled", + "fieldtype": "Select", + "label": "Agreement Fulfilled", + "options": "Ongoing\nFulfilled\nFailed", + "read_only": 1 + }, + { + "description": "in hours", + "fieldname": "response_by_variance", + "fieldtype": "Int", + "label": "Response By Variance", + "read_only": 1 + }, + { + "description": "in hours", + "fieldname": "resolution_by_variance", + "fieldtype": "Int", + "label": "Resolution By Variance", + "read_only": 1 } ], "icon": "fa fa-ticket", "idx": 7, - "modified": "2019-05-03 14:36:19.117560", + "modified": "2019-05-10 22:31:09.391044", "modified_by": "Administrator", "module": "Support", "name": "Issue", diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py index 2b734404cc..965e53c0a5 100644 --- a/erpnext/support/doctype/issue/issue.py +++ b/erpnext/support/doctype/issue/issue.py @@ -68,22 +68,27 @@ class Issue(Document): status = frappe.db.get_value("Issue", self.name, "status") if self.status!="Open" and status =="Open" and not self.first_responded_on: self.first_responded_on = now() + if self.status=="Closed" and status !="Closed": self.resolution_date = now() - self.update_agreement_status() + if not frappe.db.get_value("Issue", self.name, "agreement_fulfilled") == "Ongoing": + self.set_service_level_agreement_variance(issue=self.name) + self.update_agreement_status() + if self.status=="Open" and status !="Open": # if no date, it should be set as None and not a blank string "", as per mysql strict config self.resolution_date = None def update_agreement_status(self): current_time = frappe.flags.current_time or now_datetime() - if self.service_level_agreement and self.agreement_status == "Ongoing": + if self.service_level_agreement and self.agreement_fulfilled == "Ongoing": response_time_diff = round(time_diff_in_hours(self.response_by, current_time), 2) resolution_time_diff = round(time_diff_in_hours(self.resolution_by, current_time), 2) + if response_time_diff < 0 or resolution_time_diff < 0: - self.agreement_status = "Failed" + self.agreement_fulfilled = "Failed" else: - self.agreement_status = "Fulfilled" + self.agreement_fulfilled = "Fulfilled" def create_communication(self): communication = frappe.new_doc("Communication") @@ -151,6 +156,9 @@ class Issue(Document): self.response_by = get_expected_time_for(parameter='response', service_level=priority, start_date_time=start_date_time) self.resolution_by = get_expected_time_for(parameter='resolution', service_level=priority, start_date_time=start_date_time) + self.response_by_variance = round(time_diff_in_hours(self.response_by, now_datetime())) + self.resolution_by_variance = round(time_diff_in_hours(self.resolution_by, now_datetime())) + @frappe.whitelist() def change_sla_priority(self, priority): self.set_response_and_resolution_time(priority=priority) @@ -222,16 +230,32 @@ def get_expected_time_for(parameter, service_level, start_date_time): return current_date_time def set_service_level_agreement_status(): - issues = frappe.get_list("Issue", filters={"status": "Open", "agreement_status": "Ongoing"}) + issues = frappe.get_list("Issue", filters={"status": "Open", "agreement_fulfilled": "Ongoing"}) for issue in issues: doc = frappe.get_doc("Issue", issue.name) - if doc.service_level_agreement and doc.agreement_status == "Ongoing": + if doc.service_level_agreement and doc.agreement_fulfilled == "Ongoing": response_time_diff = round(time_diff_in_hours(doc.response_by, now_datetime()), 2) resolution_time_diff = round(time_diff_in_hours(doc.resolution_by, now_datetime()), 2) if response_time_diff < 0 or resolution_time_diff < 0: - frappe.db.set_value("Issue", doc.name, "agreement_status", "Failed") + frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Failed") else: - frappe.db.set_value("Issue", doc.name, "agreement_status", "Fulfilled") + frappe.db.set_value("Issue", doc.name, "agreement_fulfilled", "Fulfilled") + +def set_service_level_agreement_variance(issue=None): + filters = {"status": "Open", "agreement_fulfilled": "Ongoing"} + + if issue: + filters = {"status": issue} + + issues = frappe.get_list("Issue", filters=filters) + for issue in issues: + doc = frappe.get_doc("Issue", issue.name) + if not doc.first_responded_on and not doc.agreement_fulfilled == "Ongoing": + variance = round(time_diff_in_hours(doc.response_by, now_datetime())) + frappe.db.set_value("Issue", doc.name, "response_by_variance", variance) + if not doc.resolution_dateand and not doc.agreement_fulfilled == "Ongoing": + variance = round(time_diff_in_hours(self.resolution_by, now_datetime())) + frappe.db.set_value("Issue", doc.name, "resolution_by_variance", variance) def get_list_context(context=None): return {