From: mark Date: Fri, 24 Dec 2010 00:40:02 +0000 (+0000) Subject: manual batch approval and TD EFT fixes, RT#10545 X-Git-Tag: TORRUS_1_0_9~8 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=b1dacaef3f9773c72b4c6f525d2f1e584e1432fc manual batch approval and TD EFT fixes, RT#10545 --- diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm index 5fb5a272f..132ee47d7 100644 --- a/FS/FS/Conf.pm +++ b/FS/FS/Conf.pm @@ -2995,13 +2995,13 @@ and customer address. Include units.', 'type' => 'textarea', }, -# { -# 'key' => 'batch-manual_approval', -# 'section' => 'billing', -# 'description' => 'Allow manual batch closure, which will approve all payments that do not yet have a status. This is very dangerous.', -# 'type' => 'checkbox', -# }, -# + { + 'key' => 'batch-manual_approval', + 'section' => 'billing', + 'description' => 'Allow manual batch closure, which will approve all payments that do not yet have a status. This is not advised, but is needed for payment processors that provide a report of rejected rather than approved payments.', + 'type' => 'checkbox', + }, + { 'key' => 'payment_history-years', 'section' => 'UI', diff --git a/FS/FS/cust_pay_batch.pm b/FS/FS/cust_pay_batch.pm index 9ef1e1cc1..9fa14598a 100644 --- a/FS/FS/cust_pay_batch.pm +++ b/FS/FS/cust_pay_batch.pm @@ -260,6 +260,82 @@ sub retriable { ''; } +=item approve PAYBATCH + +Approve this payment. This will replace the existing record with the +same paybatchnum, set its status to 'Approved', and generate a payment +record (L). This should only be called from the batch +import process. + +=cut + +sub approve { + # to break up the Big Wall of Code that is import_results + my $new = shift; + my $paybatch = shift; + my $paybatchnum = $new->paybatchnum; + my $old = qsearchs('cust_pay_batch', { paybatchnum => $paybatchnum }) + or return "paybatchnum $paybatchnum not found"; + return "paybatchnum $paybatchnum already resolved ('".$old->status."')" + if $old->status; + $new->status('Approved'); + my $error = $new->replace($old); + if ( $error ) { + return "error updating status of paybatchnum $paybatchnum: $error\n"; + } + my $cust_pay = new FS::cust_pay ( { + 'custnum' => $new->custnum, + 'payby' => $new->payby, + 'paybatch' => $paybatch, + 'payinfo' => $new->payinfo || $old->payinfo, + 'paid' => $new->paid, + '_date' => $new->_date, + } ); + $error = $cust_pay->insert; + if ( $error ) { + return "error inserting payment for paybatchnum $paybatchnum: $error\n"; + } + $cust_pay->cust_main->apply_payments; + return; +} + +=item decline + +Decline this payment. This will replace the existing record with the +same paybatchnum, set its status to 'Declined', and run collection events +as appropriate. This should only be called from the batch import process. + + +=cut +sub decline { + my $new = shift; + my $paybatchnum = $new->paybatchnum; + my $old = qsearchs('cust_pay_batch', { paybatchnum => $paybatchnum }) + or return "paybatchnum $paybatchnum not found"; + return "paybatchnum $paybatchnum already resolved ('".$old->status."')" + if $old->status; + $new->status('Declined'); + my $error = $new->replace($old); + if ( $error ) { + return "error updating status of paybatchnum $paybatchnum: $error\n"; + } + my $due_cust_event = $new->cust_main->due_cust_event( + 'eventtable' => 'cust_pay_batch', + 'objects' => [ $new ], + ); + if ( !ref($due_cust_event) ) { + return $due_cust_event; + } + # XXX breaks transaction integrity + foreach my $cust_event (@$due_cust_event) { + next unless $cust_event->test_conditions; + if ( my $error = $cust_event->do_event() ) { + return $error; + } + } + return; +} + =back =head1 BUGS diff --git a/FS/FS/pay_batch.pm b/FS/FS/pay_batch.pm index d142411e5..5cd40cda0 100644 --- a/FS/FS/pay_batch.pm +++ b/FS/FS/pay_batch.pm @@ -356,20 +356,21 @@ sub import_results { &{$hook}(\%hash, $cust_pay_batch->hashref); + my $error = ''; if ( &{$approved_condition}(\%hash) ) { - $new_cust_pay_batch->status('Approved'); + $error = $new_cust_pay_batch->approve($hash{'paybatch'} || $self->batchnum); + $total += $hash{'paid'}; } elsif ( &{$declined_condition}(\%hash) ) { - $new_cust_pay_batch->status('Declined'); + $error = $new_cust_pay_batch->decline; } - my $error = $new_cust_pay_batch->replace($cust_pay_batch); if ( $error ) { $dbh->rollback if $oldAutoCommit; - return "error updating status of paybatchnum $hash{'paybatchnum'}: $error\n"; + return $error; } # purge CVV when the batch is processed @@ -379,64 +380,13 @@ sub import_results { $conf->config('cvv-save') ) { $new_cust_pay_batch->cust_main->remove_cvv; } - } - - if ( $new_cust_pay_batch->status =~ /Approved/i ) { - - my $cust_pay = new FS::cust_pay ( { - 'custnum' => $custnum, - 'payby' => $payby, - 'paybatch' => $hash{'paybatch'} || $self->batchnum, - 'payinfo' => ( $hash{'payinfo'} || $cust_pay_batch->payinfo ), - map { $_ => $hash{$_} } (qw( paid _date )), - } ); - $error = $cust_pay->insert; - if ( $error ) { - $dbh->rollback if $oldAutoCommit; - return "error adding payment paybatchnum $hash{'paybatchnum'}: $error\n"; - } - $total += $hash{'paid'}; - - $cust_pay->cust_main->apply_payments; - - } elsif ( $new_cust_pay_batch->status =~ /Declined/i ) { - - #false laziness w/cust_main::collect - - my $due_cust_event = $new_cust_pay_batch->cust_main->due_cust_event( - #'check_freq' => '1d', #? - 'eventtable' => 'cust_pay_batch', - 'objects' => [ $new_cust_pay_batch ], - ); - unless( ref($due_cust_event) ) { - $dbh->rollback if $oldAutoCommit; - return $due_cust_event; - } - - foreach my $cust_event ( @$due_cust_event ) { - - #XXX lock event - - #re-eval event conditions (a previous event could have changed things) - next unless $cust_event->test_conditions; - - if ( my $error = $cust_event->do_event() ) { - # gah, even with transactions. - #$dbh->commit if $oldAutoCommit; #well. - $dbh->rollback if $oldAutoCommit; - return $error; - } - - } } - } + } # foreach (@all_values) if ( defined($close_condition) ) { # Allow the module to decide whether to close the batch. - # This is used for TD EFT, which requires two imports before - # closing. # $close_condition can also die() to abort the whole import. my $close = eval { $close_condition->($self) }; if ( $@ ) { @@ -577,6 +527,52 @@ sub export_batch { return $batch; } +sub manual_approve { + my $self = shift; + my $date = time; + my %opt = @_; + my $paybatch = $opt{'paybatch'} || $self->batchnum; + my $conf = FS::Conf->new; + return 'manual batch approval disabled' + if ( ! $conf->exists('batch-manual_approval') ); + return 'batch already resolved' if $self->status eq 'R'; + return 'batch not yet submitted' if $self->status eq 'O'; + + local $SIG{HUP} = 'IGNORE'; + local $SIG{INT} = 'IGNORE'; + local $SIG{QUIT} = 'IGNORE'; + local $SIG{TERM} = 'IGNORE'; + local $SIG{TSTP} = 'IGNORE'; + local $SIG{PIPE} = 'IGNORE'; + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + my $dbh = dbh; + + my $payments = 0; + foreach my $cust_pay_batch ( + qsearch('cust_pay_batch', { batchnum => $self->batchnum, + status => '' }) + ) { + my $new_cust_pay_batch = new FS::cust_pay_batch { + $cust_pay_batch->hash, + 'paid' => $cust_pay_batch->amount, + '_date' => $date, + }; + my $error = $new_cust_pay_batch->approve($paybatch); + if ( $error ) { + $dbh->rollback; + return 'paybatchnum '.$cust_pay_batch->paybatchnum.": $error"; + } + $payments++; + } + return 'no unresolved payments in batch' if $payments == 0; + $self->set_status('R'); + + $dbh->commit; + return; +} + =back =head1 BUGS diff --git a/FS/FS/pay_batch/td_eft1464.pm b/FS/FS/pay_batch/td_eft1464.pm index c3294d161..7f58ab5f7 100644 --- a/FS/FS/pay_batch/td_eft1464.pm +++ b/FS/FS/pay_batch/td_eft1464.pm @@ -76,13 +76,13 @@ $name = 'td_eft1464'; my @cust_pay_batch = @{(shift)}; my $time = $pay_batch->download || time; my $now = sprintf("%03u%03u", - (localtime(time))[5],#year since 1900 + (localtime(time))[5] % 100,#year since 1900 (localtime(time))[7]+1);#day of year # Request settlement the next day my $duedate = time+86400; $opt{'due'} = sprintf("%03u%03u", - (localtime($duedate))[5], + (localtime($duedate))[5] % 100, (localtime($duedate))[7]+1); $opt{'fcn'} = @@ -94,7 +94,8 @@ $name = 'td_eft1464'; $opt{'fcn'}, $now, $opt{'datacenter'}, - ' ' x 1429 #filler + ' ' x 1429, #filler + "\r" ); }, row => sub { @@ -116,7 +117,7 @@ $name = 'td_eft1464'; sprintf('%09u', $aba), sprintf('%-12s', $account), ' ' x 22, - ' ' x 3, + '0' x 3, $opt{'shortname'}, sprintf('%-30s', join(' ', @@ -127,11 +128,12 @@ $name = 'td_eft1464'; sprintf('%-19s', $cust_pay_batch->paybatchnum), # originator reference num $opt{'retbranch'}, $opt{'retacct'}, + ' ' x 15, ' ' x 22, ' ' x 2, '0' x 11, ); - return $control . $payment . (' ' x 720); + return sprintf('%-1464s',$control . $payment) . "\r"; }, footer => sub { my ($pay_batch, $batchcount, $batchtotal) = @_; @@ -145,6 +147,7 @@ $name = 'td_eft1464'; '0' x 14, # total of credit txns '0' x 8, # total of credit txns ' ' x 1396, + "\r" ) }, ); diff --git a/httemplate/misc/download-batch.cgi b/httemplate/misc/download-batch.cgi index 01bf5d25f..23deba712 100644 --- a/httemplate/misc/download-batch.cgi +++ b/httemplate/misc/download-batch.cgi @@ -1,6 +1,4 @@ -<% $pay_batch->export_batch($format) %> - -<%init> +<% $pay_batch->export_batch($format) %><%init> #http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes http_header('Content-Type' => 'text/plain' ); # not necessarily correct... diff --git a/httemplate/misc/process/pay_batch-approve.cgi b/httemplate/misc/process/pay_batch-approve.cgi new file mode 100644 index 000000000..f857e2318 --- /dev/null +++ b/httemplate/misc/process/pay_batch-approve.cgi @@ -0,0 +1,16 @@ +% if ( $error ) { +% $cgi->param('error', $error); +% } +<% $cgi->redirect(popurl(3)."search/cust_pay_batch.cgi?dcln=1;batchnum=$batchnum") %> +<%init> +die "access denied" + unless $FS::CurrentUser::CurrentUser->access_right('Process batches'); + +my $batchnum = $cgi->param('batchnum'); +# make a record in the paybatch of who did this +my $paybatch = 'manual-'.$FS::CurrentUser::CurrentUser->username. + '-' . time2str('%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time); +my $pay_batch = qsearchs('pay_batch', { 'batchnum' => $batchnum }) + or die "batchnum '$batchnum' not found"; +my $error = $pay_batch->manual_approve('paybatch' => $paybatch); + diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi index df635ee8d..cb101d4db 100755 --- a/httemplate/search/cust_pay_batch.cgi +++ b/httemplate/search/cust_pay_batch.cgi @@ -193,13 +193,19 @@ if ( $pay_batch ) { qq!!. qq!!. qq!!. - qq!!. + qq!!. + qq!!. qq!
!; } $html_init .= qq!!; $html_init .= '
'; + if ( $conf->exists('batch-manual_approval') and $pay_batch->status eq 'I') { + $html_init .= qq!! + } } - $html_init .= '' + $html_init .= ''; } if ($pay_batch) {