diff --git a/helpdesk/admin.py b/helpdesk/admin.py
index 58d5ef09..32bcfd9b 100644
--- a/helpdesk/admin.py
+++ b/helpdesk/admin.py
@@ -8,13 +8,22 @@ from helpdesk.models import CustomField
@admin.register(Queue)
class QueueAdmin(admin.ModelAdmin):
- list_display = ('title', 'slug', 'email_address', 'locale')
+ list_display = ('title', 'slug', 'email_address', 'locale', 'time_spent')
prepopulated_fields = {"slug": ("title",)}
+ def time_spent(self, q):
+ if q.dedicated_time:
+ return "{} / {}".format(q.time_spent, q.dedicated_time)
+ elif q.time_spent:
+ return q.time_spent
+ else:
+ return "-"
+
@admin.register(Ticket)
class TicketAdmin(admin.ModelAdmin):
- list_display = ('title', 'status', 'assigned_to', 'queue', 'hidden_submitter_email',)
+ list_display = ('title', 'status', 'assigned_to', 'queue',
+ 'hidden_submitter_email', 'time_spent')
date_hierarchy = 'created'
list_filter = ('queue', 'assigned_to', 'status')
@@ -28,6 +37,9 @@ class TicketAdmin(admin.ModelAdmin):
return ticket.submitter_email
hidden_submitter_email.short_description = _('Submitter E-Mail')
+ def time_spent(self, ticket):
+ return ticket.time_spent
+
class TicketChangeInline(admin.StackedInline):
model = TicketChange
@@ -40,7 +52,8 @@ class AttachmentInline(admin.StackedInline):
@admin.register(FollowUp)
class FollowUpAdmin(admin.ModelAdmin):
inlines = [TicketChangeInline, AttachmentInline]
- list_display = ('ticket_get_ticket_for_url', 'title', 'date', 'ticket', 'user', 'new_status')
+ list_display = ('ticket_get_ticket_for_url', 'title', 'date', 'ticket',
+ 'user', 'new_status', 'time_spent')
list_filter = ('user', 'date', 'new_status')
def ticket_get_ticket_for_url(self, obj):
diff --git a/helpdesk/migrations/0024_time_spent.py b/helpdesk/migrations/0024_time_spent.py
new file mode 100644
index 00000000..bbb0f22f
--- /dev/null
+++ b/helpdesk/migrations/0024_time_spent.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.5 on 2019-02-06 13:24
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('helpdesk', '0023_add_enable_notifications_on_email_events_to_ticket'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='followup',
+ name='time_spent',
+ field=models.DurationField(blank=True, help_text='Time spent on this follow up', null=True),
+ ),
+ ]
diff --git a/helpdesk/migrations/0025_queue_dedicated_time.py b/helpdesk/migrations/0025_queue_dedicated_time.py
new file mode 100644
index 00000000..d3dfd8d3
--- /dev/null
+++ b/helpdesk/migrations/0025_queue_dedicated_time.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.0.5 on 2019-02-19 21:53
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('helpdesk', '0024_time_spent'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='queue',
+ name='dedicated_time',
+ field=models.DurationField(blank=True, help_text='Time to be spent on this Queue in total', null=True),
+ ),
+ ]
diff --git a/helpdesk/models.py b/helpdesk/models.py
index e05c4b7d..9096f75d 100644
--- a/helpdesk/models.py
+++ b/helpdesk/models.py
@@ -17,6 +17,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, ugettext
from io import StringIO
import re
+import datetime
import uuid
@@ -275,6 +276,11 @@ class Queue(models.Model):
verbose_name=_('Default owner'),
)
+ dedicated_time = models.DurationField(
+ help_text=_("Time to be spent on this Queue in total"),
+ blank=True, null=True
+ )
+
def __str__(self):
return "%s" % self.title
@@ -301,6 +307,17 @@ class Queue(models.Model):
return u'%s <%s>' % (self.title, self.email_address)
from_address = property(_from_address)
+ @property
+ def time_spent(self):
+ """Return back total time spent on the ticket. This is calculated value
+ based on total sum from all FollowUps
+ """
+ total = datetime.timedelta(0)
+ for val in self.ticket_set.all():
+ if val.time_spent:
+ total = total + val.time_spent
+ return total
+
def prepare_permission_name(self):
"""Prepare internally the codename for the permission and store it in permission_name.
:return: The codename that can be used to create a new Permission object.
@@ -497,6 +514,17 @@ class Ticket(models.Model):
default=mk_secret,
)
+ @property
+ def time_spent(self):
+ """Return back total time spent on the ticket. This is calculated value
+ based on total sum from all FollowUps
+ """
+ total = datetime.timedelta(0)
+ for val in self.followup_set.all():
+ if val.time_spent:
+ total = total + val.time_spent
+ return total
+
def send(self, roles, dont_send_to=None, **kwargs):
"""
Send notifications to everyone interested in this ticket.
@@ -771,6 +799,11 @@ class FollowUp(models.Model):
objects = FollowUpManager()
+ time_spent = models.DurationField(
+ help_text=_("Time spent on this follow up"),
+ blank=True, null=True
+ )
+
class Meta:
ordering = ('date',)
verbose_name = _('Follow-up')
diff --git a/helpdesk/serializers.py b/helpdesk/serializers.py
index e5c7dc7d..abd95581 100644
--- a/helpdesk/serializers.py
+++ b/helpdesk/serializers.py
@@ -18,11 +18,14 @@ class TicketSerializer(serializers.ModelSerializer):
due_date = serializers.SerializerMethodField()
status = serializers.SerializerMethodField()
row_class = serializers.SerializerMethodField()
+ time_spent = serializers.SerializerMethodField()
class Meta:
model = Ticket
# fields = '__all__'
- fields = ('ticket', 'id', 'priority', 'title', 'queue', 'status', 'created', 'due_date', 'assigned_to', 'row_class')
+ fields = ('ticket', 'id', 'priority', 'title', 'queue', 'status',
+ 'created', 'due_date', 'assigned_to', 'row_class',
+ 'time_spent')
def get_ticket(self, obj):
return (str(obj.id) + " " + obj.ticket)
@@ -45,5 +48,8 @@ class TicketSerializer(serializers.ModelSerializer):
else:
return ("None")
+ def get_time_spent(self, obj):
+ return str(obj.time_spent)
+
def get_row_class(self, obj):
return (obj.get_priority_css_class)
diff --git a/helpdesk/templates/helpdesk/followup_edit.html b/helpdesk/templates/helpdesk/followup_edit.html
index 0afff6e8..bbd2f0ca 100644
--- a/helpdesk/templates/helpdesk/followup_edit.html
+++ b/helpdesk/templates/helpdesk/followup_edit.html
@@ -46,6 +46,8 @@
New Status:
{{ form.new_status }}
If the status was changed, what was it changed to?
+ Time spent:
+ {{ form.time_spent }}
{% csrf_token %}
diff --git a/helpdesk/templates/helpdesk/report_index.html b/helpdesk/templates/helpdesk/report_index.html
index 102dfed9..4d82f40c 100644
--- a/helpdesk/templates/helpdesk/report_index.html
+++ b/helpdesk/templates/helpdesk/report_index.html
@@ -45,6 +45,7 @@
{% trans "Open" %}
{% trans "Resolved" %}
{% trans "Closed" %}
+ {% trans "Time spent" %}
@@ -54,6 +55,7 @@
{% if queue.open %}{% endif %}{{ queue.open }}{% if queue.open %} {% endif %}
{% if queue.resolved %}{% endif %}{{ queue.resolved }}{% if queue.resolved %} {% endif %}
{% if queue.closed %}{% endif %}{{ queue.closed }}{% if queue.closed %} {% endif %}
+ {{ queue.time_spent }}{% if queue.dedicated_time %} / {{ queue.dedicated_time }}{% endif %}
{% empty %}
{% trans "There are no unassigned tickets." %}
diff --git a/helpdesk/templates/helpdesk/ticket.html b/helpdesk/templates/helpdesk/ticket.html
index fcac56e0..82b50935 100644
--- a/helpdesk/templates/helpdesk/ticket.html
+++ b/helpdesk/templates/helpdesk/ticket.html
@@ -46,6 +46,9 @@
{% if followup.comment %}
{{ followup.comment|force_escape|urlizetrunc:50|num_to_link|linebreaksbr }}
{% endif %}
+ {% if followup.time_spent %}
+ {% trans "Time spent" %}: {{ followup.time_spent }}
+ {% endif %}
{% for change in followup.ticketchange_set.all %}
{% if forloop.first %}
diff --git a/helpdesk/templates/helpdesk/ticket_list.html b/helpdesk/templates/helpdesk/ticket_list.html
index ea8a5d1b..46db483e 100644
--- a/helpdesk/templates/helpdesk/ticket_list.html
+++ b/helpdesk/templates/helpdesk/ticket_list.html
@@ -222,6 +222,7 @@
{% trans "Created" %}
{% trans "Due Date" %}
{% trans "Owner" %}
+ {% trans "Time Spent" %}
{% if not server_side %}
@@ -339,6 +340,7 @@
{"data": "created"},
{"data": "due_date"},
{"data": "assigned_to"},
+ {"data": "time_spent"},
]
});
})
diff --git a/helpdesk/templates/helpdesk/ticket_list_table.html b/helpdesk/templates/helpdesk/ticket_list_table.html
index 6b999cb7..fa4b9d69 100644
--- a/helpdesk/templates/helpdesk/ticket_list_table.html
+++ b/helpdesk/templates/helpdesk/ticket_list_table.html
@@ -12,6 +12,7 @@
{{ ticket.created|naturaltime }}
{{ ticket.due_date|naturaltime }}
{{ ticket.get_assigned_to }}
+ {{ ticket.time_spent }}
{% endfor %}
diff --git a/helpdesk/tests/test_time_spent.py b/helpdesk/tests/test_time_spent.py
new file mode 100644
index 00000000..a71c3ac9
--- /dev/null
+++ b/helpdesk/tests/test_time_spent.py
@@ -0,0 +1,76 @@
+from django.contrib.auth import get_user_model
+from django.contrib.sites.models import Site
+from django.core import mail
+from django.urls import reverse
+from django.test import TestCase
+from django.test.client import Client
+from helpdesk.models import Queue, Ticket, FollowUp
+from helpdesk import settings as helpdesk_settings
+from django.contrib.auth.models import User
+from django.contrib.auth.hashers import make_password
+import uuid
+import datetime
+
+try: # python 3
+ from urllib.parse import urlparse
+except ImportError: # python 2
+ from urlparse import urlparse
+
+from helpdesk.templatetags.ticket_to_link import num_to_link
+from helpdesk.views.staff import _is_my_ticket
+
+
+class TimeSpentTestCase(TestCase):
+
+ def setUp(self):
+ self.queue_public = Queue.objects.create(
+ title='Queue 1',
+ slug='q1',
+ allow_public_submission=True,
+ dedicated_time=datetime.timedelta(minutes=60)
+ )
+
+ self.ticket_data = {
+ 'title': 'Test Ticket',
+ 'description': 'Some Test Ticket',
+ }
+
+ ticket_data = dict(queue=self.queue_public, **self.ticket_data)
+ self.ticket = Ticket.objects.create(**ticket_data)
+
+ self.client = Client()
+
+ user1_kwargs = {
+ 'username': 'staff',
+ 'email': 'staff@example.com',
+ 'password': make_password('Test1234'),
+ 'is_staff': True,
+ 'is_superuser': False,
+ 'is_active': True
+ }
+ self.user = User.objects.create(**user1_kwargs)
+
+ def test_add_followup(self):
+ """Tests whether staff can delete tickets"""
+
+ message_id = uuid.uuid4().hex
+ followup = FollowUp.objects.create(
+ ticket=self.ticket,
+ date=datetime.datetime.now(),
+ title="Testing followup",
+ comment="Testing followup time spent",
+ public=True,
+ user=self.user,
+ new_status=1,
+ message_id=message_id,
+ time_spent=datetime.timedelta(minutes=30)
+ )
+
+ followup.save()
+
+ self.assertEqual(followup.time_spent.seconds, 1800)
+ self.assertEqual(self.ticket.time_spent.seconds, 1800)
+ self.assertEqual(self.queue_public.time_spent.seconds, 1800)
+ self.assertTrue(
+ self.queue_public.dedicated_time.seconds > self.queue_public.time_spent.seconds
+ )
diff --git a/helpdesk/views/staff.py b/helpdesk/views/staff.py
index 331b5f6c..3acc775d 100644
--- a/helpdesk/views/staff.py
+++ b/helpdesk/views/staff.py
@@ -237,6 +237,7 @@ def followup_edit(request, ticket_id, followup_id):
'comment': escape(followup.comment),
'public': followup.public,
'new_status': followup.new_status,
+ 'time_spent': followup.time_spent,
})
ticketcc_string, show_subscribe = \
@@ -256,9 +257,13 @@ def followup_edit(request, ticket_id, followup_id):
comment = form.cleaned_data['comment']
public = form.cleaned_data['public']
new_status = form.cleaned_data['new_status']
+ time_spent = form.cleaned_data['time_spent']
# will save previous date
old_date = followup.date
- new_followup = FollowUp(title=title, date=old_date, ticket=_ticket, comment=comment, public=public, new_status=new_status, )
+ new_followup = FollowUp(title=title, date=old_date, ticket=_ticket,
+ comment=comment, public=public,
+ new_status=new_status,
+ time_spent=time_spent)
# keep old user if one did exist before.
if followup.user:
new_followup.user = followup.user
@@ -469,6 +474,11 @@ def update_ticket(request, ticket_id, public=False):
due_date_year = int(request.POST.get('due_date_year', 0))
due_date_month = int(request.POST.get('due_date_month', 0))
due_date_day = int(request.POST.get('due_date_day', 0))
+ if request.POST.get("time_spent"):
+ (hours, minutes) = [int(f) for f in request.POST.get("time_spent").split(":")]
+ time_spent = timedelta(hours=hours, minutes=minutes)
+ else:
+ time_spent = None
# NOTE: jQuery's default for dates is mm/dd/yy
# very US-centric but for now that's the only format supported
# until we clean up code to internationalize a little more
@@ -523,7 +533,8 @@ def update_ticket(request, ticket_id, public=False):
if owner is -1 and ticket.assigned_to:
owner = ticket.assigned_to.id
- f = FollowUp(ticket=ticket, date=timezone.now(), comment=comment)
+ f = FollowUp(ticket=ticket, date=timezone.now(), comment=comment,
+ time_spent=time_spent)
if is_helpdesk_staff(request.user):
f.user = request.user
@@ -1161,6 +1172,8 @@ def report_index(request):
'open': queue.ticket_set.filter(status__in=[1, 2]).count(),
'resolved': queue.ticket_set.filter(status=3).count(),
'closed': queue.ticket_set.filter(status=4).count(),
+ 'time_spent': queue.time_spent,
+ 'dedicated_time': queue.dedicated_time
}
dash_tickets.append(dash_ticket)