[Rt-commit] r17204 - in rt/3.8/trunk: . etc etc/upgrade/3.8.2 lib/RT lib/RT/Approval lib/RT/Approval/Rule t/approval

clkao at bestpractical.com clkao at bestpractical.com
Sat Dec 13 02:19:24 EST 2008


Author: clkao
Date: Sat Dec 13 02:19:23 2008
New Revision: 17204

Added:
   rt/3.8/trunk/lib/RT/Approval/
   rt/3.8/trunk/lib/RT/Approval.pm
   rt/3.8/trunk/lib/RT/Approval/Rule/
   rt/3.8/trunk/lib/RT/Approval/Rule.pm
   rt/3.8/trunk/lib/RT/Approval/Rule/Created.pm
   rt/3.8/trunk/lib/RT/Approval/Rule/NewPending.pm
   rt/3.8/trunk/lib/RT/Approval/Rule/Passed.pm
   rt/3.8/trunk/lib/RT/Approval/Rule/Rejected.pm
   rt/3.8/trunk/lib/RT/Rule.pm
   rt/3.8/trunk/lib/RT/Ruleset.pm
Modified:
   rt/3.8/trunk/   (props changed)
   rt/3.8/trunk/etc/RT_Config.pm.in
   rt/3.8/trunk/etc/initialdata
   rt/3.8/trunk/etc/upgrade/3.8.2/content
   rt/3.8/trunk/lib/RT/Action/SendEmail.pm
   rt/3.8/trunk/lib/RT/Template_Overlay.pm
   rt/3.8/trunk/lib/RT/Ticket_Overlay.pm
   rt/3.8/trunk/lib/RT/Transaction_Overlay.pm
   rt/3.8/trunk/t/approval/basic.t

Log:
Merge ruleset branch to trunk.
 r37974 at mtl (orig r17133):  clkao | 2008-12-07 12:53:04 +0800
 - Create branch ruleset
 r37981 at mtl (orig r17134):  clkao | 2008-12-07 17:33:51 +0800
 first cut of the rule system, which is an alternative path
 from transaction to RT::Actions, wrapped in RT::Rule and can be
 bundled in RT::Ruleset.  The approval process has been refactored
 into using the rule system.
 
 r37982 at mtl (orig r17135):  clkao | 2008-12-07 21:28:17 +0800
 merge allpassed rule with passed for rt::approval.
 
 r37983 at mtl (orig r17136):  clkao | 2008-12-07 22:26:58 +0800
 make sendemail action log originating scrip as #rule if does
 not have scripobj.
 r37984 at mtl (orig r17137):  clkao | 2008-12-07 22:39:01 +0800
 - only correspond txn content should be approver's note.
 - when approval passed, use the associated template for sending
   correspond rather than calling notify action and comment.
 - no longer needs the T:: hack in template.
 - put $Notes into the approval templates.
 
 r37985 at mtl (orig r17138):  clkao | 2008-12-07 23:02:35 +0800
 test for approval all passed template.
 
 r37989 at mtl (orig r17142):  clkao | 2008-12-07 23:39:36 +0800
 - change rejection logic so the first rejection triggers
   the original ticket to be rejected (and notified by the
   correct actor.)
 
 r37990 at mtl (orig r17143):  clkao | 2008-12-08 00:12:52 +0800
 improve newpending notification to include the content of the approval
 ticket we created from the workflow template.
 
 r38023 at mtl (orig r17153):  clkao | 2008-12-09 23:46:57 +0800
 allow template object to be passed directly to RunScripAction.
 r38024 at mtl (orig r17154):  clkao | 2008-12-09 23:59:16 +0800
 automatically open leaves of the approval tree with
 transactionbatch.
 r38077 at mtl (orig r17171):  ruz | 2008-12-11 17:56:48 +0800
 * add content upgrader for approvals
 r38135 at mtl (orig r17182):  clkao | 2008-12-12 19:21:20 +0800
 make UseTransactionBatch default.
 r38136 at mtl (orig r17183):  clkao | 2008-12-12 19:32:19 +0800
 refactor common approval templating code.
 r38137 at mtl (orig r17184):  clkao | 2008-12-12 19:38:39 +0800
 prefix the approval templates with "RT ", so it's easier for the upgrade
 to process, since the semantics of the templates have also changed.
 
 r38139 at mtl (orig r17186):  clkao | 2008-12-12 20:06:06 +0800
 when all approvals are passed, also notify the owner that the request can now be acted on.
 r38156 at mtl (orig r17187):  ruz | 2008-12-13 01:21:33 +0800
 * delete prefix from templates, we'll add prefix to OLD templates instead
 r38157 at mtl (orig r17188):  ruz | 2008-12-13 01:22:12 +0800
 * rename old and add new templates for approvals
 r38158 at mtl (orig r17189):  ruz | 2008-12-13 01:32:25 +0800
 * remove 'RT ' templates prefix in the code


