feat: record assignment on first response failure
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index d566f33..477ac72 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -211,6 +211,43 @@
self.assertEquals(issue.agreement_status, 'Fulfilled')
self.assertEquals(issue.resolution_date, frappe.flags.current_time)
+ def test_recording_of_assignment_on_first_reponse_failure(self):
+ from frappe.desk.form.assign_to import add as add_assignment
+
+ frappe.flags.current_time = get_datetime("2021-11-01 19:00")
+
+ issue = make_issue(frappe.flags.current_time, index=1)
+ create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
+ add_assignment({
+ 'doctype': issue.doctype,
+ 'name': issue.name,
+ 'assign_to': ['test@admin.com']
+ })
+ issue.reload()
+
+ # send a reply failing response SLA
+ frappe.flags.current_time = get_datetime("2021-11-02 15:00")
+ create_communication(issue.name, "test@admin.com", "Sent", frappe.flags.current_time)
+
+ # assert if a new timeline item has been added
+ # to record the assignment
+ comment = frappe.get_last_doc('Comment')
+ self.assertTrue('First Response SLA Failed' in comment.content)
+
+ def test_agreement_status_on_response(self):
+ frappe.flags.current_time = get_datetime("2021-11-01 19:00")
+
+ issue = make_issue(frappe.flags.current_time, index=1)
+ create_communication(issue.name, "test@example.com", "Received", frappe.flags.current_time)
+ self.assertTrue(issue.status == 'Open')
+
+ # send a reply within response SLA
+ frappe.flags.current_time = get_datetime("2021-11-02 11:00")
+ create_communication(issue.name, "test@admin.com", "Sent", frappe.flags.current_time)
+
+ issue.reload()
+ self.assertEquals(issue.first_responded_on, frappe.flags.current_time)
+ self.assertEquals(issue.agreement_status, 'Resolution Due')
class TestFirstResponseTime(TestSetUp):
# working hours used in all cases: Mon-Fri, 10am to 6pm
@@ -425,6 +462,7 @@
def create_issue_and_communication(issue_creation, first_responded_on):
issue = make_issue(issue_creation, index=1)
sender = create_user("test@admin.com")
+ frappe.flags.current_time = first_responded_on
create_communication(issue.name, sender.email, "Sent", first_responded_on)
issue.reload()
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 9ae2d64..19aa578 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -397,6 +397,12 @@
def is_open_status(status):
return status not in hold_statuses and status not in fulfillment_statuses
+ def set_first_response():
+ if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
+ doc.first_responded_on = now_time
+ if get_datetime(doc.get('first_responded_on')) > get_datetime(doc.get('response_by')):
+ record_assigned_users_on_failure(doc)
+
def calculate_hold_hours():
# In case issue was closed and after few days it has been opened
# The hold time should be calculated from resolution_date
@@ -408,9 +414,7 @@
doc.on_hold_since = None
if ((is_open_status(prev_status) and not is_open_status(doc.status)) or doc.flags.on_first_reply):
- # status changed from Open to something else
- if doc.meta.has_field("first_responded_on") and not doc.get('first_responded_on'):
- doc.first_responded_on = now_time
+ set_first_response()
# Open to Replied
if is_open_status(prev_status) and is_hold_status(doc.status):
@@ -688,6 +692,18 @@
doc.resolution_by = add_to_date(doc.resolution_by, seconds=round(doc.get('total_hold_time')))
+def record_assigned_users_on_failure(doc):
+ assigned_users = doc.get_assigned_users()
+ if assigned_users:
+ from frappe.utils import get_fullname
+ assigned_users = ', '.join((get_fullname(user) for user in assigned_users))
+ message = _(f'First Response SLA Failed by {assigned_users}')
+ doc.add_comment(
+ comment_type='Assigned',
+ text=message
+ )
+
+
def get_service_level_agreement_fields():
return [
{