forked from extern/django-helpdesk
* Fixed svn:keywords
* Added escalation system that allows certain days to be excluded from escalation * New script to automatically create exclusions on user-defined days, eg easily add a years worth of saturdays & sundays to the exclusion system
This commit is contained in:
parent
0f3c979650
commit
274b9300f4
8
README
8
README
@ -122,4 +122,12 @@ LICENSE.JQUERY and LICENSE.NICEDIT for their respective license terms.
|
||||
This will run the escalation process hourly, using the 'Escalation Hours'
|
||||
setting for each queue to determine which tickets to escalate.
|
||||
|
||||
5. If you wish to exclude some days (eg, weekends) from escalation calculations, enter
|
||||
the dates manually via the Admin, or setup a cronjob to run
|
||||
scripts/create_escalation_exclusions.py on a regular basis:
|
||||
|
||||
0 0 * * 0 DJANGO_SETTINGS_MODULE='myproject.settings' python /path/to/helpdesk/scripts/create_escalation_exclusions.py --days saturday,sunday --verbose
|
||||
|
||||
This will, on a weekly basis, create exclusions for the coming weekend.
|
||||
|
||||
You're now up and running!
|
||||
|
18
models.py
18
models.py
@ -46,8 +46,7 @@ class Queue(models.Model):
|
||||
title = models.CharField(maxlength=100)
|
||||
slug = models.SlugField(help_text='This slug is used when building ticket ID\'s. Once set, try not to change it or e-mailing may get messy.')
|
||||
email_address = models.EmailField(blank=True, null=True, help_text='All outgoing e-mails for this queue will use this e-mail address. If you use IMAP or POP3, this shoul be the e-mail address for that mailbox.')
|
||||
escalate_hours = models.IntegerField(blank=True, null=True, help_text='For tickets which are not held, how often do you wish to increase their priority? Set to 0 for no escalation.')
|
||||
last_escalation = models.DateTimeField(blank=True, null=True, editable=False)
|
||||
escalate_days = models.IntegerField(blank=True, null=True, help_text='For tickets which are not held, how often do you wish to increase their priority? Set to 0 for no escalation.')
|
||||
|
||||
def _from_address(self):
|
||||
if not self.email_address:
|
||||
@ -134,6 +133,8 @@ class Ticket(models.Model):
|
||||
resolution = models.TextField(blank=True, null=True)
|
||||
|
||||
priority = models.IntegerField(choices=PRIORITY_CHOICES, default=3, blank=3)
|
||||
|
||||
last_escalation = models.DateTimeField(blank=True, null=True, editable=False)
|
||||
|
||||
def _get_assigned_to(self):
|
||||
""" Custom property to allow us to easily print 'Unassigned' if a
|
||||
@ -310,3 +311,16 @@ class PreSetReply(models.Model):
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.name
|
||||
|
||||
class EscalationExclusion(models.Model):
|
||||
queues = models.ManyToManyField(Queue, blank=True, null=True, help_text='Leave blank for this exclusion to b eaplied to all queues, or select those queues you wish to exclude with this entry.')
|
||||
|
||||
name = models.CharField(maxlength=100)
|
||||
|
||||
date = models.DateField(help_text='Date on which escalation should not happen')
|
||||
|
||||
class Admin:
|
||||
pass
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s' % self.name
|
||||
|
114
scripts/create_escalation_exclusions.py
Normal file
114
scripts/create_escalation_exclusions.py
Normal file
@ -0,0 +1,114 @@
|
||||
""" ..
|
||||
.,::;::::::
|
||||
..,::::::::,,,,::: Jutda Helpdesk - A Django
|
||||
.,,::::::,,,,,,,,,,,,,:: powered ticket tracker for
|
||||
.,::::::,,,,,,,,,,,,,,,,,,:;r. small enterprise
|
||||
.::::,,,,,,,,,,,,,,,,,,,,,,:;;rr.
|
||||
.:::,,,,,,,,,,,,,,,,,,,,,,,:;;;;;rr (c) Copyright 2008
|
||||
.:::,,,,,,,,,,,,,,,,,,,,,,,:;;;:::;;rr
|
||||
.:::,,,,,,,,,,,,,,,,,,,,. ,;;;::::::;;rr Jutda
|
||||
.:::,,,,,,,,,,,,,,,,,,. .:;;:::::::::;;rr
|
||||
.:::,,,,,,,,,,,,,,,. .;r;::::::::::::;r; All Rights Reserved
|
||||
.:::,,,,,,,,,,,,,,, .;r;;:::::::::::;;:.
|
||||
.:::,,,,,,,,,,,,,,,. .;r;;::::::::::::;:.
|
||||
.;:,,,,,,,,,,,,,,, .,;rr;::::::::::::;:. This software is released
|
||||
.,:,,,,,,,,,,,,,. .,:;rrr;;::::::::::::;;. under a limited-use license that
|
||||
:,,,,,,,,,,,,,..:;rrrrr;;;::::::::::::;;. allows you to freely download this
|
||||
:,,,,,,,:::;;;rr;;;;;;:::::::::::::;;, software from it's manufacturer and
|
||||
::::;;;;;;;;;;;:::::::::::::::::;;, use it yourself, however you may not
|
||||
.r;;;;:::::::::::::::::::::::;;;, distribute it. For further details, see
|
||||
.r;::::::::::::::::::::;;;;;:, the enclosed LICENSE file.
|
||||
.;;::::::::::::::;;;;;:,.
|
||||
.;;:::::::;;;;;;:,. Please direct people who wish to download this
|
||||
.r;;;;;;;;:,. software themselves to www.jutda.com.au.
|
||||
,,,..
|
||||
|
||||
$Id$
|
||||
|
||||
"""
|
||||
from datetime import datetime, timedelta, date
|
||||
from django.db.models import Q
|
||||
from helpdesk.models import EscalationExclusion, Queue
|
||||
import sys, getopt
|
||||
|
||||
day_names = {
|
||||
'monday': 0,
|
||||
'tuesday': 1,
|
||||
'wednesday': 2,
|
||||
'thursday': 3,
|
||||
'friday': 4,
|
||||
'saturday': 5,
|
||||
'sunday': 6,
|
||||
}
|
||||
|
||||
def create_exclusions(days, occurrences, verbose, queues):
|
||||
days = days.split(',')
|
||||
for day in days:
|
||||
day_name = day
|
||||
day = day_names[day]
|
||||
workdate = date.today()
|
||||
i = 0
|
||||
while i < occurrences:
|
||||
if day == workdate.weekday():
|
||||
if EscalationExclusion.objects.filter(date=workdate).count() == 0:
|
||||
esc = EscalationExclusion(name='Auto Exclusion for %s' % day_name, date=workdate)
|
||||
esc.save()
|
||||
|
||||
if verbose:
|
||||
print "Created exclusion for %s %s" % (day_name, workdate)
|
||||
|
||||
for q in queues:
|
||||
esc.queues.add(q)
|
||||
if verbose:
|
||||
print " - for queue %s" % q
|
||||
|
||||
i += 1
|
||||
workdate += timedelta(days=1)
|
||||
|
||||
|
||||
def usage():
|
||||
print "Options:"
|
||||
print " --days, -d: Days of week (monday, tuesday, etc)"
|
||||
print " --occurrences, -o: Occurrences: How many weeks ahead to exclude this day"
|
||||
print " --queues, -q: Queues to include (default: all). Use queue slugs"
|
||||
print " --verbose, -v: Display a list of dates excluded"
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'd:o:q:v', ['days=', 'occurrences=', 'verbose', 'queues='])
|
||||
except getopt.GetoptError:
|
||||
usage()
|
||||
sys.exit(2)
|
||||
|
||||
days = None
|
||||
occurrences = None
|
||||
verbose = False
|
||||
queue_slugs = None
|
||||
queues = []
|
||||
|
||||
for o, a in opts:
|
||||
if o in ('-v', '--verbose'):
|
||||
verbose = True
|
||||
if o in ('-d', '--days'):
|
||||
days = a
|
||||
if o in ('-q', '--queues'):
|
||||
queue_slugs = a
|
||||
if o in ('-o', '--occurrences'):
|
||||
occurrences = int(a)
|
||||
|
||||
if not occurrences: occurrences = 1
|
||||
if not (days and occurrences):
|
||||
usage()
|
||||
sys.exit(2)
|
||||
|
||||
if queue_slugs is not None:
|
||||
queue_set = queue_slugs.split(',')
|
||||
for queue in queue_set:
|
||||
try:
|
||||
q = Queue.objects.get(slug__exact=queue)
|
||||
except:
|
||||
print "Queue %s does not exist." % queue
|
||||
sys.exit(2)
|
||||
queues.append(q)
|
||||
|
||||
create_exclusions(days=days, occurrences=occurrences, verbose=verbose, queues=queues)
|
@ -26,44 +26,91 @@
|
||||
$Id$
|
||||
|
||||
"""
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime, timedelta, date
|
||||
from django.db.models import Q
|
||||
from helpdesk.models import Queue, Ticket, FollowUp
|
||||
from helpdesk.models import Queue, Ticket, FollowUp, EscalationExclusion, TicketChange
|
||||
from helpdesk.lib import send_multipart_mail
|
||||
import sys, getopt
|
||||
|
||||
def escalate_tickets():
|
||||
for q in Queue.objects.filter(escalate_hours__isnull=False).exclude(escalate_hours=0):
|
||||
def escalate_tickets(queues, verbose):
|
||||
""" Only include queues with escalation configured """
|
||||
queryset = Queue.objects.filter(escalate_days__isnull=False).exclude(escalate_days=0)
|
||||
if queues:
|
||||
queryset = queryset.filter(slug__in=queues)
|
||||
|
||||
for q in queryset:
|
||||
last = date.today() - timedelta(days=q.escalate_days)
|
||||
today = date.today()
|
||||
workdate = last
|
||||
|
||||
days = 0
|
||||
|
||||
while workdate < today:
|
||||
if EscalationExclusion.objects.filter(date=workdate).count() == 0:
|
||||
days += 1
|
||||
workdate = workdate + timedelta(days=1)
|
||||
|
||||
|
||||
req_last_escl_date = date.today() - timedelta(days=days)
|
||||
|
||||
if verbose:
|
||||
print "Processing: %s" % q
|
||||
|
||||
if not q.last_escalation: q.last_escalation = datetime.now()-timedelta(hours=q.escalate_hours)
|
||||
|
||||
if (q.last_escalation + timedelta(hours=q.escalate_hours) - timedelta(minutes=2)) > datetime.now():
|
||||
continue
|
||||
|
||||
print "Processing: %s" % q
|
||||
|
||||
q.last_escalation = datetime.now()
|
||||
q.save()
|
||||
|
||||
for t in q.ticket_set.filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)).exclude(priority=1).exclude(on_hold=True):
|
||||
for t in q.ticket_set.filter(Q(status=Ticket.OPEN_STATUS) | Q(status=Ticket.REOPENED_STATUS)).exclude(priority=1).filter(Q(on_hold__isnull=True) | Q(on_hold=False)).filter(Q(last_escalation__lte=req_last_escl_date) | Q(last_escalation__isnull=True)):
|
||||
t.last_escalation = datetime.now()
|
||||
t.priority -= 1
|
||||
t.save()
|
||||
|
||||
if verbose:
|
||||
print " - Esclating %s from %s>%s" % (t.ticket, t.priority+1, t.priority)
|
||||
|
||||
f = FollowUp(
|
||||
ticket = t,
|
||||
title = 'Ticket Escalated',
|
||||
date=q.last_escalation,
|
||||
date=datetime.now(),
|
||||
public=True,
|
||||
comment='Ticket escalated after %s hours' % q.escalate_hours,
|
||||
comment='Ticket escalated after %s days' % q.escalate_days,
|
||||
)
|
||||
f.save()
|
||||
|
||||
tc = TicketChange(
|
||||
followup = f,
|
||||
field = 'priority',
|
||||
field = 'Priority',
|
||||
old_value = t.priority + 1,
|
||||
new_value = t.priority,
|
||||
)
|
||||
tc.save()
|
||||
|
||||
def usage():
|
||||
print "Options:"
|
||||
print " --queues, -q: Queues to include (default: all). Use queue slugs"
|
||||
print " --verbose, -v: Display a list of dates excluded"
|
||||
|
||||
if __name__ == '__main__':
|
||||
escalate_tickets()
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'q:v', ['queues=', 'verbose'])
|
||||
except getopt.GetoptError:
|
||||
usage()
|
||||
sys.exit(2)
|
||||
|
||||
verbose = False
|
||||
queue_slugs = None
|
||||
queues = []
|
||||
|
||||
for o, a in opts:
|
||||
if o in ('-v', '--verbose'):
|
||||
verbose = True
|
||||
if o in ('-q', '--queues'):
|
||||
queue_slugs = a
|
||||
|
||||
if queue_slugs is not None:
|
||||
queue_set = queue_slugs.split(',')
|
||||
for queue in queue_set:
|
||||
try:
|
||||
q = Queue.objects.get(slug__exact=queue)
|
||||
except:
|
||||
print "Queue %s does not exist." % queue
|
||||
sys.exit(2)
|
||||
queues.append(queue)
|
||||
|
||||
escalate_tickets(queues=queues, verbose=verbose)
|
||||
|
Loading…
Reference in New Issue
Block a user