Modified: rt/3.8/trunk/etc/RT_Config.pm.in
==============================================================================
--- rt/3.8/trunk/etc/RT_Config.pm.in	(original)
+++ rt/3.8/trunk/etc/RT_Config.pm.in	Sat Dec 13 02:19:23 2008
@@ -1375,7 +1375,7 @@
 
 =cut
 
-Set($UseTransactionBatch, 0);
+Set($UseTransactionBatch, 1);
 
 =item C<@CustomFieldValuesSources>
 

Modified: rt/3.8/trunk/etc/initialdata
==============================================================================
--- rt/3.8/trunk/etc/initialdata	(original)
+++ rt/3.8/trunk/etc/initialdata	Sat Dec 13 02:19:23 2008
@@ -319,24 +319,29 @@
     {  Queue       => '___Approvals',
        Name        => "Approval Passed",    # loc
        Description =>
-         "Notify Owner of their ticket has been approved by some approver", # loc
+         "Notify Requestor of their ticket has been approved by some approver", # loc
        Content => 'Subject: Ticket Approved: {$Ticket->Subject}
 
 Greetings,
 
 Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
 Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
 '
     },
     {  Queue       => '___Approvals',
        Name        => "All Approvals Passed",    # loc
        Description =>
-         "Notify Owner of their ticket has been approved by all approvers", # loc
+         "Notify Requestor of their ticket has been approved by all approvers", # loc
        Content => 'Subject: Ticket Approved: {$Ticket->Subject}
 
 Greetings,
 
-Your ticket has been approved.  Its Owner may now start to act on it.
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
 '
     },
     {  Queue       => '___Approvals',
@@ -348,6 +353,20 @@
 Greetings,
 
 Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Ready for Owner",    # loc
+       Description =>
+         "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
 '
     },
     {  Queue       => 0,
@@ -501,167 +520,6 @@
        ScripCondition => 'On Transaction',
        ScripAction    => 'Extract Subject Tag',
        Template       => 'Blank' },
