[ADD] HELPDESK: Added kanban view for helpdesk

This commit is contained in:
Horilla
2025-08-18 12:41:46 +05:30
parent e42e66ae80
commit dbe7bcd5d5
6 changed files with 260 additions and 12 deletions

View File

@@ -5,6 +5,7 @@ from base.methods import is_reportingmanager
from helpdesk.filter import TicketFilter, TicketReGroup
from helpdesk.models import TICKET_STATUS, Ticket
from horilla_views.cbv_methods import login_required
from horilla_views.generic.cbv.kanban import HorillaKanbanView
from horilla_views.generic.cbv.pipeline import Pipeline
from horilla_views.generic.cbv.views import (
HorillaListView,
@@ -39,6 +40,7 @@ class TicketPipelineNav(HorillaNavView):
filter_body_template = "cbv/pipeline/ticket_filter_form.html"
filter_instance = TicketFilter()
filter_form_context_name = "form"
group_by_fields = [
("employee_id", "Owner"),
("ticket_type", "Ticket Type"),
@@ -48,6 +50,7 @@ class TicketPipelineNav(HorillaNavView):
("assigned_to", "Assigner"),
("employee_id__employee_work_info__company_id", "Company"),
]
actions = [
{
"action": "Archive",
@@ -88,6 +91,25 @@ class TicketPipelineNav(HorillaNavView):
data-target="#objectCreateModal"
"""
self.view_types = [
{
"type": "list",
"icon": "list-outline",
"url": f'{reverse_lazy("ticket-tab")}?view_type=list',
"attrs": f"""
title ='List'
""",
},
{
"type": "card",
"icon": "grid-outline",
"url": f'{reverse_lazy("ticket-tab")}?view_type=card',
"attrs": f"""
title ='Card'
""",
},
]
@method_decorator(login_required, name="dispatch")
class TicketTabView(HorillaTabView):
@@ -98,16 +120,21 @@ class TicketTabView(HorillaTabView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
view_type = self.request.GET.get("view_type", "list")
url = reverse("ticket-tab-card")
if view_type == "list":
url = reverse("ticket-tab-list")
self.tabs = [
{
"title": "My Tickets",
# "url":f'{ reverse("ticket-pipeline-view")}?ticket_tab=my_tickets&',
"url": f'{ reverse("ticket-tab-list")}?ticket_tab=my_tickets&',
"url": f"{url}?ticket_tab=my_tickets",
},
{
"title": "Suggested Tickets",
# "url":f'{ reverse("ticket-pipeline-view")}?ticket_tab=suggested_tickets&',
"url": f'{ reverse("ticket-tab-list")}?ticket_tab=suggested_tickets&',
"url": f"{url}?ticket_tab=suggested_tickets",
},
]
@@ -118,7 +145,7 @@ class TicketTabView(HorillaTabView):
{
"title": "All Tickets",
# "url":f'{ reverse("ticket-pipeline-view")}?ticket_tab=all_tickets&',
"url": f'{ reverse("ticket-tab-list")}?ticket_tab=all_tickets&',
"url": f"{url}?ticket_tab=all_tickets",
}
)
@@ -167,6 +194,7 @@ class TicketListView(HorillaListView):
model = Ticket
filter_class = TicketFilter
filter_keys_to_remove = ["ticket_tab", "view_type"]
# custom_empty_template = "cbv/pipeline/empty_list.html"
bulk_update_fields = [
"ticket_type",
@@ -197,6 +225,9 @@ class TicketListView(HorillaListView):
def get_queryset(self):
queryset = super().get_queryset()
if self.request.GET.get("is_active") != "false":
self.queryset = self.queryset.filter(is_active=True)
ticket_tab = self.request.GET.get("ticket_tab", "my_tickets")
if ticket_tab == "my_tickets":
return queryset.filter(employee_id=self.request.user.employee_get)
@@ -227,3 +258,64 @@ class TicketListView(HorillaListView):
elif ticket_tab == "all_tickets":
return queryset.all()
class TicketCardView(HorillaKanbanView):
model = Ticket
filter_class = TicketFilter
group_key = "status"
records_per_page = 10
show_kanban_confirmation = False
filter_keys_to_remove = ["ticket_tab", "view_type"]
details = {
"title": "{title} ({ticket_type__prefix}-{pk})",
"Owner": "{employee_id__get_full_name}",
"Priority": "{get_priority_stars}",
}
kanban_attrs = """
onclick="window.location.href = `{get_ticket_detail_url}`"
"""
action_method = "kanban_action_method"
def get_queryset(self):
self.queryset = super().get_queryset()
if self.request.GET.get("is_active") != "false":
self.queryset = self.queryset.filter(is_active=True)
ticket_tab = self.request.GET.get("ticket_tab", "my_tickets")
if ticket_tab == "my_tickets":
self.queryset = self.queryset.filter(
employee_id=self.request.user.employee_get
)
return self.queryset
elif ticket_tab == "suggested_tickets":
employee = self.request.user.employee_get
qs_cpy = self.queryset
queryset = self.queryset.none()
if hasattr(employee, "employee_work_info"):
work_info = employee.employee_work_info
department = work_info.department_id
job_position = work_info.job_position_id
if department:
queryset |= qs_cpy.filter(
raised_on=department.id, assigning_type="department"
)
if job_position:
queryset |= qs_cpy.filter(
raised_on=job_position.id, assigning_type="job_position"
)
queryset |= qs_cpy.filter(
raised_on=employee.id, assigning_type="individual"
)
self.queryset = queryset.distinct()
return self.queryset
return self.queryset

View File

@@ -329,6 +329,20 @@ class Ticket(HorillaModel):
{"ticket": self, "tab": tab_name, "claim_request": claim_request},
)
def kanban_action_method(self):
"""
This method is used to get the ticket kanban actions
"""
request = getattr(_thread_locals, "request", None)
tab_name = request.GET.get("ticket_tab", "my_tickets")
claim_request = self.claimrequest_set.filter(
employee_id=request.user.employee_get
).first()
return render_template(
"cbv/pipeline/kanban_action_method.html",
{"ticket": self, "tab": tab_name, "claim_request": claim_request},
)
def get_status_col(self):
"""
This method is used to get the status column
@@ -461,7 +475,7 @@ class Attachment(HorillaModel):
def save(self, *args, **kwargs):
self.get_file_format()
super().save(self, *args, **kwargs)
super().save(*args, **kwargs)
def __str__(self):
return os.path.basename(self.file.name)

View File

@@ -0,0 +1,94 @@
{% load static i18n helpdeskfilters %}
{% if tab != 'my_tickets' %}
{% if perms.helpdesk.change_ticket or perms.helpdesk.change_claimrequest or request.user.employee_get|is_department_manager:ticket %}
<li>
<p
class="w-full text-left px-3 py-1 hover:text-primary-600 font-semibold"
>
<a
hx-get = "{% url "view-ticket-claim-request" ticket.id %}"
hx-target="#objectDetailsModalTarget"
data-toggle="oh-modal-toggle"
data-target="#objectDetailsModal"
>
{% trans "Claim Requests" %}
</a>
</p>
</li>
{% else %}
{% if claim_request or request.user.employee_get in ticket.assigned_to.all %}
{% else %}
<li>
<p
class="w-full text-left px-3 py-1 hover:text-primary-600 font-semibold"
>
<a
href = "{% url 'claim-ticket' ticket.id %}"
>
{% trans "Claim" %}
</a>
</p>
</li>
{% endif %}
{% endif %}
{% endif %}
{% if perms.helpdesk.change_ticket or request.user.employee_get|is_department_manager:ticket %}
<li>
<p
class="w-full text-left px-3 py-1 hover:text-primary-600 font-semibold"
>
<a
hx-get = "{% url 'ticket-update' ticket.id %}"
hx-target="#objectCreateModalTarget"
data-toggle="oh-modal-toggle"
data-target="#objectCreateModal"
>
{% trans "Edit" %}
</a>
</p>
</li>
{% endif %}
{% if perms.helpdesk.change_ticket or request.user.employee_get|is_department_manager:ticket %}
<li>
<p
class="w-full text-left px-3 py-1 hover:text-primary-600 font-semibold"
>
<a
hx-get = "{% url 'ticket-archive' ticket.id %}"
hx-swap = "outerHTML"
{% if ticket.is_active %}
hx-confirm = "Do you want to archive this ticket?"
{% else %}
hx-confirm = "Do you want to Un-archive this ticket?"
{% endif %}
hx-on-htmx-after-request="$('.reload-record').click()"
>
{% if ticket.is_active %}
{% trans "Archive" %}
{% else %}
{% trans 'Un-archive' %}
{% endif %}
</a>
</p>
</li>
{% endif %}
{% if perms.helpdesk.delete_ticket %}
{% if ticket.status == 'new' %}
<li>
<p
class="w-full text-left px-3 py-1 hover:text-primary-600 font-semibold"
>
<a
href = "{% url 'ticket-delete' ticket.id %}"
onsubmit="return confirm('{% trans 'Are you sure you want to delete this Ticket?' %}');"
>
{% trans 'Delete' %}
</a>
</p>
</li>
{% endif %}
{% endif %}

View File

@@ -1,10 +1,30 @@
{% extends "index.html" %}
{% load static i18n %}
{% include "generic/horilla_section.html" %}
{% block content %}
{% include "generic/components.html" %}
<div
class="oh-checkpoint-badge mb-2"
id="selectedTickets"
data-ids="[]"
data-clicked=""
style="display: none"
></div>
{% for path in script_static_paths %}
<script src="{% static path %}"></script>
{% endfor %}
<div
class="oh-checkpoint-badge mb-2"
id="selectedInstances"
data-ids="[]"
data-clicked=""
style="display: none"
></div>
{% if nav_url %}
<div hx-get="{{nav_url}}?{{request.GET.urlencode}}" hx-trigger="load">
<div class="mt-5 oh-wrapper animated-background" style="height: 80px"></div>
</div>
{% endif %}
{% if view_url %}
<div class="oh-wrapper" id="pipelineContainer">
<div class="mt-4 animated-background" style="height: 600px"></div>
</div>
{% endif %}
{% endblock content %}

View File

@@ -77,6 +77,7 @@ urlpatterns = [
path("get-ticket-tabs", pipeline.TicketTabView.as_view(), name="get-ticket-tabs"),
path("ticket-tab/", pipeline.TicketTabView.as_view(), name="ticket-tab"),
path("ticket-tab-list/", pipeline.TicketListView.as_view(), name="ticket-tab-list"),
path("ticket-tab-card/", pipeline.TicketCardView.as_view(), name="ticket-tab-card"),
# path("ticket-pipeline-view/", pipeline.TicketPipelineTabView.as_view(), name="ticket-pipeline-view"),
path(
"ticket-create",
@@ -246,4 +247,9 @@ urlpatterns = [
name="delete-ticket-document",
),
path("load-faqs/", views.load_faqs, name="load-faqs"),
path(
"ticket-file-upload/<int:id>/",
views.ticket_file_upload,
name="ticket-file-upload",
),
]

View File

@@ -1863,3 +1863,25 @@ def load_faqs(request):
"catagories": category_lookup,
},
)
@login_required
@hx_request_required
def ticket_file_upload(request, id):
"""
This function is used to upload files to the ticket.
"""
ticket = Ticket.objects.get(id=id)
if request.method == "POST":
files = request.FILES.getlist("file")
for file in files:
a_form = AttachmentForm({"file": file, "ticket": ticket, "comment": None})
a_form.save()
messages.success(request, _("File(s) uploaded successfully."))
return render(
request,
"helpdesk/ticket/ticket_detail.html",
{"ticket": ticket, "attachments": ticket.ticket_attachment.all()},
)