UntouchedInBusinessHours
UntouchedInBusinessHours
Based on the Modul/Condition UntouchedInHours and the Ideas from TimedNotifications we created a new Condition called UntouchedInBusinessHours.
Compared to UntouchedInHours we calculate within this module based on the Create Time of the Ticket and not on LastUpdated and also only within a definable Business Hours Range.
Problem Scenario
We needed in some Change Request Queues some kind of escalation mixed with notifications based on given business hours model. This escalation should be done in two steps, first escalation should inform head of the queue and raise the ticket priority and second should inform head of IT and raise the priority again.
Solution
As solution for this setup we found a mixture of rtcrontool / cron, UntouchedInHours and TimedNotifications.
Crontab Entries
*/15 7-15 * * * /opt/rt3/bin/rt-crontool --search RT::Search::FromSQL --search-arg "Queue = 'ChangeManagement' AND Status = 'new' AND Owner = 'Nobody' AND CF.Escalation = 'InTime'" --condition RT::Condition::UntouchedInBusinessHours --condition-arg 8:7:15:0 --action RT::Action::RecordComment --template 'esc 1 - 8Std FirstContact'
Queue Preparation
To make sure to escalate in the correct order, we added to each change queue a CustomField and a Custom Scrip
CustomFields
For each Change Queue we added a CustomField Escalation which will be filled at ticket creation with the Value "InTime" and updated after first escalation with the Value "FirstEscalation" and after second escalation with the value "SecondEscalation" This values are used inside the search as additional Search Argument.
Custom Scrip
We added to all change queues a simple scrip which set the CF Escalation at ticket create time to "InTime"
Scrip: 001_OnCreateSetEscalationCF
Condition: OnCreate Action: UserDefined Template: Blank
Custom action preparation code:
return 1;
Custom action cleanup code:
$self->TicketObj->AddCustomFieldValue(Field => 'Escalation', Value => 'InTime', RecordTransaction => 0); return 1;
Templates
Based on the ideas from we create some templates for the escalation, which will sent out a mail and raise the priority by 10 and update the CustomField "Escalation"
Template: esc 1 - 8Std FirstContact
Subject: Escalation 1 for Ticket #: {$Ticket->id} - {$Ticket->Subject()}!
RT-Send-Cc: head1@company.com Hello, this is an automated Escalation Mail from the Request Tracker. !!!! FIRST ESCALATION !!!! The following Change Request is older than 8 hours without being attended to. Ticket Id: {$Ticket->id} Queue name: {$Ticket->QueueObj->Name} Escalation: {$Ticket->AddCustomFieldValue(Field =>'Escalation', Value => 'FirstEscalation',RecordTransaction => 0)} Priority: {$Ticket->SetPriority($Ticket->Priority + 10)} Subject: {$Ticket->Subject()} Requestor: {$Ticket->RequestorAddresses} Ticket URL: {$RT::WebURL}Ticket/Display.html?id={$Ticket->id} Please take care of the Change Request immediately. Thank you
RT::Condition::UntouchedInBusinessHours
After all the preparation, we implemented our new Condition to RT under RT_HOME/local/lib/RT/Condition called UntouchedInBusinessHours.pm
You can download the file here: http://www.brumm.me/rt/UntouchInBusinessHours.pm
package RT::Condition::UntouchedInBusinessHours;
require RT::Condition::Generic; use strict; use Time::Local; use vars qw/@ISA/; @ISA = qw(RT::Condition::Generic); =head1 NAME L<RT::Condition::UntouchedInBusinessHours> - Check for untouched Tickets within business hours =head1 SYNOPSIS =head2 CLI rt-crontool --search RT::Search::ModuleName --search-arg "The Search Argument" --condition RT::Condition::UntouchedInBusinessHours --condition-arg "The Condition Argument" --action RT::Action:ActionModule --template 'Template Name or ID' =head1 DESCRIPTION L<RT::Condition::UntouchedInBusinessHours> is a RT Condition which will check for untouched Tickets within business hours. Untouched means in this case really untouched from time of ticket creation. =head1 CONFIGURATION =head2 Condition Argument The L<RT::Condition::UntouchedInBusinessHours> need exactly 4 arguments to work. Each part of the argument is separated by colons. --condition RT::Condition::UntouchedInBusinessHours --condition-arg 1:7:15:0 1 is the time in hours for escalation 7 is the start hour of the working day 15 is the end of of the working day 0 is a 5 day week from monday to friday 1 is a 7 day week from monday to sunday =head2 Example cron call rt-crontool --search RT::Search::FromSQL --search-arg "Queue = 'General' AND ( Status = 'new' ) AND Owner = 'Nobody'" --condition RT::Condition::UntouchedInBusinessHours --condition-arg 1:7:15:0 --action RT::Action::RecordComment --template 'Unowned tickets' =head1 Limitations At this moment only week/weekends from mo-fr/sa-su are supported. If you have different scenario (like arabic countries) you have to change inside the source code ($wd =~ m/(Sun|Sat)/io) Also no public holidays are excluded. =head1 NOTES =head2 Reporting Send reports to L</AUTHOR> or to the RT mailing lists. =head2 AUTHOR Torsten Brumm <torsten.brumm@googlemail.com> =head2 COPYRIGHT This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The full text of the license can be found in the Perl distribution. =cut my ($est, $tc, $bsh, $beh, $te, $we); my ($sec,$min,$hour,$mday,$mon,$year,$wd); my $ActualDate; my $ticketObj; my $tickid; my $EscalationDate; sub IsApplicable { my $self = shift; $ticketObj = $self->TicketObj; $tickid = $ticketObj->Id; $tc = $ticketObj->CreatedObj->Unix; ($est, $bsh, $beh, $we) = split(/:/, $self->Argument); $ActualDate = local_actual_date_sec(RT::Date->new($RT::SystemUser)); $EscalationDate = escalate(normalize($tc, $bsh, $beh, $we), $bsh, $beh, $est, $we); if ( $EscalationDate <= $ActualDate ) { return 1; } return undef; } sub local_actual_date_sec { my $self = shift; $self->SetToNow; my $Ausgabe_local_actual_date_sec = $self->Unix; return ($self->Unix); } sub escalate { my ($tc, $bs, $be, $et, $we) = @_; my ($rs, $es); my $bar = 60*60; $rs = $es = $et*$bar; my $elapsed = 0; my ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($tc); my $beepo = timegm(0,0,$be,$mday,$mon,$year); my ($besec,$bemin,$behour,$bemday,$bemon,$beyear,$bewd) = fromepoch($beepo); my $foo = $beepo-$tc; if ($es < $foo) { # escalate on the same day within BH return $tc+$es; } if ($hour < $behour) { $elapsed += $foo; $rs -= $foo; ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($tc+86400); $tc = timegm(0,0,$bs,$mday,$mon,$year); $tc = normalize($tc, $bs, $be, $we); ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($tc); } while(int($rs / ($bar*($be -$bs)))) { $elapsed += ($bar * ($be -$bs)); $rs -= ($bar * ($be - $bs)); ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($tc+86400); $tc = timegm(0,0,$bs,$mday,$mon,$year); $tc = normalize($tc, $bs, $be, $we); ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($tc); } $tc = normalize($tc+$rs, $bs, $be); return $tc; } sub normalize { my ($time, $bs, $be, $we) = @_; my ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($time); my $bens = timegm(0,0,$bs,$mday,$mon,$year); my $bend = timegm(0,0,$be,$mday,$mon,$year); if ($time >= $bend) { ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($time+86400); $time = $bens = timegm(0,0,$bs,$mday,$mon,$year); $bend = timegm(0,0,$be,$mday,$mon,$year); } if ($time < $bens) { $time = timegm(0,0,$bs,$mday,$mon,$year); } if (! $we) { while ($wd =~ m/(Sun|Sat)/io) { ($sec,$min,$hour,$mday,$mon,$year,$wd) = fromepoch($time + 86400); $time = timegm(0,0,$bs,$mday,$mon,$year); } } return $time; } sub fromepoch { my ($t) = @_; my @time = gmtime($t); my $ss = ($time[0]<10) ? "0".$time[0] : $time[0]; my $mm = ($time[1]<10) ? "0".$time[1] : $time[1]; my $hh = ($time[2]<10) ? "0".$time[2] : $time[2]; my $day = $time[3]; my $month = ($time[4] < 10) ? "0".($time[4]) : $time[4]; my $wday = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat")[$time[6]]; my $year = $time[5] + 1900; my $offset = timelocal(localtime) - timelocal(gmtime); my $sign ="+"; if ($offset < 0) { $sign ="-"; $offset *= -1; } my $offseth = int($offset/3600); my $offsetm = int(($offset - $offseth*3600)/60); my $tz = sprintf ("%s%0.2d%0.2d", $sign, $offseth, $offsetm); return ($ss, $mm, $hh, $day, $month, $year, $wday); } eval "require RT::Condition::UntouchedInBusinessHours_Vendor"; die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInBusinessHours_Vendor.pm}); eval "require RT::Condition::UntouchedInBusinessHours_Local"; die $@ if ($@ && $@ !~ qr{^Can't locate RT/Condition/UntouchedInBusinessHours_Local.pm}); 1;