mirror of
https://gitea.mueller.network/extern/django-helpdesk.git
synced 2024-11-21 23:43:11 +01:00
basic support for time spend of tikets and follow-ups
This commit is contained in:
parent
8c4e094705
commit
6ceb89a5cb
@ -14,7 +14,8 @@ class QueueAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(Ticket)
|
@admin.register(Ticket)
|
||||||
class TicketAdmin(admin.ModelAdmin):
|
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'
|
date_hierarchy = 'created'
|
||||||
list_filter = ('queue', 'assigned_to', 'status')
|
list_filter = ('queue', 'assigned_to', 'status')
|
||||||
|
|
||||||
@ -28,6 +29,9 @@ class TicketAdmin(admin.ModelAdmin):
|
|||||||
return ticket.submitter_email
|
return ticket.submitter_email
|
||||||
hidden_submitter_email.short_description = _('Submitter E-Mail')
|
hidden_submitter_email.short_description = _('Submitter E-Mail')
|
||||||
|
|
||||||
|
def time_spent(self, ticket):
|
||||||
|
return ticket.time_spent
|
||||||
|
|
||||||
|
|
||||||
class TicketChangeInline(admin.StackedInline):
|
class TicketChangeInline(admin.StackedInline):
|
||||||
model = TicketChange
|
model = TicketChange
|
||||||
@ -40,7 +44,8 @@ class AttachmentInline(admin.StackedInline):
|
|||||||
@admin.register(FollowUp)
|
@admin.register(FollowUp)
|
||||||
class FollowUpAdmin(admin.ModelAdmin):
|
class FollowUpAdmin(admin.ModelAdmin):
|
||||||
inlines = [TicketChangeInline, AttachmentInline]
|
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')
|
list_filter = ('user', 'date', 'new_status')
|
||||||
|
|
||||||
def ticket_get_ticket_for_url(self, obj):
|
def ticket_get_ticket_for_url(self, obj):
|
||||||
|
18
helpdesk/migrations/0024_time_spent.py
Normal file
18
helpdesk/migrations/0024_time_spent.py
Normal file
@ -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),
|
||||||
|
),
|
||||||
|
]
|
@ -17,6 +17,7 @@ from django.utils import timezone
|
|||||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import re
|
import re
|
||||||
|
import datetime
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -301,6 +302,17 @@ class Queue(models.Model):
|
|||||||
return u'%s <%s>' % (self.title, self.email_address)
|
return u'%s <%s>' % (self.title, self.email_address)
|
||||||
from_address = property(_from_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):
|
def prepare_permission_name(self):
|
||||||
"""Prepare internally the codename for the permission and store it in permission_name.
|
"""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.
|
:return: The codename that can be used to create a new Permission object.
|
||||||
@ -497,6 +509,17 @@ class Ticket(models.Model):
|
|||||||
default=mk_secret,
|
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):
|
def send(self, roles, dont_send_to=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Send notifications to everyone interested in this ticket.
|
Send notifications to everyone interested in this ticket.
|
||||||
@ -771,6 +794,11 @@ class FollowUp(models.Model):
|
|||||||
|
|
||||||
objects = FollowUpManager()
|
objects = FollowUpManager()
|
||||||
|
|
||||||
|
time_spent = models.DurationField(
|
||||||
|
help_text=_("Time spent on this follow up"),
|
||||||
|
blank=True, null=True
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('date',)
|
ordering = ('date',)
|
||||||
verbose_name = _('Follow-up')
|
verbose_name = _('Follow-up')
|
||||||
|
@ -22,7 +22,9 @@ class TicketSerializer(serializers.ModelSerializer):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
# fields = '__all__'
|
# 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):
|
def get_ticket(self, obj):
|
||||||
return (str(obj.id) + " " + obj.ticket)
|
return (str(obj.id) + " " + obj.ticket)
|
||||||
|
@ -46,6 +46,8 @@
|
|||||||
<dt><label for="id_new_status">New Status:</label></dt>
|
<dt><label for="id_new_status">New Status:</label></dt>
|
||||||
<dd>{{ form.new_status }}</dd>
|
<dd>{{ form.new_status }}</dd>
|
||||||
<p>If the status was changed, what was it changed to?</p>
|
<p>If the status was changed, what was it changed to?</p>
|
||||||
|
<dt><label for="id_time_spent">Time spent:</label></dt>
|
||||||
|
<dd>{{ form.time_spent }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<p><input class="btn btn-primary" type="submit" value="Submit"></p>{% csrf_token %}
|
<p><input class="btn btn-primary" type="submit" value="Submit"></p>{% csrf_token %}
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
<th>{% trans "Open" %}</th>
|
<th>{% trans "Open" %}</th>
|
||||||
<th>{% trans "Resolved" %}</th>
|
<th>{% trans "Resolved" %}</th>
|
||||||
<th>{% trans "Closed" %}</th>
|
<th>{% trans "Closed" %}</th>
|
||||||
|
<th>{% trans "Time spent" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -54,6 +55,7 @@
|
|||||||
<td>{% if queue.open %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td>
|
<td>{% if queue.open %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=1&status=2'>{% endif %}{{ queue.open }}{% if queue.open %}</a>{% endif %}</td>
|
||||||
<td>{% if queue.resolved %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td>
|
<td>{% if queue.resolved %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=3'>{% endif %}{{ queue.resolved }}{% if queue.resolved %}</a>{% endif %}</td>
|
||||||
<td>{% if queue.closed %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=4'>{% endif %}{{ queue.closed }}{% if queue.closed %}</a>{% endif %}</td>
|
<td>{% if queue.closed %}<a href='{{ hdlist }}?queue={{ queue.queue }}&status=4'>{% endif %}{{ queue.closed }}{% if queue.closed %}</a>{% endif %}</td>
|
||||||
|
<td>{{ queue.time_spent }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr><td colspan='6'>{% trans "There are no unassigned tickets." %}</td></tr>
|
<tr><td colspan='6'>{% trans "There are no unassigned tickets." %}</td></tr>
|
||||||
|
@ -46,6 +46,9 @@
|
|||||||
{% if followup.comment %}
|
{% if followup.comment %}
|
||||||
<p>{{ followup.comment|force_escape|urlizetrunc:50|num_to_link|linebreaksbr }}</p>
|
<p>{{ followup.comment|force_escape|urlizetrunc:50|num_to_link|linebreaksbr }}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if followup.time_spent %}
|
||||||
|
<small>{% trans "Time spent" %}: {{ followup.time_spent }}</small></p>
|
||||||
|
{% endif %}
|
||||||
{% for change in followup.ticketchange_set.all %}
|
{% for change in followup.ticketchange_set.all %}
|
||||||
{% if forloop.first %}<div class='changes'><ul>{% endif %}
|
{% if forloop.first %}<div class='changes'><ul>{% endif %}
|
||||||
<li>{% blocktrans with change.field as field and change.old_value as old_value and change.new_value as new_value %}Changed {{ field }} from {{ old_value }} to {{ new_value }}.{% endblocktrans %}</li>
|
<li>{% blocktrans with change.field as field and change.old_value as old_value and change.new_value as new_value %}Changed {{ field }} from {{ old_value }} to {{ new_value }}.{% endblocktrans %}</li>
|
||||||
@ -152,6 +155,11 @@
|
|||||||
<dd><input type='checkbox' name='public' value='1' checked='checked' /> {% trans 'Yes, make this update public.' %}</dd>
|
<dd><input type='checkbox' name='public' value='1' checked='checked' /> {% trans 'Yes, make this update public.' %}</dd>
|
||||||
<dd class='form_help_text'>{% trans "If this is public, the submitter will be e-mailed your comment or resolution." %}</dd>
|
<dd class='form_help_text'>{% trans "If this is public, the submitter will be e-mailed your comment or resolution." %}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<dt>
|
||||||
|
<label for='id_time_spent'>{% trans "Time spent" %}</label> <span class='form_optional'>{% trans "(Optional)" %}</span>
|
||||||
|
</dt>
|
||||||
|
<dd><input name='time_spent' type="time" /></dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<p id='ShowFurtherOptPara'><button class="btn btn-warning btn-sm" id='ShowFurtherEditOptions'>{% trans "Change Further Details »" %}</button></p>
|
<p id='ShowFurtherOptPara'><button class="btn btn-warning btn-sm" id='ShowFurtherEditOptions'>{% trans "Change Further Details »" %}</button></p>
|
||||||
|
@ -89,6 +89,10 @@
|
|||||||
<p><a data-toggle='tooltip' href='{% url 'helpdesk:ticket_dependency_add' ticket.id %}' title="{% trans "Click on 'Add Dependency', if you want to make this ticket dependent on another ticket. A ticket may not be closed until all tickets it depends on are closed." %}"><button type="button" class="btn btn-primary btn-sm"><i class="fas fa-child"></i> {% trans "Add Dependency" %}</button></a></p>
|
<p><a data-toggle='tooltip' href='{% url 'helpdesk:ticket_dependency_add' ticket.id %}' title="{% trans "Click on 'Add Dependency', if you want to make this ticket dependent on another ticket. A ticket may not be closed until all tickets it depends on are closed." %}"><button type="button" class="btn btn-primary btn-sm"><i class="fas fa-child"></i> {% trans "Add Dependency" %}</button></a></p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>{% trans "Total time spent" %}</th>
|
||||||
|
<td>{{ ticket.time_spent }}</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@ -222,6 +222,7 @@
|
|||||||
<th>{% trans "Created" %}</th>
|
<th>{% trans "Created" %}</th>
|
||||||
<th>{% trans "Due Date" %}</th>
|
<th>{% trans "Due Date" %}</th>
|
||||||
<th>{% trans "Owner" %}</th>
|
<th>{% trans "Owner" %}</th>
|
||||||
|
<th>{% trans "Time Spent" %}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{% if not server_side %}
|
{% if not server_side %}
|
||||||
@ -339,6 +340,7 @@
|
|||||||
{"data": "created"},
|
{"data": "created"},
|
||||||
{"data": "due_date"},
|
{"data": "due_date"},
|
||||||
{"data": "assigned_to"},
|
{"data": "assigned_to"},
|
||||||
|
{"data": "time_spent"},
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<td data-order='{{ ticket.created|date:"U" }}'><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
|
<td data-order='{{ ticket.created|date:"U" }}'><span title='{{ ticket.created|date:"r" }}'>{{ ticket.created|naturaltime }}</span></td>
|
||||||
<td data-order='{{ ticket.due_date|date:"U" }}'><span title='{{ ticket.due_date|date:"r" }}'>{{ ticket.due_date|naturaltime }}</span></td>
|
<td data-order='{{ ticket.due_date|date:"U" }}'><span title='{{ ticket.due_date|date:"r" }}'>{{ ticket.due_date|naturaltime }}</span></td>
|
||||||
<td>{{ ticket.get_assigned_to }}</td>
|
<td>{{ ticket.get_assigned_to }}</td>
|
||||||
|
<td>{{ ticket.time_spent }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -237,6 +237,7 @@ def followup_edit(request, ticket_id, followup_id):
|
|||||||
'comment': escape(followup.comment),
|
'comment': escape(followup.comment),
|
||||||
'public': followup.public,
|
'public': followup.public,
|
||||||
'new_status': followup.new_status,
|
'new_status': followup.new_status,
|
||||||
|
'time_spent': followup.time_spent,
|
||||||
})
|
})
|
||||||
|
|
||||||
ticketcc_string, show_subscribe = \
|
ticketcc_string, show_subscribe = \
|
||||||
@ -256,9 +257,13 @@ def followup_edit(request, ticket_id, followup_id):
|
|||||||
comment = form.cleaned_data['comment']
|
comment = form.cleaned_data['comment']
|
||||||
public = form.cleaned_data['public']
|
public = form.cleaned_data['public']
|
||||||
new_status = form.cleaned_data['new_status']
|
new_status = form.cleaned_data['new_status']
|
||||||
|
time_spent = form.cleaned_data['time_spent']
|
||||||
# will save previous date
|
# will save previous date
|
||||||
old_date = followup.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.
|
# keep old user if one did exist before.
|
||||||
if followup.user:
|
if followup.user:
|
||||||
new_followup.user = 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_year = int(request.POST.get('due_date_year', 0))
|
||||||
due_date_month = int(request.POST.get('due_date_month', 0))
|
due_date_month = int(request.POST.get('due_date_month', 0))
|
||||||
due_date_day = int(request.POST.get('due_date_day', 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
|
# NOTE: jQuery's default for dates is mm/dd/yy
|
||||||
# very US-centric but for now that's the only format supported
|
# very US-centric but for now that's the only format supported
|
||||||
# until we clean up code to internationalize a little more
|
# 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:
|
if owner is -1 and ticket.assigned_to:
|
||||||
owner = ticket.assigned_to.id
|
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):
|
if is_helpdesk_staff(request.user):
|
||||||
f.user = request.user
|
f.user = request.user
|
||||||
@ -1161,6 +1172,7 @@ def report_index(request):
|
|||||||
'open': queue.ticket_set.filter(status__in=[1, 2]).count(),
|
'open': queue.ticket_set.filter(status__in=[1, 2]).count(),
|
||||||
'resolved': queue.ticket_set.filter(status=3).count(),
|
'resolved': queue.ticket_set.filter(status=3).count(),
|
||||||
'closed': queue.ticket_set.filter(status=4).count(),
|
'closed': queue.ticket_set.filter(status=4).count(),
|
||||||
|
'time_spent': queue.time_spent
|
||||||
}
|
}
|
||||||
dash_tickets.append(dash_ticket)
|
dash_tickets.append(dash_ticket)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user