Merge branch 'master' of git.freeside.biz:/home/git/freeside
[freeside.git] / FS / FS / cust_event.pm
index bf3a5b7..094c4fa 100644 (file)
@@ -1,19 +1,18 @@
 package FS::cust_event;
 package FS::cust_event;
+use base qw( FS::cust_main_Mixin FS::Record );
 
 use strict;
 
 use strict;
-use vars qw( @ISA $DEBUG $me );
+use vars qw( $DEBUG $me );
 use Carp qw( croak confess );
 use FS::Record qw( qsearch qsearchs dbdef );
 use Carp qw( croak confess );
 use FS::Record qw( qsearch qsearchs dbdef );
-use FS::cust_main_Mixin;
-use FS::part_event;
 #for cust_X
 use FS::cust_main;
 use FS::cust_pkg;
 use FS::cust_bill;
 #for cust_X
 use FS::cust_main;
 use FS::cust_pkg;
 use FS::cust_bill;
+use FS::cust_pay;
+use FS::svc_acct;
 
 
-@ISA = qw(FS::cust_main_Mixin FS::Record);
-
-$DEBUG = 0;
+$DEBUG = 1;
 $me = '[FS::cust_event]';
 
 =head1 NAME
 $me = '[FS::cust_event]';
 
 =head1 NAME
@@ -55,6 +54,13 @@ L<Time::Local> and L<Date::Parse> for conversion functions.
 
 =item statustext - additional status detail (i.e. error or progress message)
 
 
 =item statustext - additional status detail (i.e. error or progress message)
 
+=item no_action - 'Y' if the event action wasn't performed. Some actions
+contain an internal check to see if the action is going to be impossible (for
+example, emailing a notice to a customer who has no email address), and if so,
+won't attempt the action. It shouldn't be reported as a failure because
+there's no need to retry it. However, the action should set no_action = 'Y'
+so that there's a record.
+
 =back
 
 =head1 METHODS
 =back
 
 =head1 METHODS
@@ -75,7 +81,7 @@ points to.  You can ask the object for a copy with the I<hash> method.
 
 sub table { 'cust_event'; }
 
 
 sub table { 'cust_event'; }
 
