[Bps-public-commit] rt-extension-sla-utils branch, master, created. 784836ee0f0555601d4b874b5b2fb7dcc868b739

Dave Goehrig dave at bestpractical.com
Thu Mar 16 13:54:27 EDT 2017


The branch, master has been created
        at  784836ee0f0555601d4b874b5b2fb7dcc868b739 (commit)

- Log -----------------------------------------------------------------
commit 784836ee0f0555601d4b874b5b2fb7dcc868b739
Author: Dave Goehrig <dave at bestpractical.com>
Date:   Thu Mar 16 13:53:24 2017 -0400

    RT extension for sla overdue checking
    
    This is an RT extension that updates SLA based on
    a customfield and can update a SLA Overdue custom field
    based on a periodic check.

diff --git a/Changes b/Changes
new file mode 100644
index 0000000..c9e879f
--- /dev/null
+++ b/Changes
@@ -0,0 +1,4 @@
+Revision history for RT-Extension-SLA-Utils
+
+0.01 [Release Date]
+ - Initial version
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..430b493
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,32 @@
+use inc::Module::Install;
+
+RTx     'RT-Extension-SLA-Utils';
+license 'gplv2';
+repository 'https://github.com/bestpractical/rt-extension-sla-utils';
+
+requires_rt '4.0.0';
+
+use Config;
+
+
+my ($lp) = ($INC{'RT.pm'} =~ /^(.*)[\\\/]/);
+my $lib_path = join( ' ', "$RT::LocalPath/lib", $lp );
+my $secure_perl_path = $Config{perlpath};
+if ($^O ne 'VMS') {
+    $secure_perl_path .= $Config{_exe}
+        unless $secure_perl_path =~ m/$Config{_exe}$/i;
+}
+
+substitute(
+    {
+        RT_LIB_PATH  => $lib_path,
+        PERL         => $ENV{PERL} || $secure_perl_path,
+    },
+    {
+        sufix => '.in'
+    },
+    qw(lib/RT/Extension/SLA/Utils/Test.pm),
+);
+
+sign;
+WriteAll;
diff --git a/etc/initialdata b/etc/initialdata
new file mode 100644
index 0000000..b763c83
--- /dev/null
+++ b/etc/initialdata
@@ -0,0 +1,85 @@
+
+our @CustomFields = (
+    {
+        Name => 'Resolution Due',
+        Description => 'SLA final resolution date',
+        Type => 'DateTime',
+        LookupType => 'RT::Queue-RT::Ticket',
+        Disabled => 0,
+        MaxValues => 1,
+    },
+    {
+        Name => 'SLA Overdue',
+        Description => 'This field is set upon first violation of an SLA due date',
+        Type => 'Freeform',
+        LookupType => 'RT::Queue-RT::Ticket',
+        Disabled => 0,
+        MaxValues => 1,
+    },
+);
+
+our @ScripConditions = (
+    {
+        Name                 => 'Require Due set according to SLA',
+        Description          => 'Detect a situation where we should set a Due date.',
+        ApplicableTransTypes => 'Create,Correspond,Set,Status,OwnerChange',
+        ExecModule           => 'SLA::RequireDueSet',
+    },
+    {
+        Name                 => 'On SLA Custom Field Change',
+        Description          => 'The SLA Custom Field has changed',
+        ApplicableTransTypes => 'CustomField',
+        ExecModule           => 'SLA::CustomFieldChanged',
+    },
+);
+
+our @ScripActions = (
+    {
+        Name                 => 'Set Due date according to SLA',
+        Description          => 'Set the due date according to an agreement.',
+        ExecModule           => 'SLA::SetDue',
+    },
+    {
+        Name                 => 'Set SLA Overdue',
+        Description          => 'Set SLA Overdue custom field',
+        ExecModule           => 'SLA::SetOverdue',
+    },
+    {
+        Name                 => 'Set SLA Based on a Custom Field',
+        Description          => 'Set the SLA based on the custom field value',
+        ExecModule           => 'SLA::SetFromCustomField',
+    },
+);
+
+our @Scrips = (
+    {
+        Description => 'Set due date if needed according to SLA',
+        ScripCondition => 'Require Due set according to SLA',
+        ScripAction => 'Set Due date according to SLA',
+        Template => 'Blank',
+    },
+    {
+        Description          => 'On Impact Change, Update SLA',
+        ScripCondition       => 'On SLA Custom Field Change',
+        ScripAction          => 'Set SLA Based on a Custom Field',
+        Template             => 'Blank',
+    },
+);
+
+our @Templates = (
+    {
+        Queue => 0,
+        Name => '2-Hour Ticket Due Notice',
+        Description => 'Notice sent via ticket comment two hours before Due date.',
+        Content => 'Subject: Two-Hour Due Notification for {$Ticket->Subject}
+
+
+This message was automatically generated as notice that a ticket is Due
+within the next two hours:
+    Subject: {$Ticket->Subject}
+    Queue:   {$Ticket->Queue}
+    ID:      {$Ticket->id}
+    Due:     {$Ticket->Due}
+',
+    },
+);
diff --git a/lib/RT/Action/SLA/SetDue.pm b/lib/RT/Action/SLA/SetDue.pm
new file mode 100644
index 0000000..502769e
--- /dev/null
+++ b/lib/RT/Action/SLA/SetDue.pm
@@ -0,0 +1,217 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::SLA::SetDue;
+
+use base qw(RT::Action::SLA);
+
+=head2 Prepare
+
+Checks if the ticket has service level defined.
+
+=cut
+
+sub Prepare {
+    my $self = shift;
+
+    unless ( $self->TicketObj->SLA ) {
+        $RT::Logger->error('SLA::SetDue scrip has been applied to ticket #'
+            . $self->TicketObj->id . ' that has no SLA defined');
+        return 0;
+    }
+
+    return 1;
+}
+
+=head2 Commit
+
+Set the Due date accordingly to SLA.
+
+=cut
+
+sub Commit {
+    my $self = shift;
+
+    my $ticket = $self->TicketObj;
+    my $txn = $self->TransactionObj;
+    my $level = $ticket->SLA;
+
+    my ($last_reply, $is_outside) = $self->LastEffectiveAct;
+    $RT::Logger->debug(
+        'Last effective '. ($is_outside? '':'non-') .'outside actors\' reply'
+        .' to ticket #'. $ticket->id .' is txn #'. $last_reply->id
+    );
+
+    my $initial_due = $self->Due(
+        Ticket => $ticket,
+        Level => $level,
+        Type => 'Initial',
+        Time => $ticket->CreatedObj->Unix,
+    );
+
+    my $response_due;
+    if ( $self->NeedsFirstResponse ){
+        $response_due = $self->Due(
+            Ticket => $ticket,
+            Level => $level,
+            Type => 'FirstResponse',
+            Time => $last_reply->CreatedObj->Unix,
+        );
+        RT::Logger->debug("Setting to FirstResponse");
+    }
+    else{
+        $response_due = $self->Due(
+            Ticket => $ticket,
+            Level => $level,
+            Type => 'KeepInLoop',
+            Time => $last_reply->CreatedObj->Unix,
+        );
+    }
+
+    my $resolve_due = $self->Due(
+        Ticket => $ticket,
+        Level => $level,
+        Type => 'Resolve',
+        Time => $ticket->CreatedObj->Unix,
+    );
+
+    my $txns = $ticket->Transactions;
+    $txns->Limit( FIELD => 'Type', VALUE => 'Set' );
+    $txns->Limit( FIELD => 'Field', VALUE => 'Owner' );
+    my $has_initial_assignment = $txns->Count;
+    my $is_subsequent_assignment = $has_initial_assignment - 1;
+    return if $is_subsequent_assignment and $self->TransactionObj->Type eq 'Set' and $self->TransactionObj->Field eq 'Owner';
+
+    my $due;
+    $due = $initial_due unless $has_initial_assignment;
+    $due = $response_due if $has_initial_assignment and defined $response_due;
+    $due = $resolve_due unless defined $due;
+    $due = $resolve_due if defined $due && defined $resolve_due && $resolve_due < $due;
+
+    my $resolve_date = RT::Date->new( $self->CurrentUser );
+    $resolve_date->Set('Format'=>'unix','Value'=>$resolve_due);
+    $ticket->AddCustomFieldValue(
+        Field => 'Resolution Due',
+        Value => $resolve_date->ISO(Timezone => 'user'),
+    );
+
+    return $self->SetDateField( Due => $due );
+}
+
+sub IsOutsideActor {
+    my $self = shift;
+    my $txn = shift || $self->TransactionObj;
+
+    my $actor = $txn->CreatorObj->PrincipalObj;
+
+    # owner is always treated as inside actor
+    return 0 if $actor->id == $self->TicketObj->Owner;
+
+    if ( $RT::ServiceAgreements{'AssumeOutsideActor'} ) {
+        # All non-admincc users are outside actors
+        return 0 if $self->TicketObj          ->AdminCc->HasMemberRecursively( $actor )
+                 or $self->TicketObj->QueueObj->AdminCc->HasMemberRecursively( $actor );
+
+        return 1;
+    } else {
+        # Only requestors are outside actors
+        return 1 if $self->TicketObj->Requestors->HasMemberRecursively( $actor );
+        return 0;
+    }
+}
+
+sub LastEffectiveAct {
+    my $self = shift;
+
+    my $txns = $self->TicketObj->Transactions;
+    $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' );
+    $txns->Limit( FIELD => 'Type', VALUE => 'Create' );
+    $txns->OrderByCols(
+        { FIELD => 'Created', ORDER => 'DESC' },
+        { FIELD => 'id', ORDER => 'DESC' },
+    );
+
+    my $res;
+    while ( my $txn = $txns->Next ) {
+        unless ( $self->IsOutsideActor( $txn ) ) {
+            last if $res;
+            return ($txn);
+        }
+        $res = $txn;
+    }
+    return ($res, 1);
+}
+
+# Determine if we have replied to the customer yet. If the ticket is
+# owned but there is no correspondence, then we need to reply.
+
+sub NeedsFirstResponse {
+    my $self = shift;
+
+    my $txns = $self->TicketObj->Transactions;
+    # Look for corresponds
+    $txns->Limit( FIELD => 'Type', VALUE => 'Correspond' );
+
+    # Remove any requestor correspondence
+    my $requestors = $self->TicketObj->Requestors->UserMembersObj; # Returns RT group object
+
+    while ( my $requestor = $requestors->Next ){
+        $txns->Limit( FIELD => 'Creator', OPERATOR => '!=', VALUE => $requestor->Id, ENTRYAGGREGATOR => 'AND' );
+    }
+
+    # If we've replied before or this transaction is our reply, first response is covered
+    return 0 if $txns->Count
+        or ($self->TransactionObj->Type eq 'Correspond'
+             and $self->TransactionObj->Creator eq $self->TicketObj->OwnerObj->Id);
+    return 1;
+}
+
+1;
diff --git a/lib/RT/Action/SLA/SetFromCustomField.pm b/lib/RT/Action/SLA/SetFromCustomField.pm
new file mode 100644
index 0000000..75fdc2d
--- /dev/null
+++ b/lib/RT/Action/SLA/SetFromCustomField.pm
@@ -0,0 +1,80 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::SLA::SetFromCustomField;
+
+use base qw(RT::Action);
+
+=head2 Prepare
+
+=cut
+
+sub Prepare {
+    return 1;
+}
+
+=head2 Commit
+
+Set SLA based on CF.
+
+=cut
+
+sub Commit {
+    my $self = shift;
+
+    my $ticket = $self->TicketObj;
+    my ($ret, $msg) = $ticket->SetSLA($self->TransactionObj->NewValue);
+    RT::Logger->error("Unable to update SLA: $msg") unless $ret;
+    return 1;
+
+}
+
+1;
diff --git a/lib/RT/Action/SLA/SetOverdue.pm b/lib/RT/Action/SLA/SetOverdue.pm
new file mode 100644
index 0000000..2c3c20a
--- /dev/null
+++ b/lib/RT/Action/SLA/SetOverdue.pm
@@ -0,0 +1,98 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Action::SLA::SetOverdue;
+
+use base qw(RT::Action::SLA);
+
+use RT::Condition::Overdue;
+
+=head2 Prepare
+
+Checks if the ticket has service level defined.
+
+=cut
+
+sub Prepare {
+    my $self = shift;
+
+    unless ( $self->TicketObj->SLA ) {
+        $RT::Logger->error('SLA::SetOverdue scrip has been applied to ticket #'
+            . $self->TicketObj->id . ' that has no SLA defined');
+        return 0;
+    }
+
+    return 1;
+}
+
+=head2 Commit
+
+Set Overdue custom field according to SLA.
+
+=cut
+
+sub Commit {
+    my $self = shift;
+
+    my $ticket = $self->TicketObj;
+    my $txn = $self->TransactionObj;
+    my $level = $ticket->SLA;
+
+    my $overdue = RT::Condition::Overdue->new(TicketObj => $ticket);
+    return 0 unless $overdue->IsApplicable;
+
+    $ticket->AddCustomFieldValue(
+        Field => 'SLA Overdue',
+        Value => 1,
+    );
+}
+
+1;
diff --git a/lib/RT/Condition/SLA/CustomFieldChanged.pm b/lib/RT/Condition/SLA/CustomFieldChanged.pm
new file mode 100644
index 0000000..2237bf7
--- /dev/null
+++ b/lib/RT/Condition/SLA/CustomFieldChanged.pm
@@ -0,0 +1,88 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Condition::SLA::CustomFieldChanged;
+
+use base qw(RT::Condition);
+
+=head1 NAME
+
+RT::Condition::SLA::CustomFieldChanged - Did the CF change?
+
+=head1 DESCRIPTION
+
+Checks if the configured CF has changed.
+
+To set the desired Custom Field for triggering the SLA change
+in your RT_SiteConfig.pm add the line:
+
+    Set($SLACustomField, 'your field name here');
+
+Then whenever this field is changed, this condition will be met.
+
+=cut
+
+sub IsApplicable {
+    my $self = shift;
+    my $txn = $self->TransactionObj;
+
+    return 0 unless $txn->Type eq 'CustomField';
+
+    my $cf = RT::CustomField->new(RT->SystemUser);
+    my ($ret, $msg) = $cf->Load($txn->Field);
+    RT::Logger->error("Unable to load CF for id: "
+          . $txn->Field . " Error: $msg" ) unless $ret;
+
+    return 1 if $cf->Id and $cf->Name eq RT->Config->Get('SLACustomField');
+    return 0;
+}
+
+1;
diff --git a/lib/RT/Condition/SLA/Overdue.pm b/lib/RT/Condition/SLA/Overdue.pm
new file mode 100644
index 0000000..20dd203
--- /dev/null
+++ b/lib/RT/Condition/SLA/Overdue.pm
@@ -0,0 +1,88 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+=head1 NAME
+
+RT::Condition::SLA::Overdue
+
+=head1 DESCRIPTION
+
+Returns true if the ticket we're operating on is overdue, and if
+the C<SLA Overdue> custom field is not yet set. Used by rt-crontool
+to help set the SLA Overdue field.
+
+=cut
+
+package RT::Condition::SLA::Overdue;
+use base 'RT::Condition';
+use strict;
+use warnings;
+
+use RT::CustomField;
+
+=head2 IsApplicable
+
+If the due date is before "now" and C<SLA Overdue> CF not set, return true
+
+=cut
+
+sub IsApplicable {
+    my $self = shift;
+    
+    my $due = $self->TicketObj->DueObj;
+    my $overdue_cf = $self->TicketObj->FirstCustomFieldValue('SLA Overdue');
+
+    return undef unless $due->IsSet and $due->Unix < time();
+    return undef if defined $overdue_cf and $overdue_cf > 0;
+    return 1;
+}
+
+RT::Base->_ImportOverlays();
+
+1;
+
diff --git a/lib/RT/Condition/SLA/RequireDueSet.pm b/lib/RT/Condition/SLA/RequireDueSet.pm
new file mode 100644
index 0000000..8d70f15
--- /dev/null
+++ b/lib/RT/Condition/SLA/RequireDueSet.pm
@@ -0,0 +1,96 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+#                                          <sales at bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+use strict;
+use warnings;
+
+package RT::Condition::SLA::RequireDueSet;
+
+use base qw(RT::Condition::SLA);
+
+=head1 NAME
+
+RT::Condition::SLA::RequireDueSet - checks if Due date require update
+
+=head1 DESCRIPTION
+
+Checks if Due date require update. This should be done when we create
+a ticket and it has service level value or when we set service level.
+
+You can ignore specific service levels by adding a 
+
+    Set($SLAIgnoreLevels, ... );
+
+which will exclude these SLA levels from requiring a due date set.
+
+=cut
+
+sub IsApplicable {
+    my $self = shift;
+    return 0 unless $self->SLAIsApplied;
+    return 0 if $self->TicketObj->QueueObj->SLADisabled;
+
+    # This SLA level has no actual SLA times attached 
+    return 0 if scalar( grep { $_ eq $self->TicketObj->SLA } @{RT->Config->Get(SLAIgnoreLevels)};
+
+    my $type = $self->TransactionObj->Type;
+    if ( $type eq 'Create' || $type eq 'Correspond' || ($type eq 'Set' && $self->TransactionObj->Field eq 'Owner') ) {
+        return 1 if $self->TicketObj->SLA;
+        return 0;
+    }
+    elsif ( $type eq 'Status' || ($type eq 'Set' && $self->TransactionObj->Field eq 'Status') ) {
+        return 1 if $self->TicketObj->SLA;
+        return 0;
+    }
+    elsif ( $type eq 'SLA' || ($type eq 'Set' && $self->TransactionObj->Field eq 'SLA') ) {
+        return 1;
+    }
+    return 0;
+}
+
+1;
diff --git a/lib/RT/Extension/SLA/Utils.pm b/lib/RT/Extension/SLA/Utils.pm
new file mode 100644
index 0000000..a3a6f9f
--- /dev/null
+++ b/lib/RT/Extension/SLA/Utils.pm
@@ -0,0 +1,101 @@
+use strict;
+use warnings;
+package RT::Extension::SLA::Utils;
+
+our $VERSION = '0.01';
+
+=head1 NAME
+
+RT-Extension-SLA-Utils - additional SLA utilities
+
+=head1 DESCRIPTION
+
+The RT::Extension::SLA::Utils plugin adds additional
+scrip actions and conditions to trigger SLA changes 
+off of a Custom Field change, and support tracking
+which tickets SLAs are overdue.
+
+
+=head1 RT VERSION
+
+Works with RT 4.4.0 and newer.
+
+=head1 INSTALLATION
+
+=over
+
+=item C<perl Makefile.PL>
+
+=item C<make>
+
+=item C<make install>
+
+=item C<make initdb>
+
+May need root permissions
+
+=item Edit your F</opt/rt4/etc/RT_SiteConfig.pm>
+
+If you are using RT 4.2 or greater, add this line:
+
+    Plugin('RT::Extension::SLA::Utils');
+
+or add C<RT::Extension::SLA::Utils> to your existing C<@Plugins> line.
+
+You willl also want to set the Custom Field to use to trigger SLA
+change conditions:
+
+    Set($SLACustomField, 'my field name');
+
+And if you want any SLA levels to not trigger changes:
+
+    Set($SLAIgnoreLevels, 'level 1', 'some other level');
+
+
+=item Setup rt-crontool to run overdue checks
+
+To have the B<SLA Overdue> field set automatically, you can have rt-crontool
+run the checks periodically to update the field.  You can use rt-crontool with cron
+such as:
+
+    rt-crontool --search RT::Search::ActiveTicketsInQueue --search-arg <queue_name> \
+        --condition RT::Condition::SLA::Overdue --action RT::Action::SLA::SetOverdue
+
+Replace the <queue_name> with the name of the queue you would like the check run on,
+such as 'General'.  If you want to run the checks on multiple queues, you will need
+a line for each queue. 
+
+
+=item Clear your mason cache
+
+    rm -rf /opt/rt4/var/mason_data/obj
+
+=item Restart your webserver
+
+=back
+
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=head1 BUGS
+
+All bugs should be reported via email to
+
+    L<bug-RT-Extension-SLA-Utils at rt.cpan.org|mailto:bug-RT-Extension-SLA-Utils at rt.cpan.org>
+
+or via the web at
+
+    L<rt.cpan.org|http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-SLA-Utils>.
+
+=head1 LICENSE AND COPYRIGHT
+
+This software is Copyright (c) 2017 by Dave Goehrig
+
+This is free software, licensed under:
+
+  The GNU General Public License, Version 2, June 1991
+
+=cut
+
+1;
diff --git a/lib/RT/Extension/SLA/Utils/Test.pm.in b/lib/RT/Extension/SLA/Utils/Test.pm.in
new file mode 100644
index 0000000..d38a4c7
--- /dev/null
+++ b/lib/RT/Extension/SLA/Utils/Test.pm.in
@@ -0,0 +1,119 @@
+use strict;
+use warnings;
+
+### after: use lib qw(@RT_LIB_PATH@);
+use lib qw(/opt/rt4/local/lib /opt/rt4/lib);
+
+package RT::Extension::SLA::Utils::Test;
+
+our @ISA;
+BEGIN {
+    local $@;
+    eval { require RT::Test; 1 } or do {
+        require Test::More;
+        Test::More::BAIL_OUT(
+            "requires 3.8 to run tests. Error:\n$@\n"
+            ."You may need to set PERL5LIB=/path/to/rt/lib"
+        );
+    };
+    push @ISA, 'RT::Test';
+}
+
+sub import {
+    my $class = shift;
+    my %args  = @_;
+
+    $args{'requires'} ||= [];
+    if ( $args{'testing'} ) {
+        unshift @{ $args{'requires'} }, 'RT::Extension::SLA::Utils';
+    } else {
+        $args{'testing'} = 'RT::Extension::SLA::Utils';
+    }
+
+    $class->SUPER::import( %args );
+    $class->export_to_level(1);
+
+    require RT::Extension::SLA::Utils;
+}
+
+sub bootstrap_more_config{
+    my $self = shift;
+    my $config = shift;
+    my $args_ref = shift;
+
+    my $sla = <<"CONFIG";
+Set(%ServiceAgreements, (
+    Default => 'Informational',
+    QueueDefault => {
+        'General' => 'Informational',
+    },
+    Levels => {
+        # The "Initial" period handled by RT::Action::SLA::SetDue
+        'Critical' => {
+            Starts      => { RealMinutes => 0     },
+            Initial     => { RealMinutes => 10    }, # Initial Response
+            FirstResponse    => { RealMinutes => 30    }, # First Response
+            KeepInLoop  => { RealMinutes => 60*1  }, # Updates
+            Resolve     => { RealMinutes => 60*4  }, # Resolution
+        },
+        'Major' => {
+            Starts      => { RealMinutes => 0     },
+            Initial     => { RealMinutes => 15    }, # Initial Response
+            FirstResponse    => { RealMinutes => 30    }, # First Response
+            KeepInLoop  => { RealMinutes => 60*24 }, # Updates
+            Resolve     => { RealMinutes => 60*48 }, # Resolution
+        },
+        'Minor' => {
+            Starts      => { RealMinutes => 0     },
+            Initial     => { RealMinutes => 60    }, # Initial Response
+            FirstResponse    => { RealMinutes => 60*2  }, # First Response
+            KeepInLoop  => { RealMinutes => 60*24 }, # Updates
+            Resolve     => { RealMinutes => 60*48 }, # Resolution
+        },
+        'Informational' => {
+            Starts      => { RealMinutes => 0     },
+            Initial     => { RealMinutes => 60    }, # Initial Response
+            FirstResponse    => { RealMinutes => 60*8  }, # First Response
+            KeepInLoop  => { RealMinutes => 60*24 }, # Updates
+            Resolve     => { RealMinutes => 60*72 }, # Resolution
+        },
+        'Trivial' => {
+         # all ignored; this level is N/A via SLAIgnoreLevels configuration value
+            Starts      => { RealMinutes => 0 },
+            Initial     => { RealMinutes => 1 },
+            FirstResponse    => { RealMinutes => 2 },
+            KeepInLoop  => { RealMinutes => 3 },
+            Resolve     => { RealMinutes => 4 },
+        }
+    },
+));
+Set(\$SLACustomField, 'Impact');
+Set(\$SLAIngnoreLevels, 'Trivial');
+CONFIG
+
+    print $config $sla;
+    
+    # Create the Impact custom field we will use for testing per the config
+    my $cf = CustomField->new(RT->SystemUser);
+    $cf->Create(
+        LookupType  => 'RT::Queue-RT::Ticket',  # for Tickets
+        Name => 'Impact',
+        Type => 'SelectSingle',  # SelectSingle is the same as: Type => 'Select', MaxValues => 1
+        RenderType  => 'Dropdown',
+        Values      => [
+            { Name => 'Critical' },
+            { Name => 'Informational' },
+            { Name => 'Major' },
+            { Name => 'Minor' },
+            { Name => 'Order to Activation' },
+        ]
+    );
+
+    my $queue = RT::Queue->new(RT->SystemUser);
+    $queue->Load('General');            # apply Impact to General queue
+    $cf->SetContextObject($queue);
+
+    return;
+}
+
+1;
diff --git a/xt/basic.t b/xt/basic.t
new file mode 100644
index 0000000..2d85ea1
--- /dev/null
+++ b/xt/basic.t
@@ -0,0 +1,38 @@
+use strict;
+use warnings;
+
+use RT::Extension::SLA::Utils::Test tests => undef;
+
+use_ok('RT::Extension::SLA::Utils');
+use_ok('RT::CustomField');
+
+
+my $queue = RT::Test->load_or_create_queue( Name => 'General' );
+ok( $queue->id, "loaded the General queue" );
+ok( $queue->SetSLADisabled(0), 'SLA enabled');
+
+
+# Create the Impact custom field we will use for testing per the config
+my $cf = RT::CustomField->new(RT->SystemUser);
+$cf->Create(
+    LookupType  => 'RT::Queue-RT::Ticket',  # for Tickets
+    Name => 'Impact',
+    Type => 'SelectSingle',  # SelectSingle is the same as: Type => 'Select', MaxValues => 1
+    RenderType  => 'Dropdown',
+    Values      => [
+        { Name => 'Critical' },
+        { Name => 'Informational' },
+        { Name => 'Major' },
+        { Name => 'Minor' },
+        { Name => 'Order to Activation' },
+    ]
+);
+$cf->AddToObject($queue);
+
+RT::Test->set_rights(
+    Principal => 'Everyone',
+    Right => ['CreateTicket', 'ShowTicket', 'SeeQueue', 'ReplyToTicket', 'ModifyTicket'],
+);
+
+
+done_testing;

-----------------------------------------------------------------------


More information about the Bps-public-commit mailing list