-    {  Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval",    # loc
-       Queue          => '___Approvals',
-       ScripCondition => 'User Defined',
-       CustomIsApplicableCode => q[
-	    $self->TicketObj->Type eq 'approval'	and
-	    $self->TransactionObj->Field eq 'Status'	and
-	    $self->TransactionObj->NewValue eq 'open'   and
-	    eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
-       ],
-       ScripAction    => 'Notify Owner',
-       Template       => 'New Pending Approval' },
-    {  Description => "If an approval is rejected, reject the original and delete pending approvals",    # loc
-       Queue            => '___Approvals',
-       ScripCondition   => 'On Status Change',
-       ScripAction      => 'User Defined',
-       CustomPrepareCode => q[
-# ------------------------------------------------------------------- #
-return(0) unless ( lc($self->TransactionObj->NewValue) eq "rejected" or
-	           lc($self->TransactionObj->NewValue) eq "deleted" );
-
-my $rejected = 0;
-my $links = $self->TicketObj->DependedOnBy;
-foreach my $link (@{ $links->ItemsArrayRef }) {
-    my $obj = $link->BaseObj;
-    if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
-	if ($obj->Type eq 'ticket') {
-	    $obj->Comment(
-		Content	=> $self->loc("Your request was rejected."),
-	    );
-	    $obj->SetStatus(
-		Status	=> 'rejected',
-		Force	=> 1,
-	    );
-
-	    $T::Approval = $self->TicketObj; # so we can access it inside templates
-	    $self->{TicketObj} = $obj;  # we want the original id in the token line
-	    $rejected = 1;
-	}
-	else {
-	    $obj->SetStatus(
-		Status	=> 'deleted',
-		Force	=> 1,
-	    );
-	}
-    }
-}
-
-$links = $self->TicketObj->DependsOn;
-foreach my $link (@{ $links->ItemsArrayRef }) {
-    my $obj = $link->TargetObj;
-    if ($obj->QueueObj->IsActiveStatus($obj->Status)) {
-	$obj->SetStatus(
-	    Status	=> 'deleted',
-	    Force	=> 1,
-	);
-    }
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return $rejected;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template          => 'Approval Rejected', },
-    {  Description => "When a ticket has been approved by any approver, add correspondence to the original ticket", # loc
-       Queue             => '___Approvals',
-       ScripCondition    => 'On Resolve',
-       ScripAction       => 'User Defined',
-       CustomPrepareCode => q[
-# ------------------------------------------------------------------- #
-return(0) unless ($self->TicketObj->Type eq 'approval');
-
-my $note;
-my $t = $self->TicketObj->Transactions;
-while (my $o = $t->Next) {
-    $note .= $o->Content . "\n" if $o->ContentObj
-	    and $o->Content !~ /Default Approval/;
-}
-
-foreach my $obj ($self->TicketObj->AllDependedOnBy( Type => 'ticket' )) {
-    $obj->Comment(
-	Content => $self->loc( "Your request has been approved by [_1]. Other approvals may still be pending.", # loc
-	    $self->TransactionObj->CreatorObj->Name,
-	) . "\n" . $self->loc( "Approver's notes: [_1]", # loc
-	    $note
-	),
-    );
-    $T::Approval = $self->TicketObj; # so we can access it inside templates
-    $self->{TicketObj} = $obj;  # we want the original id in the token line
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return 1;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template => 'Approval Passed' },
-    {  Description => "When a ticket has been approved by all approvers, add correspondence to the original ticket", # loc
-       Queue             => '___Approvals',
-       ScripCondition    => 'On Resolve',
-       ScripAction       => 'User Defined',
-       CustomPrepareCode  => q[
-# ------------------------------------------------------------------- #
-# Find all the tickets that depend on this (that this is approving)
-
-my $Ticket = $self->TicketObj;
-my @TOP    = $Ticket->AllDependedOnBy( Type => 'ticket' );
-my $links  = $Ticket->DependedOnBy;
-my $passed = 0;
-
-while (my $link = $links->Next) {
-    my $obj = $link->BaseObj;
-    next if ($obj->HasUnresolvedDependencies( Type => 'approval' ));
-
-    if ($obj->Type eq 'ticket') {
-	$obj->Comment(
-	    Content	=> $self->loc("Your request has been approved."),
-	);
-	$T::Approval  = $Ticket;    # so we can access it inside templates
-	$self->{TicketObj} = $obj;  # we want the original id in the token line
-	$passed = 1;
-    }
-    elsif ($obj->Type eq 'approval') {
-	$obj->SetStatus( Status => 'open', Force => 1 );
-    }
-    elsif (RT->Config->Get('UseCodeTickets') and $obj->Type eq 'code') {
-        #XXX: RT->Config->Get('UseCodeTickets') used only once here!!!
-	my $code = $obj->Transactions->First->Content;
-	my $rv;
-
-	foreach my $TOP (@TOP) {
-	    local $@;
-	    $rv++ if eval $code;
-	    $RT::Logger->error("Cannot eval code: $@") if $@;
-	}
-
-	if ($rv or !@TOP) {
-	    $obj->SetStatus( Status	=> 'resolved', Force	=> 1,);
-	}
-	else {
-	    $obj->SetStatus( Status	=> 'rejected', Force	=> 1,);
-	}
-    }
-}
-
-# Now magically turn myself into a Requestor Notify object...
-require RT::Action::Notify; bless($self, 'RT::Action::Notify');
-$self->{Argument} = 'Requestor'; $self->Prepare;
-
-return 0; # ignore $passed;
-# ------------------------------------------------------------------- #
-	],
-       CustomCommitCode => '"never needed"',
-       Template => 'All Approvals Passed', },
-
 );
 
 @ACL = (

Modified: rt/3.8/trunk/etc/upgrade/3.8.2/content
==============================================================================
--- rt/3.8/trunk/etc/upgrade/3.8.2/content	(original)
+++ rt/3.8/trunk/etc/upgrade/3.8.2/content	Sat Dec 13 02:19:23 2008
@@ -1,9 +1,111 @@
+ at Initial = (
+    sub {
+        $RT::Logger->warning(
+            "Going to add [OLD] prefix to all temlates in approvals queue."
+            ." If you never used approvals then you can delete all these"
+            ." templates with [OLD] prefix. Leave new there may be you will"
+            ." want to use approvals some time."
+        );
+
+        my $approvals_q = RT::Queue->new( $RT::SystemUser );
+        $approvals_q->Load('___Approvals');
+        unless ( $approvals_q->id ) {
+            $RT::Logger->error("You have no approvals queue.");
+            return 1;
+        }
+
+        my $templates = RT::Templates->new( $RT::SystemUser );
+        $templates->LimitToQueue( $approvals_q->id );
+        while ( my $tmpl = $templates->Next ) {
+            my ($status, $msg) = $tmpl->SetName( "[OLD] ". $tmpl->Name );
+            unless ( $status ) {
+                $RT::Logger->error("Couldn't rename template #". $tmpl->id .": $msg");
+            }
+        }
+        return 1;
+    },
+);
 @ACL = (
     { GroupDomain => 'SystemInternal',
       GroupType => 'privileged',
       Right  => 'ShowApprovalsTab', },
 );
 
+ at Templates = (
+    {  Queue       => '___Approvals',
+       Name        => "New Pending Approval",    # loc
+       Description =>
+         "Notify Owners and AdminCcs of new items pending their approval", # loc
+       Content => 'Subject: New Pending Approval: {$Ticket->Subject}
+
+Greetings,
+
+There is a new item pending your approval: "{$Ticket->Subject()}", 
+a summary of which appears below.
+
+Please visit {RT->Config->Get(\'WebURL\')}Approvals/Display.html?id={$Ticket->id}
+to approve or reject this ticket, or {RT->Config->Get(\'WebURL\')}Approvals/ to
+batch-process all your pending approvals.
+
+-------------------------------------------------------------------------
+{$Transaction->Content()}
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Passed",    # loc
+       Description =>
+         "Notify Requestor of their ticket has been approved by some approver", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Other approvals may be pending.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "All Approvals Passed",    # loc
+       Description =>
+         "Notify Requestor of their ticket has been approved by all approvers", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been approved by { eval { $Approval->OwnerObj->Name } }.
+Its Owner may now start to act on it.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Rejected",    # loc
+       Description =>
+         "Notify Owner of their rejected ticket", # loc
+       Content => 'Subject: Ticket Rejected: {$Ticket->Subject}
+
+Greetings,
+
+Your ticket has been rejected by { eval { $Approval->OwnerObj->Name } }.
+
+Approver\'s notes: { $Notes }
+'
+    },
+    {  Queue       => '___Approvals',
+       Name        => "Approval Ready for Owner",    # loc
+       Description =>
+         "Notify Owner of their ticket has been approved and is ready to be acted on", # loc
+       Content => 'Subject: Ticket Approved: {$Ticket->Subject}
+
+Greetings,
+
+The ticket has been approved, you may now start to act on it.
+
+'
+    },
+);
+
 @Final = (
     sub {
         $RT::Logger->debug("Going to adjust dashboards");
@@ -44,4 +146,41 @@
         $RT::Logger->debug("Fixed.");
         return 1;
     },
+    sub {
+        my $approvals_q = RT::Queue->new( $RT::SystemUser );
+        $approvals_q->Load('___Approvals');
+        unless ( $approvals_q->id ) {
+            $RT::Logger->error("You have no approvals queue.");
+            return 1;
+        }
+
+        require File::Temp;
+        my ($tmp_fh, $tmp_fn) = File::Temp::tempfile( 'rt-approvals-scrips-XXXX', CLEANUP => 0 );
+        unless ( $tmp_fh ) {
+            $RT::Logger->error("Couldn't create temporary file.");
+            return 0;
+        }
+
+        $RT::Logger->warning(
+            "IMPORTANT: We're going to delete all scrips in Approvals queue"
+            ." and save them in '$tmp_fn' file."
+        );
+
+        require Data::Dumper;
+
+        my $scrips = RT::Scrips->new( $RT::SystemUser );
+        $scrips->LimitToQueue( $approvals_q->id );
+        while ( my $scrip = $scrips->Next ) {
+            my %tmp =
+                map { $tmp->{ $_ } = $scrip->_Value( $_ ) }
+                $scrip->ReadableAttributes;
+
+            print $tmp_fh Data::Dumper::Dumper( \%tmp );
+
+            my ($status, $msg) = $scrip->Delete;
+            unless ( $status ) {
+                $RT::Logger->error( "Couldn't delete scrip: $msg");
+            }
+        }
+    },
 );

Modified: rt/3.8/trunk/lib/RT/Action/SendEmail.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Action/SendEmail.pm	(original)
+++ rt/3.8/trunk/lib/RT/Action/SendEmail.pm	Sat Dec 13 02:19:23 2008
@@ -302,7 +302,7 @@
             . $self->TicketObj->id . "/"
             . $self->TransactionObj->id
             . " - Scrip "
-            . $self->ScripObj->id . " "
+            . ($self->ScripObj->id || '#rule'). " "
             . ( $self->ScripObj->Description || '' ) );
 
     my $status = RT::Interface::Email::SendEmail(

Added: rt/3.8/trunk/lib/RT/Approval.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,16 @@
+package RT::Approval;
+use strict;
+use warnings;
+
+use RT::Ruleset;
+
+RT::Ruleset->Add(
+    Name => 'Approval',
+    Rules => [
+        'RT::Approval::Rule::NewPending',
+        'RT::Approval::Rule::Rejected',
+        'RT::Approval::Rule::Passed',
+        'RT::Approval::Rule::Created',
+    ]);
+
+1;

Added: rt/3.8/trunk/lib/RT/Approval/Rule.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,27 @@
+package RT::Approval::Rule;
+use strict;
+use warnings;
+
+use base 'RT::Rule';
+
+use constant _Queue => '___Approvals';
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+    $self->TicketObj->Type eq 'approval';
+}
+
+sub GetTemplate {
+    my ($self, $template_name, %args) = @_;
+    my $template = RT::Template->new($self->CurrentUser);
+    $template->Load($template_name) or return;
+    my ($result, $msg) = $template->Parse(%args);
+
+    # XXX: error handling
+
+    return $template;
+}
+
+1;
+

Added: rt/3.8/trunk/lib/RT/Approval/Rule/Created.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/Created.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,23 @@
+package RT::Approval::Rule::Created;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant _Stage => 'TransactionBatch';
+
+use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->TransactionObj->Type eq 'Create' &&
+    !$self->TicketObj->HasUnresolvedDependencies( Type => 'approval' );
+}
+
+sub Commit {
+    my $self = shift;
+    $self->RunScripAction('Open Tickets' => 'Blank');
+}
+
+1;

Added: rt/3.8/trunk/lib/RT/Approval/Rule/NewPending.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/NewPending.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,49 @@
+package RT::Approval::Rule::NewPending;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "When an approval ticket is created, notify the Owner and AdminCc of the item awaiting their approval"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->OnStatusChange('open') and
+    eval { $T::Approving = ($self->TicketObj->AllDependedOnBy( Type => 'ticket' ))[0] }
+}
+
+sub Commit {
+    my $self = shift;
+    my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' );
+    my $t = $self->TicketObj->Transactions;
+    my $to;
+    while ( my $o = $t->Next ) {
+        $to = $o, last if $o->Type eq 'Create';
+    }
+
+    # XXX: this makes the owner incorrect so notify owner won't work
+    # local $self->{TicketObj} = $top;
+
+    # first txn entry of the approval ticket
+    local $self->{TransactionObj} = $to;
+    $self->RunScripAction('Notify Owner', 'New Pending Approval', @_);
+
+    return;
+
+    # this generates more correct content of the message, but not sure
+    # if ccmessageto is the right way to do this.
+    my $template = $self->GetTemplate('New Pending Approval',
+                                      TicketObj => $top,
+                                      TransactionObj => $to)
+        or return;
+
+    my ( $result, $msg ) = $template->Parse(
+        TicketObj => $top,
+    );
+    $self->TicketObj->Comment( CcMessageTo => $self->TicketObj->OwnerObj->EmailAddress,
+                               MIMEObj => $template->MIMEObj );
+
+}
+
+1;

Added: rt/3.8/trunk/lib/RT/Approval/Rule/Passed.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/Passed.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,53 @@
+package RT::Approval::Rule::Passed;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "Notify Owner of their ticket has been approved by some or all approvers"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    $self->OnStatusChange('resolved');
+}
+
+sub Commit {
+    my $self = shift;
+    my $note;
+    my $t = $self->TicketObj->Transactions;
+
+    while ( my $o = $t->Next ) {
+        next unless $o->Type eq 'Correspond';
+        $note .= $o->Content . "\n" if $o->ContentObj;
+    }
+    my ($top) = $self->TicketObj->AllDependedOnBy( Type => 'ticket' );
+    my $links  = $self->TicketObj->DependedOnBy;
+
+    while ( my $link = $links->Next ) {
+        my $obj = $link->BaseObj;
+        next unless $obj->Type eq 'approval';
+        next if $obj->HasUnresolvedDependencies( Type => 'approval' );
+
+        $obj->SetStatus( Status => 'open', Force => 1 );
+    }
+
+    my $passed = !$top->HasUnresolvedDependencies( Type => 'approval' );
+    my $template = $self->GetTemplate(
+        $passed ? 'All Approvals Passed' : 'Approval Passed',
+        TicketObj => $top,
+        Approval => $self->TicketObj,
+        Notes => $note,
+    ) or die;
+
+    $top->Correspond( MIMEObj => $template->MIMEObj );
+
+    if ($passed) {
+        $self->RunScripAction('Notify Owner', 'Approval Ready for Owner',
+                              TicketObj => $top);
+    }
+
+    return;
+}
+
+1;

Added: rt/3.8/trunk/lib/RT/Approval/Rule/Rejected.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Approval/Rule/Rejected.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,57 @@
+package RT::Approval::Rule::Rejected;
+use strict;
+use warnings;
+use base 'RT::Approval::Rule';
+
+use constant Description => "If an approval is rejected, reject the original and delete pending approvals"; # loc
+
+sub Prepare {
+    my $self = shift;
+    return unless $self->SUPER::Prepare();
+
+    return (0)
+        unless $self->OnStatusChange('rejected') or $self->OnStatusChange('deleted')
+}
+
+sub Commit {    # XXX: from custom prepare code
+    my $self = shift;
+    if ( my ($rejected) =
+        $self->TicketObj->AllDependedOnBy( Type => 'ticket' ) ) {
+        my $template = $self->GetTemplate('Approval Rejected',
+                                          TicketObj => $rejected,
+                                          Approval  => $self->TicketObj,
+                                          Notes     => '');
+
+        $rejected->Correspond( MIMEObj => $template->MIMEObj );
+        $rejected->SetStatus(
+            Status => 'rejected',
+            Force  => 1,
+        );
+    }
+    my $links = $self->TicketObj->DependedOnBy;
+    foreach my $link ( @{ $links->ItemsArrayRef } ) {
+        my $obj = $link->BaseObj;
+        if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+            if ( $obj->Type eq 'approval' ) {
+                $obj->SetStatus(
+                    Status => 'deleted',
+                    Force  => 1,
+                );
+            }
+        }
+    }
+
+    $links = $self->TicketObj->DependsOn;
+    foreach my $link ( @{ $links->ItemsArrayRef } ) {
+        my $obj = $link->TargetObj;
+        if ( $obj->QueueObj->IsActiveStatus( $obj->Status ) ) {
+            $obj->SetStatus(
+                Status => 'deleted',
+                Force  => 1,
+            );
+        }
+    }
+
+}
+
+1;

Added: rt/3.8/trunk/lib/RT/Rule.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Rule.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,60 @@
+package RT::Rule;
+use strict;
+use warnings;
+use base 'RT::Action';
+
+use constant _Stage => 'TransactionCreate';
+use constant _Queue => undef;
+
+sub Prepare {
+    my $self = shift;
+    return (0) if $self->_Queue && $self->TicketObj->QueueObj->Name ne $self->_Queue;
+    return 1;
+}
+
+sub Commit  {
+    my $self = shift;
+    return(0, $self->loc("Commit Stubbed"));
+}
+
+sub Describe {
+    my $self = shift;
+    return $self->loc( $self->Description );
+}
+
+sub OnStatusChange {
+    my ($self, $value) = @_;
+
+    $self->TransactionObj->Type eq 'Status' and
+    $self->TransactionObj->Field eq 'Status' and
+    $self->TransactionObj->NewValue eq $value
+}
+
+sub RunScripAction {
+    my ($self, $scrip_action, $template, %args) = @_;
+    my $ScripAction = RT::ScripAction->new($self->CurrentUser);
+    $ScripAction->Load($scrip_action) or die ;
+
+    unless (ref($template)) {
+        # XXX: load per-queue template
+        #    $template->LoadQueueTemplate( Queue => ..., ) || $template->LoadGlobalTemplate(...)
+
+        my $t = RT::Template->new($self->CurrentUser);
+        $t->Load($template) or die;
+        $template = $t;
+    }
+
+    my $action = $ScripAction->LoadAction( TransactionObj => $self->TransactionObj,
+                                           TicketObj => $self->TicketObj,
+                                           %args,
+                                       );
+
+    # XXX: fix template to allow additional arguments to be passed from here
+    $action->{'TemplateObj'} = $template;
+    $action->{'ScripObj'} = RT::Scrip->new($self->CurrentUser); # Stub. sendemail action really wants a scripobj available
+    $action->Prepare or return;
+    $action->Commit;
+
+}
+
+1;

Added: rt/3.8/trunk/lib/RT/Ruleset.pm
==============================================================================
--- (empty file)
+++ rt/3.8/trunk/lib/RT/Ruleset.pm	Sat Dec 13 02:19:23 2008
@@ -0,0 +1,36 @@
+package RT::Ruleset;
+use strict;
+use warnings;
+
+use base 'Class::Accessor::Fast';
+use UNIVERSAL::require;
+
+__PACKAGE__->mk_accessors(qw(Name Rules));
+
+my @RULE_SETS;
+
+sub FindAllRules {
+    my ($class, %args) = @_;
+    return [
+        grep { $_->Prepare }
+        map { $_->new(CurrentUser => $RT::SystemUser, %args) }
+        grep { $_->_Stage eq $args{Stage} }
+        map { @{$_->Rules} } @RULE_SETS
+    ];
+}
+
+sub CommitRules {
+    my ($class, $rules) = @_;
+    $_->Commit
+        for @$rules;
+}
+
+sub Add {
+    my ($class, %args) = @_;
+    for (@{$args{Rules}}) {
+        $_->require or die $UNIVERSAL::require::ERROR;
+    }
+    push @RULE_SETS, $class->new(\%args);
+}
+
+1;

Modified: rt/3.8/trunk/lib/RT/Template_Overlay.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Template_Overlay.pm	(original)
+++ rt/3.8/trunk/lib/RT/Template_Overlay.pm	Sat Dec 13 02:19:23 2008
@@ -414,7 +414,6 @@
 
     my $is_broken = 0;
     my $retval = $template->fill_in(
-        PACKAGE => 'T',
         HASH => \%args,
         BROKEN => sub {
             my (%args) = @_;

Modified: rt/3.8/trunk/lib/RT/Ticket_Overlay.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Ticket_Overlay.pm	(original)
+++ rt/3.8/trunk/lib/RT/Ticket_Overlay.pm	Sat Dec 13 02:19:23 2008
@@ -3164,6 +3164,15 @@
         TransactionObj => $batch->[0],
         Type           => join( ',', map $_->Type, grep defined, @{$batch} )
     );
+
+    # Entry point of the rule system
+    my $rules = RT::Ruleset->FindAllRules(
+        Stage          => 'TransactionBatch',
+        TicketObj      => $self,
+        TransactionObj => $batch->[0],
+        Type           => join( ',', map $_->Type, grep defined, @{$batch} )
+    );
+    RT::Ruleset->CommitRules($rules);
 }
 
 # }}}

Modified: rt/3.8/trunk/lib/RT/Transaction_Overlay.pm
==============================================================================
--- rt/3.8/trunk/lib/RT/Transaction_Overlay.pm	(original)
+++ rt/3.8/trunk/lib/RT/Transaction_Overlay.pm	Sat Dec 13 02:19:23 2008
@@ -78,11 +78,11 @@
 
 use RT::Attachments;
 use RT::Scrips;
+use RT::Ruleset;
 
 use HTML::FormatText;
 use HTML::TreeBuilder;
 
-
 # {{{ sub Create 
 
 =head2 Create
@@ -172,9 +172,21 @@
             Ticket      => $args{'ObjectId'},
             Transaction => $self->id,
         );
+
+       # Entry point of the rule system
+       my $ticket = RT::Ticket->new($RT::SystemUser);
+       $ticket->Load($args{'ObjectId'});
+       my $rules = RT::Ruleset->FindAllRules(
+            Stage       => 'TransactionCreate',
+            Type        => $args{'Type'},
+            TicketObj   => $ticket,
+            TransactionObj => $self,
+       );
+
         if ($args{'CommitScrips'} ) {
             $RT::Logger->debug('About to commit scrips for transaction #' .$self->Id);
             $self->{'scrips'}->Commit();
+            RT::Ruleset->CommitRules($rules);
         }
     }
 

