X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fcust_event.pm;h=53637c5628fe34a3fb9ac1c0377324506e3f6141;hp=5ca816752a70ceef2e2a7e9e0f3241ca82632748;hb=dc83512c36dc6bea2585abada4f88d714c600e55;hpb=477015901c379f8cd6ba46cfaa4a9ae284ae582d diff --git a/FS/FS/cust_event.pm b/FS/FS/cust_event.pm index 5ca816752..53637c562 100644 --- a/FS/FS/cust_event.pm +++ b/FS/FS/cust_event.pm @@ -1,19 +1,19 @@ package FS::cust_event; +use base qw( FS::cust_main_Mixin FS::Record ); use strict; -use vars qw( @ISA $DEBUG ); +use vars qw( $DEBUG $me ); 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; - -@ISA = qw(FS::cust_main_Mixin FS::Record); +use FS::cust_pay; +use FS::svc_acct; $DEBUG = 0; +$me = '[FS::cust_event]'; =head1 NAME @@ -54,6 +54,13 @@ L and L for conversion functions. =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 @@ -74,7 +81,7 @@ points to. You can ask the object for a copy with the I method. 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; @@ -139,8 +146,9 @@ sub check { $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_flag('no_action') ; return $error if $error; @@ -151,13 +159,6 @@ sub check { Returns the event definition (see L) 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 @@ -191,6 +192,11 @@ sub test_conditions { 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; @@ -213,6 +219,8 @@ Runs the event action. sub do_event { my $self = shift; + my %opt = @_; # currently only 'time' + my $time = $opt{'time'} || time; my $part_event = $self->part_event; @@ -223,13 +231,10 @@ sub do_event { " (". $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 - $error = eval { $part_event->do_action($object); }; + $error = eval { $part_event->do_action($object, $self); }; } my $status = ''; @@ -240,13 +245,19 @@ sub do_event { $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 - $self->_date(time); + $self->_date($time); $self->status($status); $self->statustext($statustext); @@ -295,6 +306,181 @@ sub retriable { $self->replace($old); } +=item join_sql + +=cut + +sub join_sql { + #my $class = shift; + + " + JOIN part_event USING ( eventpart ) + + LEFT JOIN cust_bill ON ( eventtable = 'cust_bill' AND tablenum = cust_bill.invnum ) + LEFT JOIN cust_pkg ON ( eventtable = 'cust_pkg' AND tablenum = cust_pkg.pkgnum ) + LEFT JOIN cust_pay ON ( eventtable = 'cust_pay' AND tablenum = cust_pay.paynum ) + LEFT JOIN cust_pay_batch ON ( eventtable = 'cust_pay_batch' AND tablenum = cust_pay_batch.paybatchnum ) + LEFT JOIN cust_statement ON ( eventtable = 'cust_statement' AND tablenum = cust_statement.statementnum ) + + LEFT JOIN cust_svc ON ( eventtable = 'svc_acct' AND tablenum = cust_svc.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_where HASHREF + +Class method which returns an SQL WHERE fragment to search for parameters +specified in HASHREF. Valid parameters are + +=over 4 + +=item agentnum + +=item custnum + +=item invnum + +=item pkgnum + +=item svcnum + +=item failed + +=item beginning + +=item ending + +=back + +=cut + +#Note: validates all passed-in data; i.e. safe to use with unchecked CGI params. +#sub + +sub search_sql_where { + my($class, $param) = @_; + if ( $DEBUG ) { + warn "$me search_sql_where called with params: \n". + join("\n", map { " $_: ". $param->{$_} } keys %$param ). "\n"; + } + + my @search = $class->cust_search_sql($param); + + #eventpart + my @eventpart = ref($param->{'eventpart'}) + ? @{ $param->{'eventpart'} } + : split(',', $param->{'eventpart'}); + @eventpart = grep /^(\d+)$/, @eventpart; + if ( @eventpart ) { + push @search, 'eventpart IN ('. join(',', @eventpart). ')'; + } + + if ( $param->{'beginning'} =~ /^(\d+)$/ ) { + push @search, "cust_event._date >= $1"; + } + if ( $param->{'ending'} =~ /^(\d+)$/ ) { + push @search, "cust_event._date <= $1"; + } + + #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->{'invnum'} =~ /^(\d+)$/ ) { + push @search, "part_event.eventtable = 'cust_bill'", + "tablenum = '$1'"; + } + + if ( $param->{'pkgnum'} =~ /^(\d+)$/ ) { + push @search, "part_event.eventtable = 'cust_pkg'", + "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 ); + +} + =back =head1 SUBROUTINES @@ -325,53 +511,57 @@ sub process_refax { process_re_X('fax', @_); } -use Storable qw(thaw); use Data::Dumper; -use MIME::Base64; 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( $method, - $param->{'beginning'}, - $param->{'ending'}, - $param->{'failed'}, + $param, $job, ); } -#this needs some updating based on the 1.7 cust_bill_event.pm still, i think sub re_X { - my($method, $beginning, $ending, $failed, $job) = @_; + my($method, $param, $job) = @_; + + my $search_sql = FS::cust_event->search_sql_where($param); - my $from = 'LEFT JOIN part_event USING ( eventpart )'; + #maybe not...? we do want the "re-" action to match the search more closely + # # yuck! hardcoded *AND* sequential scans! + #my $where = " WHERE action LIKE 'cust_bill_send%' ". + # ( $search_sql ? " AND $search_sql" : "" ); - # yuck! hardcoed *AND* sequential scans! - my $where = " WHERE action LIKE 'cust_bill_send%'". - " AND cust_event._date >= $beginning". - " AND cust_event._date <= $ending"; - $where .= " AND statustext != '' AND statustext IS NOT NULL" - if $failed; + my $where = ( $search_sql ? " WHERE $search_sql" : "" ); my @cust_event = qsearch({ 'table' => 'cust_event', - 'addl_from' => $from, + 'addl_from' => FS::cust_event->join_sql(), 'hashref' => {}, 'extra_sql' => $where, }); + warn "$me re_X found ". scalar(@cust_event). " events\n" + if $DEBUG; + my( $num, $last, $min_sec ) = (0, time, 5); #progresbar foo foreach my $cust_event ( @cust_event ) { - # XXX - $cust_event->cust_bill->$method( - $cust_event->part_event->templatename - || $cust_event->cust_main->agent_template - ); + my $cust_X = $cust_event->cust_X; # cust_bill + next unless $cust_X->can($method); + + 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++;