From f7e3a2b8aa13985852ce9ba94fb7414f623178b1 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 13 Feb 2024 08:59:48 +0000 Subject: [PATCH 01/22] Automatic absolute time_spent calculation for ticket FollowUp --- helpdesk/models.py | 5 ++++- helpdesk/settings.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index b585f9ea..00a92aa3 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1000,8 +1000,11 @@ class FollowUp(models.Model): return u"%s#followup%s" % (self.ticket.get_absolute_url(), self.id) def save(self, *args, **kwargs): + now = timezone.now() t = self.ticket - t.modified = timezone.now() + if helpdesk_settings.FOLLOWUP_TIME_SPENT_AUTO and not self.time_spent: + self.time_spent = now - t.modified + t.modified = now t.save() super(FollowUp, self).save(*args, **kwargs) diff --git a/helpdesk/settings.py b/helpdesk/settings.py index cf5ca644..511109d7 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -151,6 +151,11 @@ TICKET_PRIORITY_CHOICES = getattr(settings, 'HELPDESK_TICKET_PRIORITY_CHOICES', DEFAULT_TICKET_PRIORITY_CHOICES) +# Follow-ups automatic time_spent calculation +FOLLOWUP_TIME_SPENT_AUTO = getattr(settings, + 'HELPDESK_FOLLOWUP_TIME_SPENT_AUTO', + False) + ############################ # options for public pages # ############################ From 13e42f19560a03a1b40a5a3634d8932da597c893 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:06:58 +0000 Subject: [PATCH 02/22] Document HELPDESK_FOLLOWUP_TIME_SPENT_AUTO setting --- docs/settings.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/settings.rst b/docs/settings.rst index 56199046..9dd96f40 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -291,6 +291,13 @@ Options that change ticket properties (7, _('7. Hot')), ) +Time Tracking Options +--------------------- + +- **HELPDESK_FOLLOWUP_TIME_SPENT_AUTO** If ``True``, calculate follow-up 'time_spent' with previous follow-up or ticket creation time. + + **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_AUTO = False`` + Staff Ticket Creation Settings ------------------------------ From cd77ac7a9db4879d5cafef44465e4829569867ec Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:27:12 +0000 Subject: [PATCH 03/22] Update FollowUp time calculation method --- helpdesk/models.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 00a92aa3..44575f07 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1003,7 +1003,12 @@ class FollowUp(models.Model): now = timezone.now() t = self.ticket if helpdesk_settings.FOLLOWUP_TIME_SPENT_AUTO and not self.time_spent: - self.time_spent = now - t.modified + try: + latest_fup = t.followup_set.exclude(id=self.id).latest("date") + latest_time = latest_fup.date + except ObjectDoesNotExist: + latest_time = t.created + self.time_spent = now - latest_time t.modified = now t.save() super(FollowUp, self).save(*args, **kwargs) From ab8bd7637dfcbe1857e4bf526acde3e302875949 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:30:21 +0000 Subject: [PATCH 04/22] Adding comment header according to documentation header --- helpdesk/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/helpdesk/settings.py b/helpdesk/settings.py index 511109d7..220bc424 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -151,6 +151,11 @@ TICKET_PRIORITY_CHOICES = getattr(settings, 'HELPDESK_TICKET_PRIORITY_CHOICES', DEFAULT_TICKET_PRIORITY_CHOICES) + +######################### +# time tracking options # +######################### + # Follow-ups automatic time_spent calculation FOLLOWUP_TIME_SPENT_AUTO = getattr(settings, 'HELPDESK_FOLLOWUP_TIME_SPENT_AUTO', From ed6aa132f37e556725fb4d2ff98330d0ab7f4c86 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:31:06 +0000 Subject: [PATCH 05/22] timedelta seconds property is limited to 86400 --- helpdesk/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 44575f07..a258a315 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -36,8 +36,8 @@ import uuid def format_time_spent(time_spent): if time_spent: time_spent = "{0:02d}h:{1:02d}m".format( - time_spent.seconds // 3600, - time_spent.seconds % 3600 // 60 + int(time_spent.total_seconds()) // 3600, + int(time_spent.total_seconds()) % 3600 // 60 ) else: time_spent = "" From 05dfde59cb7b395623ab34a932303752943315d5 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:16:20 +0000 Subject: [PATCH 06/22] Include opening hours for follow-up time_spent calculation --- docs/settings.rst | 20 ++++++++++++++++++++ helpdesk/lib.py | 37 +++++++++++++++++++++++++++++++++++++ helpdesk/models.py | 30 ++++++++++++++++++++++++++++-- helpdesk/settings.py | 5 +++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 9dd96f40..a78fd2af 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -298,6 +298,26 @@ Time Tracking Options **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_AUTO = False`` +- **HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS** If defined, calculates follow-up 'time_spent' according to open hours. + + **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS = {}`` + + If HELPDESK_FOLLOWUP_TIME_SPENT_AUTO is ``True``, you may set open hours to remove off hours from 'time_spent':: + + HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS = { + "monday": (8.5, 19), + "tuesday": (8.5, 19), + "wednesday": (8.5, 19), + "thursday": (8.5, 19), + "friday": (8.5, 19), + "saturday": (0, 0), + "sunday": (0, 0), + } + + Valid hour values must be set between 0 and 23.9999. + In this example 8.5 is interpreted as 8:30AM, saturdays and sundays don't count. + + Staff Ticket Creation Settings ------------------------------ diff --git a/helpdesk/lib.py b/helpdesk/lib.py index ac0d46bc..10d53736 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -194,3 +194,40 @@ def convert_value(value): return value.strftime(CUSTOMFIELD_TIME_FORMAT) else: return value + + +def daily_time_spent_calculation(earliest, latest, open_hours): + """Returns timedelta for a single day time interval according to open hours.""" + + # avoid rendering day in different locale + weekday = ('monday', 'tuesday', 'wednesday', 'thursday', + 'friday', 'saturday', 'sunday')[earliest.weekday()] + + # enforce correct settings + MIDNIGHT = 23.9999 + start, end = open_hours.get(weekday, (0, MIDNIGHT)) + if not 0 <= start <= end <= MIDNIGHT: + start, end = 0, MIDNIGHT + + # transform decimals to minutes + start_hour, start_minute, start_second = int(start), int(start % 1 * 60), int(start * 60 % 1 * 60) + end_hour, end_minute, end_second = int(end), int(end % 1 * 60), int(end * 60 % 1 * 60) + + # translate time for delta calculation + earliest_f = earliest.hour + earliest.minute / 60 + latest_f = latest.hour + latest.minute / 60 + + if earliest_f < start: + earliest = earliest.replace(hour=start_hour, minute=start_minute, second=start_second) + elif earliest_f >= end: + earliest = earliest.replace(hour=end_hour, minute=end_minute, second=end_second) + + if latest_f < start: + latest = latest.replace(hour=start_hour, minute=start_minute, second=start_second) + elif latest_f >= end: + latest = latest.replace(hour=end_hour, minute=end_minute, second=end_second) + + day_delta = latest - earliest + + # returns up to 86399 seconds + return day_delta.seconds \ No newline at end of file diff --git a/helpdesk/models.py b/helpdesk/models.py index a258a315..9cd4de81 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -8,7 +8,7 @@ models.py - Model (and hence database) definitions. This is the core of the """ -from .lib import convert_value +from .lib import convert_value, daily_time_spent_calculation from .templated_email import send_templated_mail from .validators import validate_file_extension from .webhooks import send_new_ticket_webhook @@ -1008,7 +1008,7 @@ class FollowUp(models.Model): latest_time = latest_fup.date except ObjectDoesNotExist: latest_time = t.created - self.time_spent = now - latest_time + self.time_spent = self.time_spent_calculation(latest_time, now) t.modified = now t.save() super(FollowUp, self).save(*args, **kwargs) @@ -1020,6 +1020,32 @@ class FollowUp(models.Model): def time_spent_formated(self): return format_time_spent(self.time_spent) + def time_spent_calculation(self, earliest, latest): + "Returns timedelta according to rules settings." + + time_spent_seconds = 0 + open_hours = helpdesk_settings.FOLLOWUP_TIME_SPENT_OPENING_HOURS + + # split time interval by days + days = latest.toordinal() - earliest.toordinal() + if days: + for day in range(days + 1): + if day == 0: + start_day_time = earliest + end_day_time = earliest.replace(hour=23, minute=59, second=59) + elif day == days: + start_day_time = latest.replace(hour=0, minute=0, second=0) + end_day_time = latest + else: + middle_day_time = earliest + timedelta(days=day) + 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) + time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) + # handle same day + else: + time_spent_seconds += daily_time_spent_calculation(earliest, latest, open_hours) + + return datetime.timedelta(seconds=time_spent_seconds) class TicketChange(models.Model): """ diff --git a/helpdesk/settings.py b/helpdesk/settings.py index 220bc424..96eac1ee 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -161,6 +161,11 @@ FOLLOWUP_TIME_SPENT_AUTO = getattr(settings, 'HELPDESK_FOLLOWUP_TIME_SPENT_AUTO', False) +# Calculate time_spent according to open hours +FOLLOWUP_TIME_SPENT_OPENING_HOURS = getattr(settings, + 'HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS', + {}) + ############################ # options for public pages # ############################ From c3f6a1caa2c702af3bc095c179e1d1bf8a890f1c Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:21:34 +0000 Subject: [PATCH 07/22] Adding second precision for float conversion --- helpdesk/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 10d53736..15e417f3 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -214,8 +214,8 @@ def daily_time_spent_calculation(earliest, latest, open_hours): end_hour, end_minute, end_second = int(end), int(end % 1 * 60), int(end * 60 % 1 * 60) # translate time for delta calculation - earliest_f = earliest.hour + earliest.minute / 60 - latest_f = latest.hour + latest.minute / 60 + earliest_f = earliest.hour + earliest.minute / 60 + earliest.second / 3600 + latest_f = latest.hour + latest.minute / 60 + latest.second / 3600 if earliest_f < start: earliest = earliest.replace(hour=start_hour, minute=start_minute, second=start_second) From a671a9e8dc693241e237e24075ce9f470c8b12fa Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:23:31 +0000 Subject: [PATCH 08/22] Fixed undefined timedelta error --- helpdesk/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 9cd4de81..d14239fa 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1037,7 +1037,7 @@ class FollowUp(models.Model): start_day_time = latest.replace(hour=0, minute=0, second=0) end_day_time = latest else: - middle_day_time = earliest + timedelta(days=day) + middle_day_time = earliest + datetime.timedelta(days=day) 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) time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) From eb9d947dd60f750b58aa711c8115adabf04c8cf8 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:33:20 +0000 Subject: [PATCH 09/22] Update comments --- helpdesk/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 15e417f3..2edbaebd 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -197,7 +197,7 @@ def convert_value(value): def daily_time_spent_calculation(earliest, latest, open_hours): - """Returns timedelta for a single day time interval according to open hours.""" + """Returns the number of seconds for a single day time interval according to open hours.""" # avoid rendering day in different locale weekday = ('monday', 'tuesday', 'wednesday', 'thursday', @@ -209,7 +209,7 @@ def daily_time_spent_calculation(earliest, latest, open_hours): if not 0 <= start <= end <= MIDNIGHT: start, end = 0, MIDNIGHT - # transform decimals to minutes + # transform decimals to minutes and seconds start_hour, start_minute, start_second = int(start), int(start % 1 * 60), int(start * 60 % 1 * 60) end_hour, end_minute, end_second = int(end), int(end % 1 * 60), int(end * 60 % 1 * 60) From 220f6d56a89e7c51d72ade36e1fbe62a2d521420 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:03:58 +0000 Subject: [PATCH 10/22] Adding HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS setting --- docs/settings.rst | 13 +++++++++++-- helpdesk/models.py | 29 ++++++++++++++++------------- helpdesk/settings.py | 5 +++++ 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index a78fd2af..8d7001dd 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -299,11 +299,11 @@ Time Tracking Options **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_AUTO = False`` - **HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS** If defined, calculates follow-up 'time_spent' according to open hours. - + **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS = {}`` If HELPDESK_FOLLOWUP_TIME_SPENT_AUTO is ``True``, you may set open hours to remove off hours from 'time_spent':: - + HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS = { "monday": (8.5, 19), "tuesday": (8.5, 19), @@ -316,7 +316,16 @@ Time Tracking Options Valid hour values must be set between 0 and 23.9999. In this example 8.5 is interpreted as 8:30AM, saturdays and sundays don't count. + +- **HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS** List of days in format "%m-%d" to exclude from automatic follow-up 'time_spent' calculation. + **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = ()`` + + This example removes the first and last days of the year together with Christmas:: + + HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = ("01-01", + "12-25", + "12-31",) Staff Ticket Creation Settings ------------------------------ diff --git a/helpdesk/models.py b/helpdesk/models.py index d14239fa..413b5baa 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1025,25 +1025,28 @@ class FollowUp(models.Model): time_spent_seconds = 0 open_hours = helpdesk_settings.FOLLOWUP_TIME_SPENT_OPENING_HOURS + holidays = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS # split time interval by days days = latest.toordinal() - earliest.toordinal() - if days: - for day in range(days + 1): - if day == 0: - start_day_time = earliest - end_day_time = earliest.replace(hour=23, minute=59, second=59) - elif day == days: - start_day_time = latest.replace(hour=0, minute=0, second=0) + for day in range(days + 1): + if day == 0: + start_day_time = earliest + if days == 0: + # close single day case end_day_time = latest else: - middle_day_time = earliest + datetime.timedelta(days=day) - 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) + end_day_time = earliest.replace(hour=23, minute=59, second=59) + elif day == days: + start_day_time = latest.replace(hour=0, minute=0, second=0) + end_day_time = latest + else: + middle_day_time = earliest + datetime.timedelta(days=day) + 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) + + if start_day_time.strftime("%m-%d") not in holidays: time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) - # handle same day - else: - time_spent_seconds += daily_time_spent_calculation(earliest, latest, open_hours) return datetime.timedelta(seconds=time_spent_seconds) diff --git a/helpdesk/settings.py b/helpdesk/settings.py index 96eac1ee..ab610697 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -166,6 +166,11 @@ FOLLOWUP_TIME_SPENT_OPENING_HOURS = getattr(settings, 'HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS', {}) +# Holidays don't count for time_spent calculation +FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS= getattr(settings, + 'HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS', + ()) + ############################ # options for public pages # ############################ From 90666a47ba355fbeb668e4656a8862a082eb6e3e Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:21:19 +0000 Subject: [PATCH 11/22] Adding HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES setting --- docs/settings.rst | 8 ++++++++ helpdesk/models.py | 4 +++- helpdesk/settings.py | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 8d7001dd..2085c39e 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -327,6 +327,14 @@ Time Tracking Options "12-25", "12-31",) +- **HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES** List of ticket statuses to exclude from automatic follow-up 'time_spent' calculation. + + **Default:** ``HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES = ()`` + + This example will have follow-ups to resolved ticket status not to be counted in:: + + HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES = (HELPDESK_TICKET_RESOLVED_STATUS,) + Staff Ticket Creation Settings ------------------------------ diff --git a/helpdesk/models.py b/helpdesk/models.py index 413b5baa..4231b8e8 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1026,6 +1026,7 @@ class FollowUp(models.Model): 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_CALCULATION_EXCLUDE_STATUSES # split time interval by days days = latest.toordinal() - earliest.toordinal() @@ -1046,7 +1047,8 @@ class FollowUp(models.Model): end_day_time = middle_day_time.replace(hour=23, minute=59, second=59) if start_day_time.strftime("%m-%d") not in holidays: - time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) + if self.ticket.status not in exclude_statuses: + 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/settings.py b/helpdesk/settings.py index ab610697..1df0fe4f 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -167,10 +167,15 @@ FOLLOWUP_TIME_SPENT_OPENING_HOURS = getattr(settings, {}) # Holidays don't count for time_spent calculation -FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS= getattr(settings, +FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = getattr(settings, 'HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS', ()) +# Time doesn't count for listed ticket statuses +FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES = getattr(settings, + 'HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES', + ()) + ############################ # options for public pages # ############################ From 7e65e3d36764af9dcc943d78bcacc8c2d1fd3d97 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:23:15 +0000 Subject: [PATCH 12/22] Renamed setting to HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES --- docs/settings.rst | 6 +++--- helpdesk/models.py | 2 +- helpdesk/settings.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 2085c39e..e332a7ad 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -327,13 +327,13 @@ Time Tracking Options "12-25", "12-31",) -- **HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES** List of ticket statuses to exclude from automatic follow-up 'time_spent' calculation. +- **HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES** List of ticket statuses to exclude from automatic follow-up 'time_spent' calculation. - **Default:** ``HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES = ()`` + **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES = ()`` This example will have follow-ups to resolved ticket status not to be counted in:: - HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES = (HELPDESK_TICKET_RESOLVED_STATUS,) + HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES = (HELPDESK_TICKET_RESOLVED_STATUS,) Staff Ticket Creation Settings ------------------------------ diff --git a/helpdesk/models.py b/helpdesk/models.py index 4231b8e8..9d07ce71 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1026,7 +1026,7 @@ class FollowUp(models.Model): 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_CALCULATION_EXCLUDE_STATUSES + exclude_statuses = helpdesk_settings.FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES # split time interval by days days = latest.toordinal() - earliest.toordinal() diff --git a/helpdesk/settings.py b/helpdesk/settings.py index 1df0fe4f..a74ef3fa 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -172,8 +172,8 @@ FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = getattr(settings, ()) # Time doesn't count for listed ticket statuses -FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES = getattr(settings, - 'HELPDESK_FOLLOWUP_TIME_CALCULATION_EXCLUDE_STATUSES', +FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES = getattr(settings, + 'HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES', ()) ############################ From b3cbfdbe097ce6e3cb2893b201681e3577de9bd3 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 13:36:46 +0000 Subject: [PATCH 13/22] Added HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES setting --- docs/settings.rst | 8 ++++++++ helpdesk/models.py | 4 +++- helpdesk/settings.py | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index e332a7ad..4a48875a 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -335,6 +335,14 @@ Time Tracking Options HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES = (HELPDESK_TICKET_RESOLVED_STATUS,) +- **HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES** List of ticket queues slugs to exclude from automatic follow-up 'time_spent' calculation. + + **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = ()`` + + This example will have follow-ups to ticket queue 'time-not-counting-queue' not to be counted in:: + + HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = ('time-not-counting-queue',) + Staff Ticket Creation Settings ------------------------------ diff --git a/helpdesk/models.py b/helpdesk/models.py index 9d07ce71..d7bd9521 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1027,6 +1027,7 @@ class FollowUp(models.Model): 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() @@ -1047,7 +1048,8 @@ class FollowUp(models.Model): end_day_time = middle_day_time.replace(hour=23, minute=59, second=59) if start_day_time.strftime("%m-%d") not in holidays: - if self.ticket.status not in exclude_statuses: + if (self.ticket.status not in exclude_statuses and + self.ticket.queue.slug not in exclude_queues): 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/settings.py b/helpdesk/settings.py index a74ef3fa..995081f6 100644 --- a/helpdesk/settings.py +++ b/helpdesk/settings.py @@ -176,6 +176,11 @@ FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES = getattr(settings, 'HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES', ()) +# Time doesn't count for listed queues slugs +FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES = getattr(settings, + 'HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_QUEUES', + ()) + ############################ # options for public pages # ############################ From b1759520d8297c22dc3f273219183aa4a913850e Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 14 Feb 2024 14:52:23 +0000 Subject: [PATCH 14/22] Merge exclusion conditions for time_spent calculation --- helpdesk/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index d7bd9521..71ab2631 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1047,10 +1047,10 @@ 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) - if start_day_time.strftime("%m-%d") not in holidays: - if (self.ticket.status not in exclude_statuses and - self.ticket.queue.slug not in exclude_queues): - time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) + if (start_day_time.strftime("%m-%d") not in holidays and + self.ticket.status not in exclude_statuses and + self.ticket.queue.slug not in exclude_queues): + time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) return datetime.timedelta(seconds=time_spent_seconds) From 99b49e3f39e9dea8d49c0f5ef16d9bf55b004dc2 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:08:25 +0000 Subject: [PATCH 15/22] Refactoring earliest and latest follow-up time selection logic, also allows for time_spent recalculation if unset --- helpdesk/models.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 71ab2631..d965e0b0 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1000,17 +1000,12 @@ class FollowUp(models.Model): return u"%s#followup%s" % (self.ticket.get_absolute_url(), self.id) def save(self, *args, **kwargs): - now = timezone.now() - t = self.ticket + self.ticket.modified = timezone.now() + self.ticket.save() + if helpdesk_settings.FOLLOWUP_TIME_SPENT_AUTO and not self.time_spent: - try: - latest_fup = t.followup_set.exclude(id=self.id).latest("date") - latest_time = latest_fup.date - except ObjectDoesNotExist: - latest_time = t.created - self.time_spent = self.time_spent_calculation(latest_time, now) - t.modified = now - t.save() + self.time_spent = self.time_spent_calculation() + super(FollowUp, self).save(*args, **kwargs) def get_markdown(self): @@ -1020,9 +1015,23 @@ class FollowUp(models.Model): def time_spent_formated(self): return format_time_spent(self.time_spent) - def time_spent_calculation(self, earliest, latest): + def time_spent_calculation(self): "Returns timedelta according to rules settings." - + + # extract earliest time from previous follow-up + # or ticket creation time + 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 + + # 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 From cec1035c2d58fadb2553a1e5712edfb3002f4400 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:18:36 +0000 Subject: [PATCH 16/22] Return precisely 86400 seconds for a full day --- helpdesk/lib.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 2edbaebd..b94eaf50 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -229,5 +229,9 @@ def daily_time_spent_calculation(earliest, latest, open_hours): day_delta = latest - earliest - # returns up to 86399 seconds - return day_delta.seconds \ No newline at end of file + # returns up to 86399 seconds, add one second if full day + time_spent_seconds = day_delta.seconds + if time_spent_seconds == 86399: + time_spent_seconds += 1 + + return time_spent_seconds \ No newline at end of file From ae89d182a97bd35fc55f909400c0a0a0e7ffb8cc Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:30:02 +0000 Subject: [PATCH 17/22] Removed duplicate format_time_spent function definition --- helpdesk/lib.py | 5 ++--- helpdesk/models.py | 15 ++------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index b94eaf50..67cc325b 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -173,11 +173,10 @@ def format_time_spent(time_spent): """Format time_spent attribute to "[H]HHh:MMm" text string to be allign in all graphical outputs """ - if time_spent: time_spent = "{0:02d}h:{1:02d}m".format( - time_spent.seconds // 3600, - time_spent.seconds // 60 + int(time_spent.total_seconds()) // 3600, + int(time_spent.total_seconds()) % 3600 // 60 ) else: time_spent = "" diff --git a/helpdesk/models.py b/helpdesk/models.py index d965e0b0..0ac55504 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -8,7 +8,7 @@ models.py - Model (and hence database) definitions. This is the core of the """ -from .lib import convert_value, daily_time_spent_calculation +from .lib import format_time_spent, convert_value, daily_time_spent_calculation from .templated_email import send_templated_mail from .validators import validate_file_extension from .webhooks import send_new_ticket_webhook @@ -33,17 +33,6 @@ from rest_framework import serializers import uuid -def format_time_spent(time_spent): - if time_spent: - time_spent = "{0:02d}h:{1:02d}m".format( - int(time_spent.total_seconds()) // 3600, - int(time_spent.total_seconds()) % 3600 // 60 - ) - else: - time_spent = "" - return time_spent - - class EscapeHtml(Extension): def extendMarkdown(self, md): md.preprocessors.deregister('html_block') @@ -1017,7 +1006,7 @@ class FollowUp(models.Model): def time_spent_calculation(self): "Returns timedelta according to rules settings." - + # extract earliest time from previous follow-up # or ticket creation time try: From 02e333cf52e42c47d3c4e62b3787c5428c970b9e Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 09:55:57 +0000 Subject: [PATCH 18/22] Precise selection of previous status --- helpdesk/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 0ac55504..21c4a555 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1007,16 +1007,17 @@ class FollowUp(models.Model): def time_spent_calculation(self): "Returns timedelta according to rules settings." - # extract earliest time from previous follow-up - # or ticket creation time + # extract parameters 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 + prev_status = prev_fup.new_status except ObjectDoesNotExist: earliest = self.ticket.created + prev_status = self.ticket.status # latest time is current follow-up date latest = self.date @@ -1046,7 +1047,7 @@ class FollowUp(models.Model): end_day_time = middle_day_time.replace(hour=23, minute=59, second=59) if (start_day_time.strftime("%m-%d") not in holidays and - self.ticket.status not in exclude_statuses and + prev_status not in exclude_statuses and self.ticket.queue.slug not in exclude_queues): time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) From ae7c8c7aeaf6634450ebcbc29fd9f5718b6de368 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:24:55 +0000 Subject: [PATCH 19/22] Raise ImproperlyConfigured if HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS is out of (0, 23.9999) boundary values --- helpdesk/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/helpdesk/lib.py b/helpdesk/lib.py index 67cc325b..481d4cbb 100644 --- a/helpdesk/lib.py +++ b/helpdesk/lib.py @@ -9,7 +9,7 @@ lib.py - Common functions (eg multipart e-mail) from datetime import date, datetime, time from django.conf import settings -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, ImproperlyConfigured from django.utils.encoding import smart_str from helpdesk.settings import CUSTOMFIELD_DATE_FORMAT, CUSTOMFIELD_DATETIME_FORMAT, CUSTOMFIELD_TIME_FORMAT import logging @@ -206,7 +206,8 @@ def daily_time_spent_calculation(earliest, latest, open_hours): MIDNIGHT = 23.9999 start, end = open_hours.get(weekday, (0, MIDNIGHT)) if not 0 <= start <= end <= MIDNIGHT: - start, end = 0, MIDNIGHT + raise ImproperlyConfigured("HELPDESK_FOLLOWUP_TIME_SPENT_OPENING_HOURS" + f" setting for {weekday} out of (0, 23.9999) boundary") # transform decimals to minutes and seconds start_hour, start_minute, start_second = int(start), int(start % 1 * 60), int(start * 60 % 1 * 60) From 5482d7b6df23fb86b5850e92e95d14635299c6cd Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 15:42:29 +0000 Subject: [PATCH 20/22] Adding year to HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS dates --- docs/settings.rst | 6 ++---- helpdesk/models.py | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 4a48875a..df4cb5d0 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -321,11 +321,9 @@ Time Tracking Options **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = ()`` - This example removes the first and last days of the year together with Christmas:: + This example removes Christmas and New Year's Eve in 2024:: - HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = ("01-01", - "12-25", - "12-31",) + HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = ("2024-12-25", "2024-12-31",) - **HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_STATUSES** List of ticket statuses to exclude from automatic follow-up 'time_spent' calculation. diff --git a/helpdesk/models.py b/helpdesk/models.py index 21c4a555..167745fc 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1046,7 +1046,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) - if (start_day_time.strftime("%m-%d") not in holidays and + 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): time_spent_seconds += daily_time_spent_calculation(start_day_time, end_day_time, open_hours) From 7a7a39d567ddc39c9a764f497b53339fb0485b01 Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Thu, 15 Feb 2024 16:16:24 +0000 Subject: [PATCH 21/22] Extracting previous ticket status from latest follow-up with new_status set --- helpdesk/models.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/helpdesk/models.py b/helpdesk/models.py index 167745fc..4409d901 100644 --- a/helpdesk/models.py +++ b/helpdesk/models.py @@ -1007,16 +1007,24 @@ class FollowUp(models.Model): def time_spent_calculation(self): "Returns timedelta according to rules settings." - # extract parameters from previous follow-up or ticket + # 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 - prev_status = prev_fup.new_status 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 From 6724e29e28c17d8346c1e083379593b24df1528f Mon Sep 17 00:00:00 2001 From: Sam Splunks <72095718+samsplunks@users.noreply.github.com> Date: Wed, 21 Feb 2024 15:36:21 +0100 Subject: [PATCH 22/22] Fixed holidays exclusion format in documentation --- docs/settings.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/settings.rst b/docs/settings.rst index df4cb5d0..70ba5f6d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -317,7 +317,7 @@ Time Tracking Options Valid hour values must be set between 0 and 23.9999. In this example 8.5 is interpreted as 8:30AM, saturdays and sundays don't count. -- **HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS** List of days in format "%m-%d" to exclude from automatic follow-up 'time_spent' calculation. +- **HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS** List of days in format "%Y-%m-%d" to exclude from automatic follow-up 'time_spent' calculation. **Default:** ``HELPDESK_FOLLOWUP_TIME_SPENT_EXCLUDE_HOLIDAYS = ()``