Modified: rt/3.8/trunk/t/approval/basic.t
==============================================================================
--- rt/3.8/trunk/t/approval/basic.t	(original)
+++ rt/3.8/trunk/t/approval/basic.t	Sat Dec 13 02:19:23 2008
@@ -8,33 +8,30 @@
         or plan skip_all => 'require Email::Abstract and Test::Email';
 }
 
-plan tests => 28;
+plan tests => 38;
 
 use RT;
 use RT::Test;
 use RT::Test::Email;
 
 RT->Config->Set( LogToScreen => 'debug' );
-
+RT->Config->Set( UseTransactionBatch => 1 );
 my ($baseurl, $m) = RT::Test->started_ok;
 
-my ($user_a, $user_b) = (RT::User->new($RT::SystemUser), RT::User->new($RT::SystemUser));
-my ($user_c) = RT::User->new($RT::SystemUser);
-
-$user_a->Create( Name => 'CFO', Privileged => 1, EmailAddress => 'cfo at company.com');
-$user_b->Create( Name => 'CEO', Privileged => 1, EmailAddress => 'ceo at company.com');
-$user_c->Create( Name => 'minion', Privileged => 1, EmailAddress => 'minion at company.com');
-
 my $q = RT::Queue->new($RT::SystemUser);
 $q->Load('___Approvals');
