diff --git a/helpdesk/models.py b/helpdesk/models.py index 1781093f..a9ce9a1b 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1006,36 +1006,61 @@ class FollowUp(models.Model): def time_spent_calculation(self): "Returns timedelta according to rules settings." + + open_hours = helpdesk_settings.FOLLOWUP_TIME_SPENT_OPENING_HOURS + holidays = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS + exclude_statuses = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES + exclude_queues = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES + + # queryset for this ticket previous follow-ups + prev_fup_qs = self.ticket.followup_set.all() + if self.id: + # if the follow-up exist in DB, only keep previous follow-ups + prev_fup_qs = prev_fup_qs.filter(date__lt=self.date) + + # handle exclusions + + # extract previous status from follow-up or ticket for exclusion check + if exclude_statuses: + try: + prev_fup = prev_fup_qs.latest("date") + prev_status = prev_fup.new_status + except ObjectDoesNotExist: + prev_status = self.ticket.status + + # don't calculate status exclusions + if prev_status in exclude_statuses: + return datetime.timedelta(seconds=0) + + # find the previous queue for exclusion check + if exclude_queues: + try: + prev_fup_ids = prev_fup_qs.values_list('id', flat=True) + prev_queue_change = TicketChange.objects.filter(followup_id__in=prev_fup_ids, + field=_('Queue')).latest('id') + prev_queue = Queue.objects.get(pk=prev_queue_change.new_value) + prev_queue_slug = prev_queue.slug + except ObjectDoesNotExist: + prev_queue_slug = self.ticket.queue.slug + + # don't calculate queue exclusions + if prev_queue_slug in exclude_queues: + return datetime.timedelta(seconds=0) + + # no exclusion found + + time_spent_seconds = 0 # extract earliest from previous follow-up or ticket try: - prev_fup_qs = self.ticket.followup_set.all() - if self.id: - prev_fup_qs = prev_fup_qs.filter(id__lt=self.id) prev_fup = prev_fup_qs.latest("date") earliest = prev_fup.date except ObjectDoesNotExist: earliest = self.ticket.created - # extract previous status from follow-up or ticket - try: - prev_fup_qs = self.ticket.followup_set.exclude(new_status__isnull=True) - if self.id: - prev_fup_qs = prev_fup_qs.filter(id__lt=self.id) - prev_fup = prev_fup_qs.latest("date") - prev_status = prev_fup.new_status - except ObjectDoesNotExist: - prev_status = self.ticket.status - # latest time is current follow-up date latest = self.date - time_spent_seconds = 0 - open_hours = helpdesk_settings.FOLLOWUP_TIME_SPENT_OPENING_HOURS - holidays = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS - exclude_statuses = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES - exclude_queues = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES - # split time interval by days days = latest.toordinal() - earliest.toordinal() for day in range(days + 1): @@ -1054,9 +1079,7 @@ class FollowUp(models.Model): start_day_time = middle_day_time.replace(hour=0, minute=0, second=0) end_day_time = middle_day_time.replace(hour=23, minute=59, second=59, microsecond=999999) - if (start_day_time.strftime("%Y-%m-%d") not in holidays and - prev_status not in exclude_statuses and - self.ticket.queue.slug not in exclude_queues): + if start_day_time.strftime("%Y-%m-%d") not in holidays: time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) return datetime.timedelta(seconds=time_spent_seconds) diff --git a/helpdesk/templates/helpdesk/ticket.html b/helpdesk/templates/helpdesk/ticket.html index b4eb61db..8f0ede76 100644 --- a/helpdesk/templates/helpdesk/ticket.html +++ b/helpdesk/templates/helpdesk/ticket.html @@ -150,6 +150,9 @@
+
+
+
{{ form.due_date }}
diff --git a/helpdesk/tests/test_ticket_actions.py b/helpdesk/tests/test_ticket_actions.py index abf95c3f..0daf1f1d 100644 --- a/helpdesk/tests/test_ticket_actions.py +++ b/helpdesk/tests/test_ticket_actions.py @@ -7,6 +7,7 @@ from helpdesk import settings as helpdesk_settings from helpdesk.models import CustomField, Queue, Ticket from helpdesk.templatetags.ticket_to_link import num_to_link from helpdesk.user import HelpdeskUser +from django.utils.translation import gettext_lazy as _ try: # python 3 @@ -323,3 +324,46 @@ class TicketActionsTestCase(TestCase): ticket_1_follow_up, ticket_2_follow_up]) self.assertEqual(list(ticket_1.ticketcc_set.all()), [ticket_1_cc, ticket_2_cc]) + + def test_update_ticket_queue(self): + """Tests whether user can change the queue in the Respond to this ticket section.""" + + # log user in + self.loginUser() + + # create ticket + initial_data = { + 'title': 'Queue change ticket test', + 'queue': self.queue_public, + 'assigned_to': self.user, + 'status': Ticket.OPEN_STATUS, + } + ticket = Ticket.objects.create(**initial_data) + ticket_id = ticket.id + + # initial queue + self.assertEqual(ticket.queue, self.queue_public) + + # POST first follow-up with new queue + new_queue = Queue.objects.create( + title='New Queue', + slug='newqueue', + ) + post_data = { + 'comment': 'first follow-up in new queue', + 'queue': str(new_queue.id), + } + response = self.client.post(reverse('helpdesk:update', + kwargs={'ticket_id': ticket_id}), + post_data) + + # queue was correctly modified + ticket.refresh_from_db() + self.assertEqual(ticket.queue, new_queue) + + # ticket change was saved + latest_fup = ticket.followup_set.latest('date') + latest_ticketchange = latest_fup.ticketchange_set.latest('id') + self.assertEqual(latest_ticketchange.field, _('Queue')) + self.assertEqual(int(latest_ticketchange.old_value), self.queue_public.id) + self.assertEqual(int(latest_ticketchange.new_value), new_queue.id) \ No newline at end of file diff --git a/helpdesk/tests/test_time_spent_auto.py b/helpdesk/tests/test_time_spent_auto.py index 2cdaab91..56b25ad0 100644 --- a/helpdesk/tests/test_time_spent_auto.py +++ b/helpdesk/tests/test_time_spent_auto.py @@ -2,7 +2,10 @@ from datetime import datetime, timedelta from django.contrib.auth.hashers import make_password from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.test import TestCase, override_settings +from django.test.client import Client +from django.urls import reverse from helpdesk.models import FollowUp, Queue, Ticket from helpdesk import settings as helpdesk_settings import uuid @@ -33,6 +36,21 @@ class TimeSpentAutoTestCase(TestCase): is_active=True ) + self.client = Client() + + + def loginUser(self, is_staff=True): + """Create a staff user and login""" + User = get_user_model() + self.user = User.objects.create( + username='User_1', + is_staff=is_staff, + ) + self.user.set_password('pass') + self.user.save() + self.client.login(username='User_1', password='pass') + + def test_add_two_followups_time_spent_auto(self): """Tests automatic time_spent calculation.""" # activate automatic calculation @@ -251,4 +269,57 @@ class TimeSpentAutoTestCase(TestCase): self.assertEqual(ticket.time_spent.total_seconds(), 0.0) # Remove queues exclusion + helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = () + + def test_http_followup_time_spent_auto_exclude_queues(self): + """Tests automatic time_spent calculation queues exclusion with client""" + + # activate automatic calculation + helpdesk_settings.FOLLOWUP_TIME_SPENT_AUTO = True + helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = ('stop1', 'stop2') + + # make staff user + self.loginUser() + + # create queues + queues_sequence = ('new', 'stop1', 'resume1', 'stop2', 'resume2', 'end') + queues = dict() + for slug in queues_sequence: + queues[slug] = Queue.objects.create( + title=slug, + slug=slug, + ) + + # create ticket + initial_data = { + 'title': 'Queue change ticket test', + 'queue': queues['new'], + 'assigned_to': self.user, + 'status': Ticket.OPEN_STATUS, + 'created': datetime.strptime('2024-04-09T08:00:00+00:00', "%Y-%m-%dT%H:%M:%S%z") + } + ticket = Ticket.objects.create(**initial_data) + + # create a change queue follow-up every hour + # first follow-up created at the same time of the ticket without queue change + # new --1h--> stop1 --0h--> resume1 --1h--> stop2 --0h--> resume2 --1h--> end + for (i, queue) in enumerate(queues_sequence): + # create follow-up + post_data = { + 'comment': 'ticket in queue {}'.format(queue), + 'queue': queues[queue].id, + } + response = self.client.post(reverse('helpdesk:update', kwargs={ + 'ticket_id': ticket.id}), post_data) + latest_fup = ticket.followup_set.latest('id') + latest_fup.date = ticket.created + timedelta(hours=i) + latest_fup.time_spent = None + latest_fup.save() + + # total ticket time for followups is 5 hours + self.assertEqual(latest_fup.date - ticket.created, timedelta(hours=5)) + # calculated time spent with 2 hours exclusion is 3 hours + self.assertEqual(ticket.time_spent.total_seconds(), timedelta(hours=3).total_seconds()) + + # remove queues exclusion helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = () \ No newline at end of file diff --git a/helpdesk/update_ticket.py b/helpdesk/update_ticket.py index 711566ba..deca0b40 100644 --- a/helpdesk/update_ticket.py +++ b/helpdesk/update_ticket.py @@ -200,6 +200,7 @@ def update_ticket( owner=-1, ticket_title=None, priority=-1, + queue=-1, new_status=None, time_spent=None, due_date=None, @@ -213,6 +214,8 @@ def update_ticket( title = ticket.title if priority == -1: priority = ticket.priority + if queue == -1: + queue = ticket.queue.id if new_status is None: new_status = ticket.status if new_checklists is None: @@ -302,6 +305,14 @@ def update_ticket( c.save() ticket.priority = priority + if queue != ticket.queue.id: + c = f.ticketchange_set.create( + field=_('Queue'), + old_value=ticket.queue.id, + new_value=queue, + ) + ticket.queue_id = queue + if due_date != ticket.due_date: c = TicketChange( followup=f, diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py index e1cdc967..8abb9e7b 100644 --- a/helpdesk/views/staff.py +++ b/helpdesk/views/staff.py @@ -428,6 +428,7 @@ def view_ticket(request, ticket_id): 'form': form, 'active_users': users, 'priorities': Ticket.PRIORITY_CHOICES, + 'queues': queue_choices, 'preset_replies': PreSetReply.objects.filter( Q(queues=ticket.queue) | Q(queues__isnull=True)), 'ticketcc_string': ticketcc_string, @@ -566,6 +567,7 @@ def update_ticket_view(request, ticket_id, public=False): title = request.POST.get('title', '') owner = int(request.POST.get('owner', -1)) priority = int(request.POST.get('priority', ticket.priority)) + queue = int(request.POST.get('queue', ticket.queue.id)) # Check if a change happened on checklists new_checklists = {} @@ -589,6 +591,7 @@ def update_ticket_view(request, ticket_id, public=False): new_status == ticket.status, title == ticket.title, priority == int(ticket.priority), + queue == int(ticket.queue.id), due_date == ticket.due_date, (owner == -1) or (not owner and not ticket.assigned_to) or (owner and User.objects.get(id=owner) == ticket.assigned_to), @@ -605,6 +608,7 @@ def update_ticket_view(request, ticket_id, public=False): public = request.POST.get('public', False), owner = int(request.POST.get('owner', -1)), priority = int(request.POST.get('priority', -1)), + queue = int(request.POST.get('queue', -1)), new_status = new_status, time_spent = get_time_spent_from_request(request), due_date = get_due_date_from_request_or_ticket(request, ticket),