diff --git a/desk/src/App.vue b/desk/src/App.vue
index 37c20a52a..a56b0e0e1 100644
--- a/desk/src/App.vue
+++ b/desk/src/App.vue
@@ -2,16 +2,17 @@
+
diff --git a/desk/src/pages/TicketNew.vue b/desk/src/pages/TicketNew.vue
index a8c11d8f5..a675f3070 100644
--- a/desk/src/pages/TicketNew.vue
+++ b/desk/src/pages/TicketNew.vue
@@ -28,12 +28,17 @@
-
+
+
+ Subject
+ *
+
+
+
{
const preferKnowledgeBase = computed(
() => !!parseInt(config.value.prefer_knowledge_base)
);
+ const isFeedbackMandatory = computed(
+ () => !!parseInt(config.value.is_feedback_mandatory)
+ );
socket.on("helpdesk:settings-updated", () => configRes.reload());
@@ -29,5 +32,6 @@ export const useConfigStore = defineStore("config", () => {
preferKnowledgeBase,
isSetupComplete,
skipEmailWorkflow,
+ isFeedbackMandatory,
};
});
diff --git a/desk/src/types.ts b/desk/src/types.ts
index a4d9feb13..97d40f0fc 100644
--- a/desk/src/types.ts
+++ b/desk/src/types.ts
@@ -192,6 +192,7 @@ export interface EmailAccount {
api_key?: string;
api_secret?: string;
password?: string;
+ frappe_mail_site?: string;
enable_outgoing?: boolean;
enable_incoming?: boolean;
default_outgoing?: boolean;
diff --git a/helpdesk/api/config.py b/helpdesk/api/config.py
index 1772ccf88..a431ea1df 100644
--- a/helpdesk/api/config.py
+++ b/helpdesk/api/config.py
@@ -8,6 +8,7 @@ def get_config():
"prefer_knowledge_base",
"setup_complete",
"skip_email_workflow",
+ "is_feedback_mandatory",
]
res = frappe.get_value(doctype="HD Settings", fieldname=fields, as_dict=True)
return res
diff --git a/helpdesk/api/settings.py b/helpdesk/api/settings.py
index 66d9034af..f7eaf7d01 100644
--- a/helpdesk/api/settings.py
+++ b/helpdesk/api/settings.py
@@ -29,13 +29,15 @@ def create_email_account(data):
**service_config,
}
)
- email_doc.append(
- "imap_folder", {"append_to": "HD Ticket", "folder_name": "INBOX"}
- )
if service == "Frappe Mail":
email_doc.api_key = data.get("api_key")
email_doc.api_secret = data.get("api_secret")
+ email_doc.frappe_mail_site = data.get("frappe_mail_site")
+ email_doc.append_to = "HD Ticket"
else:
+ email_doc.append(
+ "imap_folder", {"append_to": "HD Ticket", "folder_name": "INBOX"}
+ )
email_doc.password = data.get("password")
# validate whether the credentials are correct
email_doc.get_incoming_server()
diff --git a/helpdesk/helpdesk/doctype/hd_agent/hd_agent.json b/helpdesk/helpdesk/doctype/hd_agent/hd_agent.json
index d5f6d0724..f3425c6bc 100644
--- a/helpdesk/helpdesk/doctype/hd_agent/hd_agent.json
+++ b/helpdesk/helpdesk/doctype/hd_agent/hd_agent.json
@@ -9,8 +9,7 @@
"user",
"agent_name",
"user_image",
- "is_active",
- "groups"
+ "is_active"
],
"fields": [
{
@@ -35,12 +34,6 @@
"in_list_view": 1,
"label": "Is Active"
},
- {
- "fieldname": "groups",
- "fieldtype": "Table",
- "label": "Groups",
- "options": "HD Team Item"
- },
{
"fetch_from": "user.user_image",
"fieldname": "user_image",
@@ -51,7 +44,7 @@
],
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2023-07-24 22:31:23.178626",
+ "modified": "2024-11-11 17:30:22.859253",
"modified_by": "Administrator",
"module": "Helpdesk",
"name": "HD Agent",
@@ -95,6 +88,5 @@
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
- "title_field": "agent_name",
- "track_changes": 1
+ "title_field": "agent_name"
}
\ No newline at end of file
diff --git a/helpdesk/helpdesk/doctype/hd_agent/hd_agent.py b/helpdesk/helpdesk/doctype/hd_agent/hd_agent.py
index 44a9936c0..4ecc6ccd2 100644
--- a/helpdesk/helpdesk/doctype/hd_agent/hd_agent.py
+++ b/helpdesk/helpdesk/doctype/hd_agent/hd_agent.py
@@ -21,156 +21,6 @@ def set_user_roles(self):
user.save()
- def on_update(self):
- if self.has_value_changed("is_active"):
- if not self.is_active:
- self.remove_from_support_rotations()
- else:
- self.add_to_support_rotations()
-
- if self.has_value_changed("groups") and self.is_active:
- previous = self.get_doc_before_save()
- if previous:
- for group in previous.groups:
- if not next(
- (g for g in self.groups if g.team == group.team),
- None,
- ):
- self.remove_from_support_rotations(group.team)
-
- self.add_to_support_rotations()
-
- def on_trash(self):
- self.remove_from_support_rotations()
-
- def add_to_support_rotations(self, group=None):
- """
- Add the hd_agent to the support rotation for the given group or all groups
- the hd_agent belongs to if hd_agent already added to the support roatation
- for a group, skip
-
- :param str group: Team name, defaults to None.
- """
- rule_docs = []
- if not group:
- # Add the hd_agent to the base support rotation
-
- rule_docs.append(
- frappe.get_doc(
- "Assignment Rule",
- frappe.get_doc("HD Settings").get_base_support_rotation(),
- )
- )
-
- # Add the hd_agent to the support rotation for each group they belong to
- if self.groups:
- for group in self.groups:
- try:
- team_assignment_rule = frappe.get_doc(
- "HD Team", group.team
- ).get_assignment_rule()
- rule_docs.append(
- frappe.get_doc(
- "Assignment Rule",
- team_assignment_rule,
- )
- )
- except frappe.DoesNotExistError:
- frappe.throw(
- frappe._(
- "Assignment Rule for HD Team {0} does not exist"
- ).format(group.team)
- )
- else:
- # check if the group is in self.groups
- if next(
- (group for group in self.groups if group["group_name"] == group), None
- ):
- rule_docs.append(
- frappe.get_doc(
- "Assignment Rule",
- frappe.get_doc("HD Team", group).get_assignment_rule(),
- )
- )
- else:
- frappe.throw(
- frappe._(
- "Agent {0} does not belong to team {1}".format(
- self.agent_name, group
- )
- )
- )
-
- for rule_doc in rule_docs:
- skip = False
- if rule_doc:
- if rule_doc.users and len(rule_doc.users) > 0:
- for user in rule_doc.users:
- if (
- user.user == self.user
- ): # if the user is already in the rule, skip
- skip = True
- break
- if skip:
- continue
-
- user_doc = frappe.get_doc(
- {"doctype": "Assignment Rule User", "user": self.user}
- )
- rule_doc.append("users", user_doc)
- rule_doc.disabled = False # enable the rule if it is disabled
- rule_doc.save(ignore_permissions=True)
-
- def remove_from_support_rotations(self, group=None):
- rule_docs = []
-
- if group:
- # remove the hd_agent from the support rotation for the given group
- rule_docs.append(
- frappe.get_doc(
- "Assignment Rule",
- frappe.get_doc("HD Team", group).get_assignment_rule(),
- )
- )
-
- else:
- # Remove the hd_agent from the base support rotation
- rule_docs.append(
- frappe.get_doc(
- "Assignment Rule",
- frappe.get_doc("HD Settings").get_base_support_rotation(),
- )
- )
-
- # Remove the hd_agent from the support rotation for each group they belong to
- for group in self.groups:
- rule_docs.append(
- frappe.get_doc(
- "Assignment Rule",
- frappe.get_doc("HD Team", group.team).get_assignment_rule(),
- )
- )
-
- for rule_doc in rule_docs:
- if rule_doc.users and len(rule_doc.users) > 0:
- for user in rule_doc.users:
- if user.user == self.user:
- if len(rule_doc.users) == 1:
- rule_doc.disabled = (
- True # disable the rule if there are no users left
- )
- rule_doc.remove(user)
- rule_doc.save()
-
- def in_group(self, group):
- """
- Check if agent is in the given group
- """
- if self.groups:
- return next((g for g in self.groups if g.team == group), False)
-
- return False
-
@frappe.whitelist()
def create_hd_agent(first_name, last_name, email, signature, team):
diff --git a/helpdesk/helpdesk/doctype/hd_settings/hd_settings.json b/helpdesk/helpdesk/doctype/hd_settings/hd_settings.json
index a01dbb9c4..5bf9407ef 100644
--- a/helpdesk/helpdesk/doctype/hd_settings/hd_settings.json
+++ b/helpdesk/helpdesk/doctype/hd_settings/hd_settings.json
@@ -28,6 +28,10 @@
"column_break_zxek",
"ticket_restrictions_section",
"allow_anyone_to_create_tickets",
+ "feedback_section",
+ "is_feedback_mandatory",
+ "section_break_duow",
+ "auto_update_status",
"workflow_tab",
"skip_email_workflow",
"instantly_send_email",
@@ -294,11 +298,34 @@
"fieldname": "allow_anyone_to_create_tickets",
"fieldtype": "Check",
"label": "Allow anyone to create tickets"
+ },
+ {
+ "fieldname": "section_break_duow",
+ "fieldtype": "Section Break"
+ },
+ {
+ "default": "0",
+ "description": "When enabled, the ticket status will automatically change to \"Replied\" whenever the agent responds to a ticket.\n",
+ "fieldname": "auto_update_status",
+ "fieldtype": "Check",
+ "label": "Auto Update Status"
+ },
+ {
+ "fieldname": "feedback_section",
+ "fieldtype": "Section Break",
+ "label": "Feedback"
+ },
+ {
+ "default": "0",
+ "description": "If enabled, the feedback dialog will be shown, when a user tries to close a ticket. \nNote: User can't close a ticket without giving a feedback.",
+ "fieldname": "is_feedback_mandatory",
+ "fieldtype": "Check",
+ "label": "Make Feedback Mandatory"
}
],
"issingle": 1,
"links": [],
- "modified": "2024-10-22 02:14:33.360809",
+ "modified": "2024-11-22 17:25:40.112881",
"modified_by": "Administrator",
"module": "Helpdesk",
"name": "HD Settings",
diff --git a/helpdesk/helpdesk/doctype/hd_team/hd_team.py b/helpdesk/helpdesk/doctype/hd_team/hd_team.py
index 900c80cb1..75fb85e7c 100644
--- a/helpdesk/helpdesk/doctype/hd_team/hd_team.py
+++ b/helpdesk/helpdesk/doctype/hd_team/hd_team.py
@@ -2,6 +2,8 @@
# For license information, please see license.txt
import frappe
+from frappe import _
+from frappe.core.doctype.version.version import get_diff
from frappe.exceptions import DoesNotExistError
from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists
@@ -12,8 +14,20 @@ class HDTeam(Document):
def rename_self(self, new_name: str):
self.rename(new_name)
+ # nosemgrep: frappe-semgrep-rules.rules.frappe-modifying-but-not-comitting-other-method
def after_insert(self):
self.create_assignment_rule()
+ assignment_rule_doc = frappe.get_doc("Assignment Rule", self.assignment_rule)
+
+ for user in self.users:
+ _user = user.get("user")
+ if not _user:
+ continue
+ assignment_rule_doc.append("users", {"user": _user})
+
+ if assignment_rule_doc.disabled and assignment_rule_doc.users:
+ assignment_rule_doc.disabled = False
+ assignment_rule_doc.save()
def after_rename(self, olddn, newdn, merge=False):
# Update the condition for the linked assignment rule
@@ -22,18 +36,28 @@ def after_rename(self, olddn, newdn, merge=False):
rule_doc.assign_condition = f"status == 'Open' and agent_group == '{newdn}'"
rule_doc.save(ignore_permissions=True)
- def on_trash(self):
+ def on_update(self):
+ self.update_support_rotations()
+ def on_trash(self):
# Deletes the assignment rule for this group
+ rule = self.assignment_rule
+ if not rule:
+ return
try:
- rule = self.get_assignment_rule()
- if rule:
- self.assignment_rule = ""
- self.save()
- frappe.get_doc("Assignment Rule", rule).delete()
+ frappe.delete_doc(
+ "Assignment Rule",
+ rule,
+ ignore_permissions=True,
+ force=True,
+ ignore_on_trash=True,
+ )
+ frappe.db.commit()
except DoesNotExistError:
- # TODO: Log this error
- pass
+ frappe.log_error(
+ title="Assignment Rule not found",
+ message=f"Assignment Rule {rule} not found",
+ )
def create_assignment_rule(self):
"""Creates the assignment rule for this group"""
@@ -72,3 +96,69 @@ def get_assignment_rule(self):
self.create_assignment_rule()
return self.assignment_rule
+
+ def update_support_rotations(self):
+ """
+ Update the support rotations for the hd_agent
+ # If agent removed, remove from the support rule of the team
+ # If agent added add to the support rule of the team and also, while adding remove from base Support Rotation
+ """
+ assg_rule_doc = frappe.get_doc("Assignment Rule", self.assignment_rule)
+ if not assg_rule_doc:
+ return
+
+ previous_doc = self.get_doc_before_save()
+ diff = get_diff(previous_doc, self)
+ if not diff:
+ return
+
+ if not diff.get("removed") and not diff.get("added"):
+ return
+
+ for user in diff.get("removed"):
+ self.update_assignment_rule_users(user, assg_rule_doc, action="remove")
+
+ for user in diff.get("added"):
+ self.update_assignment_rule_users(user, assg_rule_doc)
+
+ def update_assignment_rule_users(self, user, assignment_rule_doc, action="add"):
+ _user = user[1].get("user")
+ if not user:
+ frappe.throw(_("User Not found"))
+ return
+
+ if action == "add":
+ assignment_rule_doc.append("users", {"user": _user})
+ if assignment_rule_doc.disabled:
+ assignment_rule_doc.disabled = False
+ assignment_rule_doc.save()
+
+ # remove the user from the base assignment rule
+ base_assignment_rule = frappe.get_value(
+ "HD Settings", "HD Settings", "base_support_rotation"
+ )
+ base_assignment_rule = frappe.get_doc(
+ "Assignment Rule", base_assignment_rule
+ )
+ user_id = frappe.get_value(
+ "Assignment Rule User",
+ {"user": _user, "parent": base_assignment_rule.name},
+ )
+ if user_id:
+ frappe.delete_doc("Assignment Rule User", user_id)
+ else:
+ user_id = frappe.get_value(
+ "Assignment Rule User",
+ {"user": _user, "parent": assignment_rule_doc.name},
+ )
+ if not user_id:
+ return
+ frappe.delete_doc("Assignment Rule User", user_id)
+
+ # disable the assignment rule if there are no users
+ total_users_in_assignment_rule = frappe.db.count(
+ "Assignment Rule User", {"parent": assignment_rule_doc.name}
+ )
+ if total_users_in_assignment_rule == 0:
+ assignment_rule_doc.disabled = True
+ assignment_rule_doc.save()
diff --git a/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py b/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py
index da3acb747..daf302688 100644
--- a/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py
+++ b/helpdesk/helpdesk/doctype/hd_ticket/hd_ticket.py
@@ -191,9 +191,6 @@ def after_insert(self):
log_ticket_activity(self.name, "created this ticket")
capture_event("ticket_created")
publish_event("helpdesk:new-ticket", {"name": self.name})
- # create communication if we are not hitting the new ticket creation API
- if not self.via_customer_portal:
- self.create_communication_via_contact(self.description)
def on_update(self):
# flake8: noqa
@@ -346,14 +343,16 @@ def remove_assignment_if_not_in_team(self):
if not self.agent_group or (hasattr(self, "_assign") and not self._assign):
return
if self.has_value_changed("agent_group") and self.status == "Open":
- current_assigned_agent_doc = self.get_assigned_agent()
+ current_assigned_agent = self.get_assigned_agent()
+ if not current_assigned_agent:
+ return
+ is_agent_in_assigned_team = self.agent_in_assigned_team(
+ current_assigned_agent, self.agent_group
+ )
+
if (
- current_assigned_agent_doc
- and not current_assigned_agent_doc.in_group(self.agent_group)
- ) and frappe.get_doc(
- "Assignment Rule",
- frappe.get_doc("HD Team", self.agent_group).assignment_rule,
- ).users:
+ not is_agent_in_assigned_team
+ ) and self.users_present_in_team_assignment_rule():
clear_all_assignments("HD Ticket", self.name)
frappe.publish_realtime(
"helpdesk:update-ticket-assignee",
@@ -361,6 +360,39 @@ def remove_assignment_if_not_in_team(self):
after_commit=True,
)
+ def agent_in_assigned_team(self, agent, team):
+ return frappe.db.exists(
+ "HD Team Member",
+ {
+ "parent": team,
+ "user": agent,
+ },
+ )
+
+ def users_present_in_team_assignment_rule(self):
+ if not self.agent_group:
+ return False
+
+ assignment_rule = frappe.db.get_value(
+ "HD Team", self.agent_group, "assignment_rule"
+ )
+ if not assignment_rule:
+ return False
+
+ is_disabled = frappe.db.get_value(
+ "Assignment Rule", assignment_rule, "disabled"
+ )
+ if is_disabled:
+ return False
+
+ users = frappe.get_all(
+ "Assignment Rule User", filters={"parent": assignment_rule}
+ )
+ if not users:
+ return False
+
+ return True
+
@frappe.whitelist()
def assign_agent(self, agent):
assign({"assign_to": [agent], "doctype": "HD Ticket", "name": self.name})
@@ -386,8 +418,7 @@ def get_assigned_agent(self):
# TODO: temporary fix, remove this when only agents can be assigned to ticket
exists = frappe.db.exists("HD Agent", assignees[0])
if exists:
- agent_doc = frappe.get_doc("HD Agent", assignees[0])
- return agent_doc
+ return assignees[0]
assignees = get_assignees({"doctype": "HD Ticket", "name": self.name})
if len(assignees) > 0:
@@ -707,6 +738,10 @@ def on_communication_update(self, c):
self.first_responded_on = (
self.first_responded_on or frappe.utils.now_datetime()
)
+
+ if frappe.db.get_single_value("HD Settings", "auto_update_status"):
+ self.status = "Replied"
+
# Fetch description from communication if not set already. This might not be needed
# anymore as a communication is created when a ticket is created.
self.description = self.description or c.content
diff --git a/helpdesk/helpdesk/doctype/hd_ticket_template/api.py b/helpdesk/helpdesk/doctype/hd_ticket_template/api.py
index b46ac5e89..71da35d60 100644
--- a/helpdesk/helpdesk/doctype/hd_ticket_template/api.py
+++ b/helpdesk/helpdesk/doctype/hd_ticket_template/api.py
@@ -60,5 +60,6 @@ def get_fields(template: str, fetch: Literal["Custom Field", "DocField"]):
.join(QBFetch, JoinType.inner)
.on(QBFetch.fieldname == fields.fieldname)
.where(where_parent)
+ .orderby(fields.idx)
.run(as_dict=True)
)
diff --git a/helpdesk/patches.txt b/helpdesk/patches.txt
index 28ff52d9a..c04b156ed 100644
--- a/helpdesk/patches.txt
+++ b/helpdesk/patches.txt
@@ -17,3 +17,4 @@ helpdesk.helpdesk.doctype.hd_ticket.patches.replace_overdue_failed
helpdesk.patches.create_helpdesk_folder
helpdesk.helpdesk.doctype.hd_ticket.patches.feedback_in_master
helpdesk.helpdesk.doctype.hd_ticket.patches.first_responded_on
+helpdesk.patches.update_hd_team_users
diff --git a/helpdesk/patches/update_hd_team_users.py b/helpdesk/patches/update_hd_team_users.py
new file mode 100644
index 000000000..e162cb065
--- /dev/null
+++ b/helpdesk/patches/update_hd_team_users.py
@@ -0,0 +1,22 @@
+import frappe
+
+
+def execute():
+ teams = frappe.get_all("HD Team", pluck="name")
+ for team in teams:
+ existing_agents = frappe.get_all(
+ "HD Team Item", filters={"team": team}, pluck="parent"
+ ) # agents in HD Agent doctype
+ team_users = frappe.get_all(
+ "HD Team Member", filters={"parent": team}, pluck="user"
+ ) # agents in HD Team doctype
+
+ for agent in existing_agents:
+ is_agent_active = frappe.get_value("HD Agent", agent, "is_active")
+ if is_agent_active and agent not in team_users:
+ team_doc = (
+ frappe.get_doc("HD Team", team)
+ .append("users", {"user": agent})
+ .save()
+ )
+ print("Agent Added")