-
 $q->SetDisabled(0);
 
-my ($val, $msg);
-($val, $msg) = $user_a->PrincipalObj->GrantRight(Object =>$q, Right => $_)
-    for qw(ModifyTicket OwnTicket ShowTicket);
-($val, $msg) = $user_b->PrincipalObj->GrantRight(Object =>$q, Right => $_)
-    for qw(ModifyTicket OwnTicket ShowTicket);
+my %users;
+for my $user_name (qw(minion cfo ceo )) {
+    my $user = $users{$user_name} = RT::User->new($RT::SystemUser);
+    $user->Create( Name => uc($user_name),
+                   Privileged => 1,
+                   EmailAddress => $user_name.'@company.com');
+    my ($val, $msg);
+    ($val, $msg) = $user->PrincipalObj->GrantRight(Object =>$q, Right => $_)
+        for qw(ModifyTicket OwnTicket ShowTicket);
+}
 
 # XXX: we need to make the first approval ticket open so notification is sent.
 my $approvals = 
@@ -94,7 +91,11 @@
         $t->Create(Subject => "PO for stationary",
                    Owner => "root", Requestor => 'minion',
                    Queue => $q->Id);
-} { from => qr/PO via RT/,
+} { from => qr/RT System/,
+    to => 'cfo at company.com',
+    subject => qr/New Pending Approval: CFO Approval/,
+    body => qr/pending your approval.*Your approval is requested.*Blah/s
+},{ from => qr/PO via RT/,
     to => 'minion at company.com',
     subject => qr/PO for stationary/,
     body => qr/automatically generated in response/
@@ -104,19 +105,6 @@
 
 is ($t->ReferredToBy->Count,2, "referred to by the two tickets");
 
-# open the approval tickets that are ready for approval
-mail_ok {
-    for my $ticket ($t->AllDependsOn) {
-        next if $ticket->Type ne 'approval' && $ticket->Status ne 'new';
-        next if $ticket->HasUnresolvedDependencies( Type => 'approval' );
-        $ticket->SetStatus('open');
-    }
-} { from => qr/RT System/,
-    to => 'cfo at company.com',
-    subject => qr/New Pending Approval: CFO Approval/,
-    body => qr/pending your approval/
-};
-
 my $deps = $t->DependsOn;
 is ($deps->Count, 1, "The ticket we created depends on one other ticket");
 my $dependson_ceo= $deps->First->TargetObj;
@@ -135,20 +123,28 @@
 
 mail_ok {
     my $cfo = RT::CurrentUser->new;
-    $cfo->Load( $user_a );
+    $cfo->Load( $users{cfo} );
 
     $dependson_cfo->CurrentUser($cfo);
+    my $notes = MIME::Entity->build(
+        Data => [ 'Resources exist to be consumed.' ]
+    );
+    RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+    my ( $notesval, $notesmsg ) = $dependson_cfo->Correspond( MIMEObj => $notes );
+    ok($notesval, $notesmsg);
+
     my ($ok, $msg) = $dependson_cfo->SetStatus( Status => 'resolved' );
     ok($ok, "cfo can approve - $msg");
 
 } { from => qr/RT System/,
     to => 'ceo at company.com',
     subject => qr/New Pending Approval: PO approval request for PO/,
-    body => qr/pending your approval/
-},{ from => qr/CFO via RT/,
+    body => qr/pending your approval.*CFO approved.*ok with that\?/s
+},{ from => qr/RT System/,
     to => 'minion at company.com',
     subject => qr/Ticket Approved:/,
-    body => qr/approved by CFO/
+    body => qr/approved by CFO.*notes: Resources exist to be consumed/s
 };
 
 is ($t->DependsOn->Count, 1, "still depends only on the CEO approval");
@@ -156,3 +152,68 @@
 
 is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ],
           [ 'new', 'resolved', 'open'], 'ticket state after cfo approval');
