Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 46 additions & 14 deletions helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,23 +876,55 @@ def on_communication_update(self, c):
# be reopened.
# handle re opening tickets for email
if c.sent_or_received == "Received":
# check if agent has replied

if self.has_agent_replied:
self.status = self.ticket_reopen_status
# Check if sender is an agent (agent replying via email)
sender_is_agent = is_agent(c.sender)

if sender_is_agent:
# Agent replying via email - treat as helpdesk panel response
# Set first response date from communication timestamp if not set already
if not self.first_responded_on:
# Use communication_date if available, otherwise use current time
self.first_responded_on = c.communication_date or frappe.utils.now_datetime()

# If ticket is not resolved, change status to "Replied" or configured status
if self.status_category != "Resolved":
# TODO: remove this feature once we add automation feature
if frappe.db.get_single_value("HD Settings", "auto_update_status"):
self.status = frappe.db.get_single_value(
"HD Settings", "update_status_to"
)
else:
self.status = "Replied"
else:
self.status = self.default_open_status
# Customer reply - reopen ticket
if self.has_agent_replied:
self.status = self.ticket_reopen_status
else:
self.status = self.default_open_status
# If communication is outgoing, it must be a reply from agent
if c.sent_or_received == "Sent":
# Set first response date if not set already
self.first_responded_on = (
self.first_responded_on or frappe.utils.now_datetime()
)

# TODO: remove this feature once we add automation feature
if frappe.db.get_single_value("HD Settings", "auto_update_status"):
self.status = frappe.db.get_single_value(
"HD Settings", "update_status_to"
# Check if sender is an agent
sender_is_agent = is_agent(c.sender)

if sender_is_agent:
# Set first response date from communication timestamp if not set already
if not self.first_responded_on:
# Use communication_date if available, otherwise use current time
self.first_responded_on = c.communication_date or frappe.utils.now_datetime()

# If ticket is not resolved, change status to "Replied" or configured status
if self.status_category != "Resolved":
# TODO: remove this feature once we add automation feature
if frappe.db.get_single_value("HD Settings", "auto_update_status"):
self.status = frappe.db.get_single_value(
"HD Settings", "update_status_to"
)
else:
self.status = "Replied"
else:
# Non-agent outgoing communication
self.first_responded_on = (
self.first_responded_on or frappe.utils.now_datetime()
)

# Fetch description from communication if not set already. This might not be needed
Expand Down
95 changes: 95 additions & 0 deletions helpdesk/helpdesk/doctype/hd_ticket/test_hd_ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,101 @@ def test_resolve_closed_resolution_time(self):
ticket.save()
self.assertEqual(ticket.resolution_time, 30 * 60)

def test_agent_reply_via_communication(self):
"""
Test that when an agent replies via communication:
1. first_responded_on is set from communication timestamp
2. status changes to "Replied" if ticket is not resolved
3. Works for both Sent (panel) and Received (email) communications
"""
# Create a ticket
ticket = frappe.get_doc(get_ticket_obj())
ticket.insert()

# Ensure first_responded_on is not set
self.assertIsNone(ticket.first_responded_on)

# Test 1: Agent replying via email (Received)
communication_email = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"communication_medium": "Email",
"sent_or_received": "Received",
"sender": agent,
"content": "Agent reply via email to ticket",
"reference_doctype": "HD Ticket",
"reference_name": ticket.name,
"subject": f"Re: {ticket.subject}",
})
communication_email.insert(ignore_permissions=True)

# Reload ticket and verify
ticket.reload()
self.assertIsNotNone(ticket.first_responded_on)
self.assertEqual(ticket.status, "Replied")

# Test 2: Agent replying via panel (Sent) - subsequent reply
ticket.reload()
ticket.status = "Open"
ticket.save()

communication_panel = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"communication_medium": "Email",
"sent_or_received": "Sent",
"sender": agent,
"content": "Agent reply via panel",
"reference_doctype": "HD Ticket",
"reference_name": ticket.name,
"subject": f"Re: {ticket.subject}",
})
communication_panel.insert(ignore_permissions=True)

ticket.reload()
self.assertEqual(ticket.status, "Replied")

# Test 3: Customer reply after agent reply (Received from non-agent)
ticket.reload()
customer_communication = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"communication_medium": "Email",
"sent_or_received": "Received",
"sender": non_agent,
"content": "Customer reply",
"reference_doctype": "HD Ticket",
"reference_name": ticket.name,
"subject": f"Re: {ticket.subject}",
})
customer_communication.insert(ignore_permissions=True)

ticket.reload()
# Status should change to reopen status for customer reply
self.assertNotEqual(ticket.status, "Replied")

# Test 4: Resolved tickets don't change to Replied
ticket.reload()
ticket.status = "Resolved"
ticket.save()

communication_resolved = frappe.get_doc({
"doctype": "Communication",
"communication_type": "Communication",
"communication_medium": "Email",
"sent_or_received": "Received",
"sender": agent,
"content": "Agent reply after resolved",
"reference_doctype": "HD Ticket",
"reference_name": ticket.name,
"subject": f"Re: {ticket.subject}",
})
communication_resolved.insert(ignore_permissions=True)

ticket.reload()
# Status should remain Resolved, not change to Replied
self.assertEqual(ticket.status, "Resolved")

def tearDown(self):
remove_holidays()
frappe.db.set_single_value("HD Settings", "default_ticket_status", "Open")
Expand Down