-sub cust_linked { $_[0]->cust_main_custnum; } 
+sub cust_linked { $_[0]->cust_main_custnum || $_[0]->custnum } 
 sub cust_unlinked_msg {
   my $self = shift;
   "WARNING: can't find cust_main.custnum ". $self->custnum;
 sub cust_unlinked_msg {
   my $self = shift;
   "WARNING: can't find cust_main.custnum ". $self->custnum;
@@ -140,8 +146,9 @@ sub check {
                               $dbdef_eventtable->primary_key
                             )
     || $self->ut_number('_date')
                               $dbdef_eventtable->primary_key
                             )
     || $self->ut_number('_date')
-    || $self->ut_enum('status', [qw( new locked done failed )])
+    || $self->ut_enum('status', [qw( new locked done failed initial)])
     || $self->ut_anything('statustext')
     || $self->ut_anything('statustext')
+    || $self->ut_flag('no_action')
   ;
   return $error if $error;
 
   ;
   return $error if $error;
 
@@ -152,13 +159,6 @@ sub check {
 
 Returns the event definition (see L<FS::part_event>) for this completed event.
 
 
 Returns the event definition (see L<FS::part_event>) for this completed event.
 
-=cut
-
-sub part_event {
-  my $self = shift;
-  qsearchs( 'part_event', { 'eventpart' => $self->eventpart } );
-}
-
 =item cust_X
 
 Returns the customer, package, invoice or batched payment (see
 =item cust_X
 
 Returns the customer, package, invoice or batched payment (see
@@ -192,6 +192,11 @@ sub test_conditions {
   my $object = $self->cust_X;
   my @conditions = $part_event->part_event_condition;
   $opt{'cust_event'} = $self;
   my $object = $self->cust_X;
   my @conditions = $part_event->part_event_condition;
   $opt{'cust_event'} = $self;
+  $opt{'time'} = $self->_date
+      or die "test_conditions called without cust_event._date\n";
+    # this MUST be set, or all hell breaks loose in event conditions.
+    # it MUST be in the same time as in the cust_event object, or
+    # future time-dependent events will trigger incorrectly.
 
   #no unsatisfied conditions
   #! grep ! $_->condition( $object, %opt ), @conditions;
 
   #no unsatisfied conditions
   #! grep ! $_->condition( $object, %opt ), @conditions;
@@ -214,6 +219,8 @@ Runs the event action.
 
 sub do_event {
   my $self = shift;
 
 sub do_event {
   my $self = shift;
+  my %opt = @_; # currently only 'time'
+  my $time = $opt{'time'} || time;
 
   my $part_event = $self->part_event;
 
 
   my $part_event = $self->part_event;
 
@@ -224,13 +231,10 @@ sub do_event {
        " (". $part_event->action. ") $for\n"
     if $DEBUG;
 
        " (". $part_event->action. ") $for\n"
     if $DEBUG;
 
-  my $oldAutoCommit = $FS::UID::AutoCommit;
-  local $FS::UID::AutoCommit = 0;
-
   my $error;
   {
     local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
   my $error;
   {
     local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
-    $error = eval { $part_event->do_action($object); };
+    $error = eval { $part_event->do_action($object, $self); };
   }
 
   my $status = '';
   }
 
   my $status = '';
@@ -241,13 +245,19 @@ sub do_event {
     $statustext = "Error running ". $part_event->action. " action: $@";
   } elsif ( $error ) {
     $status = 'done';
     $statustext = "Error running ". $part_event->action. " action: $@";
   } elsif ( $error ) {
     $status = 'done';
-    $statustext = $error;
+    if ( $error eq 'N/A' ) {
+      # archaic way to indicate no-op completion of spool_csv (and maybe
+      # other events)?
+      $self->no_action('Y');
+    } else {
+      $statustext = $error;
+    }
   } else {
     $status = 'done';
   }
 
   #replace or add myself
   } else {
     $status = 'done';
   }
 
   #replace or add myself
-  $self->_date(time);
+  $self->_date($time);
   $self->status($status);
   $self->statustext($statustext);
 
   $self->status($status);
   $self->statustext($statustext);
 
@@ -296,7 +306,7 @@ sub retriable {
   $self->replace($old);
 }
 
   $self->replace($old);
 }
 
-=item join_cust_sql
+=item join_sql
 
 =cut
 
 
 =cut
 
@@ -307,15 +317,21 @@ sub join_sql {
        JOIN part_event USING ( eventpart )
   LEFT JOIN cust_bill ON ( eventtable = 'cust_bill' AND tablenum = invnum  )
   LEFT JOIN cust_pkg  ON ( eventtable = 'cust_pkg'  AND tablenum = pkgnum  )
        JOIN part_event USING ( eventpart )
   LEFT JOIN cust_bill ON ( eventtable = 'cust_bill' AND tablenum = invnum  )
   LEFT JOIN cust_pkg  ON ( eventtable = 'cust_pkg'  AND tablenum = pkgnum  )
-  LEFT JOIN cust_main ON (    ( eventtable = 'cust_main' AND tablenum = cust_main.custnum )
-                           OR ( eventtable = 'cust_bill' AND cust_bill.custnum = cust_main.custnum )
-                           OR ( eventtable = 'cust_pkg'  AND cust_pkg.custnum  = cust_main.custnum )
-                         )
+  LEFT JOIN cust_pay  ON ( eventtable = 'cust_pay'  AND tablenum = paynum  )
+  LEFT JOIN cust_svc  ON ( eventtable = 'svc_acct'  AND tablenum = svcnum  )
+  LEFT JOIN cust_pkg AS cust_pkg_for_svc ON ( cust_svc.pkgnum = cust_pkg_for_svc.pkgnum )
+  LEFT JOIN cust_main ON (
+       ( eventtable = 'cust_main' AND tablenum = cust_main.custnum )
+    OR ( eventtable = 'cust_bill' AND cust_bill.custnum = cust_main.custnum )
+    OR ( eventtable = 'cust_pkg'  AND cust_pkg.custnum  = cust_main.custnum )
+    OR ( eventtable = 'cust_pay'  AND cust_pay.custnum  = cust_main.custnum )
+    OR ( eventtable = 'svc_acct'  AND cust_pkg_for_svc.custnum  = cust_main.custnum )
+  )
   ";
 
 }
 
   ";
 
 }
 
-=item search_sql HASHREF
+=item search_sql_where HASHREF
 
 Class method which returns an SQL WHERE fragment to search for parameters
 specified in HASHREF.  Valid parameters are
 
 Class method which returns an SQL WHERE fragment to search for parameters
 specified in HASHREF.  Valid parameters are
@@ -330,16 +346,14 @@ specified in HASHREF.  Valid parameters are
 
 =item pkgnum
 
 
 =item pkgnum
 
+=item svcnum
+
 =item failed
 
 =item beginning
 
 =item ending
 
 =item failed
 
 =item beginning
 
 =item ending
 
-=item payby
-
-=item 
-
 =back
 
 =cut
 =back
 
 =cut
@@ -347,10 +361,10 @@ specified in HASHREF.  Valid parameters are
 #Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
 #sub 
 
 #Note: validates all passed-in data; i.e. safe to use with unchecked CGI params.
 #sub 
 
-sub search_sql {
+sub search_sql_where {
   my($class, $param) = @_;
   if ( $DEBUG ) {
   my($class, $param) = @_;
   if ( $DEBUG ) {
-    warn "$me search_sql called with params: \n".
+    warn "$me search_sql_where called with params: \n".
          join("\n", map { "  $_: ". $param->{$_} } keys %$param ). "\n";
   }
 
          join("\n", map { "  $_: ". $param->{$_} } keys %$param ). "\n";
   }
 
@@ -372,11 +386,65 @@ sub search_sql {
     push @search, "cust_event._date <= $1";
   }
 
     push @search, "cust_event._date <= $1";
   }
 
-  if ( $param->{'failed'} ) {
-    push @search, "statustext != ''",
-                  "statustext IS NOT NULL",
-                  "statustext != 'N/A'";
-  }
+  #if ( $param->{'failed'} ) {
+  #  push @search, "statustext != ''",
+  #                "statustext IS NOT NULL",
+  #                "statustext != 'N/A'";
+  #}
+  # huh?
+
+  my @event_status = ref($param->{'event_status'})
+                    ? @{ $param->{'event_status'} }
+                    : split(',', $param->{'event_status'});
+  if ( @event_status ) {
+    my @status;
+
+    my ($done_Y, $done_N, $done_S);
+    # done_Y: action was taken
+    # done_N: action was not taken
+    # done_S: status message returned
+    foreach (@event_status) {
+      if ($_ eq 'done_Y') {
+        $done_Y = 1;
+      } elsif ( $_ eq 'done_N' ) {
+        $done_N = 1;
+      } elsif ( $_ eq 'done_S' ) {
+        $done_S = 1;
+      } else {
+        push @status, $_;
+      }
+    }
+    if ( $done_Y or $done_N or $done_S ) {
+      push @status, 'done';
+    }
+    if ( @status ) {
+      push @search, "cust_event.status IN(" .
+                    join(',', map "'$_'", @status) .
+                    ')';
+    }
+
+    # done_S status should include only those where statustext is not null,
+    # and done_Y should include only those where it is.
+    if ( $done_Y and $done_N and $done_S ) {
+      # then not necessary
+    } else {
+      my @done_status;
+      if ( $done_Y ) {
+        push @done_status, "(cust_event.no_action IS NULL AND cust_event.statustext IS NULL)";
+      }
+      if ( $done_N ) {
+        push @done_status, "(cust_event.no_action = 'Y')";
+      }
+      if ( $done_S ) {
+        push @done_status, "(cust_event.no_action IS NULL AND cust_event.statustext IS NOT NULL)";
+      }
+      push @search, join(' OR ', @done_status) if @done_status;
+    }
+
+  } # event_status
+
+  # always hide initialization
+  push @search, 'cust_event.status != \'initial\'';
 
   if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
     push @search, "cust_main.custnum = '$1'";
 
   if ( $param->{'custnum'} =~ /^(\d+)$/ ) {
     push @search, "cust_main.custnum = '$1'";
@@ -392,6 +460,16 @@ sub search_sql {
                   "tablenum = '$1'";
   }
 
                   "tablenum = '$1'";
   }
 
+  if ( $param->{'paynum'} =~ /^(\d+)$/ ) {
+    push @search, "part_event.eventtable = 'cust_pay'",
+                  "tablenum = '$1'";
+  }
+
+  if ( $param->{'svcnum'} =~ /^(\d+)$/ ) {
+    push @search, "part_event.eventtable = 'svc_acct'",
+                  "tablenum = '$1'";
+  }
+
   my $where = 'WHERE '. join(' AND ', @search );
 
   join(' AND ', @search );
   my $where = 'WHERE '. join(' AND ', @search );
 
   join(' AND ', @search );
@@ -428,13 +506,9 @@ sub process_refax {
   process_re_X('fax', @_);
 }
 
   process_re_X('fax', @_);
 }
 
-use Storable qw(thaw);
 use Data::Dumper;
 use Data::Dumper;
-use MIME::Base64;
 sub process_re_X {
 sub process_re_X {
-  my( $method, $job ) = ( shift, shift );
-
-  my $param = thaw(decode_base64(shift));
+  my( $method, $job, $param ) = @_;
   warn Dumper($param) if $DEBUG;
 
   re_X(
   warn Dumper($param) if $DEBUG;
 
   re_X(
@@ -448,7 +522,7 @@ sub process_re_X {
 sub re_X {
   my($method, $param, $job) = @_;
 
 sub re_X {
   my($method, $param, $job) = @_;
 
-  my $search_sql = FS::cust_event->search_sql($param);
+  my $search_sql = FS::cust_event->search_sql_where($param);
 
   #maybe not...?  we do want the "re-" action to match the search more closely
   #            # yuck!  hardcoded *AND* sequential scans!
 
   #maybe not...?  we do want the "re-" action to match the search more closely
   #            # yuck!  hardcoded *AND* sequential scans!
@@ -473,9 +547,16 @@ sub re_X {
     my $cust_X = $cust_event->cust_X; # cust_bill
     next unless $cust_X->can($method);
 
     my $cust_X = $cust_event->cust_X; # cust_bill
     next unless $cust_X->can($method);
 
-    $cust_X->$method( $cust_event->part_event->templatename
-                      || $cust_X->agent_template
-                    );
+    my $part_event = $cust_event->part_event;
+    my $template = $part_event->templatename
+                   || $cust_X->agent_template;
+    my $modenum = $part_event->option('modenum') || '';
+    my $invoice_from = $part_event->option('agent_invoice_from') || '';
+    $cust_X->set('mode' => $modenum);
+    $cust_X->$method( { template => $template,
+                        modenum  => $modenum,
+                        from     => $invoice_from,
+                    } );
 
     if ( $job ) { #progressbar foo
       $num++;
 
     if ( $job ) { #progressbar foo
       $num++;