+
+mail_ok {
+    my $ceo = RT::CurrentUser->new;
+    $ceo->Load( $users{ceo} );
+
+    $dependson_ceo->CurrentUser($ceo);
+    my $notes = MIME::Entity->build(
+        Data => [ 'And consumed they will be.' ]
+    );
+    RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+    my ( $notesval, $notesmsg ) = $dependson_ceo->Correspond( MIMEObj => $notes );
+    ok($notesval, $notesmsg);
+
+    my ($ok, $msg) = $dependson_ceo->SetStatus( Status => 'resolved' );
+    ok($ok, "ceo can approve - $msg");
+
+} { from => qr/RT System/,
+    to => 'minion at company.com',
+    subject => qr/Ticket Approved:/,
+    body => qr/approved by CEO.*Its Owner may now start to act on it.*notes: And consumed they will be/s,
+}, { from => qr'CEO via RT',
+     to => 'root at localhost',
+     subject => qr/Ticket Approved/,
+     body => qr/The ticket has been approved, you may now start to act on it/,
+};
+
+
+is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ],
+          [ 'new', 'resolved', 'resolved'], 'ticket state after ceo approval');
+
+$dependson_cfo->_Set(
+    Field => 'Status',
+    Value => 'open');
+
+$dependson_ceo->_Set(
+    Field => 'Status',
+    Value => 'new');
+
+mail_ok {
+    my $cfo = RT::CurrentUser->new;
+    $cfo->Load( $users{cfo} );
+
+    $dependson_cfo->CurrentUser($cfo);
+    my $notes = MIME::Entity->build(
+        Data => [ 'sorry, out of resources.' ]
+    );
+    RT::I18N::SetMIMEEntityToUTF8($notes); # convert text parts into utf-8
+
+#    my ( $notesval, $notesmsg ) = $dependson_cfo->Correspond( MIMEObj => $notes );
+#    ok($notesval, $notesmsg);
+
+    my ($ok, $msg) = $dependson_cfo->SetStatus( Status => 'rejected' );
+    ok($ok, "cfo can approve - $msg");
+
+} { from => qr/RT System/,
+    to => 'minion at company.com',
+    subject => qr/Ticket Rejected: PO for stationary/,
+    body => qr/rejected by CFO/
+};
+
+$t->Load($t->id);$dependson_ceo->Load($dependson_ceo->id);
+is_deeply([ $t->Status, $dependson_cfo->Status, $dependson_ceo->Status ],
+          [ 'rejected', 'rejected', 'deleted'], 'ticket state after cfo rejection');
+


More information about the Rt-commit mailing list