batch refactor continued
authorjeff <jeff>
Sat, 26 Aug 2006 23:15:14 +0000 (23:15 +0000)
committerjeff <jeff>
Sat, 26 Aug 2006 23:15:14 +0000 (23:15 +0000)
27 files changed:
FS/FS/ClientAPI/MyAccount.pm
FS/FS/ClientAPI/Signup.pm
FS/FS/Schema.pm
FS/FS/Setup.pm
FS/FS/cust_bill.pm
FS/FS/cust_bill_event.pm
FS/FS/cust_bill_pay_batch.pm [new file with mode: 0644]
FS/FS/cust_main.pm
FS/FS/cust_pay_batch.pm
FS/FS/part_bill_event.pm
FS/FS/payby.pm
FS/MANIFEST
FS/t/cust_bill_pay_batch.t [new file with mode: 0644]
httemplate/browse/cust_pay_batch.cgi [deleted file]
httemplate/edit/cust_bill_pay.cgi
httemplate/edit/cust_credit.cgi
httemplate/edit/cust_credit_bill.cgi
httemplate/edit/part_bill_event.cgi
httemplate/edit/process/cust_main.cgi
httemplate/edit/process/cust_refund.cgi
httemplate/misc/bill.cgi
httemplate/misc/download-batch.cgi
httemplate/misc/process/payment.cgi
httemplate/search/cust_pay_batch.cgi [new file with mode: 0755]
httemplate/search/pay_batch.cgi [new file with mode: 0755]
httemplate/search/pay_batch.html [new file with mode: 0644]
httemplate/search/report_cust_pay_batch.html [new file with mode: 0644]

index 54b8a99..fe9e8a1 100644 (file)
@@ -658,7 +658,7 @@ sub order_pkg {
     my $bill_error = $cust_main->bill;
     $cust_main->apply_payments;
     $cust_main->apply_credits;
     my $bill_error = $cust_main->bill;
     $cust_main->apply_payments;
     $cust_main->apply_credits;
-    $bill_error = $cust_main->collect;
+    $bill_error = $cust_main->collect('realtime' => 1);
 
     if (    $cust_main->balance > $old_balance
          && $cust_main->balance > 0
 
     if (    $cust_main->balance > $old_balance
          && $cust_main->balance > 0
index ed71651..765ce40 100644 (file)
@@ -302,7 +302,7 @@ sub new_customer {
     $cust_main->apply_payments;
     $cust_main->apply_credits;
 
     $cust_main->apply_payments;
     $cust_main->apply_credits;
 
-    $bill_error = $cust_main->collect;
+    $bill_error = $cust_main->collect('realtime' => 1);
     #warn "[fs_signup_server] error collecting from new customer: $bill_error"
     #  if $bill_error;
 
     #warn "[fs_signup_server] error collecting from new customer: $bill_error"
     #  if $bill_error;
 
index 30de0c9..9f9770c 100644 (file)
@@ -545,6 +545,19 @@ sub tables_hashref {
       'index' => [ [ 'paynum' ], [ 'invnum' ] ],
     },
 
       'index' => [ [ 'paynum' ], [ 'invnum' ] ],
     },
 
+    'cust_bill_pay_batch' => {
+      'columns' => [
+        'billpaynum', 'serial',     '',   '', '', '', 
+        'invnum',  'int',     '',   '', '', '', 
+        'paybatchnum',  'int',     '',   '', '', '', 
+        'amount',  @money_type, '', '', 
+        '_date',   @date_type, '', '', 
+      ],
+      'primary_key' => 'billpaynum',
+      'unique' => [],
+      'index' => [ [ 'paybatchnum' ], [ 'invnum' ] ],
+    },
+
     'cust_bill_pay_pkg' => {
       'columns' => [
         'billpaypkgnum', 'serial', '', '', '', '',
     'cust_bill_pay_pkg' => {
       'columns' => [
         'billpaypkgnum', 'serial', '', '', '', '',
index 90f6f10..fff8256 100644 (file)
@@ -143,7 +143,7 @@ sub initial_data {
       { 'payby'     => 'CARD',
         'event'     => 'Batch card',
         'seconds'   => 0,
       { 'payby'     => 'CARD',
         'event'     => 'Batch card',
         'seconds'   => 0,
-        'eventcode' => '$cust_bill->batch_card();',
+        'eventcode' => '$cust_bill->batch_card(%options);',
         'weight'    => 40,
         'plan'      => 'batch-card',
       },
         'weight'    => 40,
         'plan'      => 'batch-card',
       },
@@ -175,6 +175,13 @@ sub initial_data {
         'weight'    => 40,
         'plan'      => 'suspend',
       },
         'weight'    => 40,
         'plan'      => 'suspend',
       },
+      { 'payby'     => 'DCLN',
+        'event'     => 'Retriable',
+        'seconds'   => 0,
+        'eventcode' => '$cust_bill_event->retriable();',
+        'weight'    => 60,
+        'plan'      => 'retriable',
+      },
     ],
     
     #you must create a service definition. An example of a service definition
     ],
     
     #you must create a service definition. An example of a service definition
index a93d175..62d67c1 100644 (file)
@@ -1,7 +1,7 @@
 package FS::cust_bill;
 
 use strict;
 package FS::cust_bill;
 
 use strict;
-use vars qw( @ISA $DEBUG $conf $money_char );
+use vars qw( @ISA $DEBUG $me $conf $money_char );
 use vars qw( $invoice_lines @buf ); #yuck
 use Fcntl qw(:flock); #for spool_csv
 use IPC::Run3;
 use vars qw( $invoice_lines @buf ); #yuck
 use Fcntl qw(:flock); #for spool_csv
 use IPC::Run3;
@@ -26,11 +26,14 @@ use FS::cust_pay_batch;
 use FS::cust_bill_event;
 use FS::part_pkg;
 use FS::cust_bill_pay;
 use FS::cust_bill_event;
 use FS::part_pkg;
 use FS::cust_bill_pay;
+use FS::cust_bill_pay_batch;
 use FS::part_bill_event;
 use FS::part_bill_event;
+use FS::payby qw( payby2bop );
 
 @ISA = qw( FS::cust_main_Mixin FS::Record );
 
 $DEBUG = 0;
 
 @ISA = qw( FS::cust_main_Mixin FS::Record );
 
 $DEBUG = 0;
+$me = '[FS::cust_bill]';
 
 #ask FS::UID to run this stuff for us later
 FS::UID->install_callback( sub { 
 
 #ask FS::UID to run this stuff for us later
 FS::UID->install_callback( sub { 
@@ -1308,12 +1311,18 @@ L<FS::cust_pay_batch>).
 =cut
 
 sub batch_card {
 =cut
 
 sub batch_card {
-  my $self = shift;
+  my ($self, %options) = @_;
   my $cust_main = $self->cust_main;
 
   my $amount = sprintf("%.2f", $cust_main->balance - $cust_main->in_transit_payments);
   return '' unless $amount > 0;
   
   my $cust_main = $self->cust_main;
 
   my $amount = sprintf("%.2f", $cust_main->balance - $cust_main->in_transit_payments);
   return '' unless $amount > 0;
   
+  if ($options{'realtime'}) {
+    return $cust_main->realtime_bop ( $FS::payby::payby2bop->{$cust_main->payby}, $amount,
+      %options,
+    );
+  }
+
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
   my $oldAutoCommit = $FS::UID::AutoCommit;
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
@@ -1369,6 +1378,30 @@ sub batch_card {
     die $error;
   }
 
     die $error;
   }
 
+  my $unapplied = $cust_main->total_credited + $cust_main->total_unapplied_payments + $cust_main->in_transit_payments;
+  foreach my $cust_bill ($cust_main->open_cust_bill) {
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+    my $cust_bill_pay_batch = new FS::cust_bill_pay_batch {
+      'invnum' => $cust_bill->invnum,
+      'paybatchnum' => $cust_pay_batch->paybatchnum,
+      'amount' => $cust_bill->owed,
+      '_date' => time,
+    };
+    if ($unapplied >= $cust_bill_pay_batch->amount){
+      $unapplied -= $cust_bill_pay_batch->amount;
+      next;
+    }else{
+      $cust_bill_pay_batch->amount(sprintf ( "%.2f", 
+                                   $cust_bill_pay_batch->amount - $unapplied ));
+      $unapplied = 0;
+    }
+    $error = $cust_bill_pay_batch->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      die $error;
+    }
+  }
+
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
   '';
 }
index 128e5a5..4496bed 100644 (file)
@@ -126,12 +126,13 @@ sub check {
     || $self->ut_textn('statustext')
   ;
 
     || $self->ut_textn('statustext')
   ;
 
+  return "Unknown eventpart ". $self->eventpart
+    unless my $part_bill_event =
+      qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
+
   return "Unknown invnum ". $self->invnum
     unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
 
   return "Unknown invnum ". $self->invnum
     unless qsearchs( 'cust_bill' ,{ 'invnum' => $self->invnum } );
 
-  return "Unknown eventpart ". $self->eventpart
-    unless qsearchs( 'part_bill_event' ,{ 'eventpart' => $self->eventpart } );
-
   $self->SUPER::check;
 }
 
   $self->SUPER::check;
 }
 
@@ -173,6 +174,21 @@ sub retry {
   $self->replace($old);
 }
 
   $self->replace($old);
 }
 
+=item retryable
+
+Changes the statustext of this event to B<retriable>, rendering it 
+retriable (should retry be called).
+
+=cut
+
+sub retriable {
+  my $self = shift;
+  return '' unless $self->status eq 'done';
+  my $old = ref($self)->new( { $self->hash } );
+  $self->statustext('retriable');
+  $self->replace($old);
+}
+
 =back
 
 =head1 SUBROUTINES
 =back
 
 =head1 SUBROUTINES
diff --git a/FS/FS/cust_bill_pay_batch.pm b/FS/FS/cust_bill_pay_batch.pm
new file mode 100644 (file)
index 0000000..30fb744
--- /dev/null
@@ -0,0 +1,120 @@
+package FS::cust_bill_pay_batch;
+
+use strict;
+use vars qw( @ISA );
+use FS::Record qw( qsearch qsearchs );
+
+@ISA = qw(FS::Record);
+
+=head1 NAME
+
+FS::cust_bill_pay_batch - Object methods for cust_bill_pay_batch records
+
+=head1 SYNOPSIS
+
+  use FS::cust_bill_pay_batch;
+
+  $record = new FS::cust_bill_pay_batch \%hash;
+  $record = new FS::cust_bill_pay_batch { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::cust_bill_pay_batch object represents a relationship between a
+customer's bill and a batch.  FS::cust_bill_pay_batch inherits from
+FS::Record.  The following fields are currently supported:
+
+=over 4
+
+=item billpaynum - primary key
+
+=item invnum - customer's bill (invoice)
+
+=item paybatchnum - entry in cust_pay_batch table
+
+=item amount - 
+
+=item _date - 
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new record.  To add the record to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'cust_bill_pay_batch'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=cut
+
+=item delete
+
+Delete this record from the database.
+
+=cut
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=cut
+
+=item check
+
+Checks all fields to make sure this is a valid example.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_numbern('billpaynum')
+    || $self->ut_number('invnum')
+    || $self->ut_number('paybatchnum')
+    || $self->ut_money('amount')
+    || $self->ut_numbern('_date')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+Just hangs there.
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
index e86f399..875db93 100644 (file)
@@ -42,7 +42,7 @@ use FS::cust_bill_pay;
 use FS::prepay_credit;
 use FS::queue;
 use FS::part_pkg;
 use FS::prepay_credit;
 use FS::queue;
 use FS::part_pkg;
-use FS::part_bill_event;
+use FS::part_bill_event qw(due_events);
 use FS::cust_bill_event;
 use FS::cust_tax_exempt;
 use FS::cust_tax_exempt_pkg;
 use FS::cust_bill_event;
 use FS::cust_tax_exempt;
 use FS::cust_tax_exempt_pkg;
@@ -2251,79 +2251,28 @@ sub collect {
     warn "  invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")\n"
       if $DEBUG > 1;
 
     warn "  invnum ". $cust_bill->invnum. " (owed ". $cust_bill->owed. ")\n"
       if $DEBUG > 1;
 
-    foreach my $part_bill_event (
-      sort {    $a->seconds   <=> $b->seconds
-             || $a->weight    <=> $b->weight
-             || $a->eventpart <=> $b->eventpart }
-        grep { $_->seconds <= ( $invoice_time - $cust_bill->_date )
-               && ! qsearch( 'cust_bill_event', {
-                                'invnum'    => $cust_bill->invnum,
-                                'eventpart' => $_->eventpart,
-                                'status'    => 'done',
-                                                                   } )
-             }
-          qsearch( {
-            'table'     => 'part_bill_event',
-            'hashref'   => { 'payby'    => (exists($options{'payby'})
-                                            ? $options{'payby'}
-                                            : $self->payby
-                                          ),
-                             'disabled' => '',           },
-            'extra_sql' => $extra_sql,
-          } )
-    ) {
+    foreach my $part_bill_event ( due_events ( $cust_bill,
+                                               exists($options{'payby'}) 
+                                                ? $options{'payby'}
+                                                : $self->payby,
+                                              $invoice_time,
+                                              $extra_sql ) ) {
 
       last if $cust_bill->owed <= 0  # don't run subsequent events if owed<=0
            || $self->balance   <= 0; # or if balance<=0
 
 
       last if $cust_bill->owed <= 0  # don't run subsequent events if owed<=0
            || $self->balance   <= 0; # or if balance<=0
 
-      warn "  calling invoice event (". $part_bill_event->eventcode. ")\n"
-        if $DEBUG > 1;
-      my $cust_main = $self; #for callback
-
-      my $error;
       {
         local $realtime_bop_decline_quiet = 1 if $options{'quiet'};
       {
         local $realtime_bop_decline_quiet = 1 if $options{'quiet'};
-        local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
-        $error = eval $part_bill_event->eventcode;
-      }
-
-      my $status = '';
-      my $statustext = '';
-      if ( $@ ) {
-        $status = 'failed';
-        $statustext = $@;
-      } elsif ( $error ) {
-        $status = 'done';
-        $statustext = $error;
-      } else {
-        $status = 'done'
-      }
-
-      #add cust_bill_event
-      my $cust_bill_event = new FS::cust_bill_event {
-        'invnum'     => $cust_bill->invnum,
-        'eventpart'  => $part_bill_event->eventpart,
-        #'_date'      => $invoice_time,
-        '_date'      => time,
-        'status'     => $status,
-        'statustext' => $statustext,
-      };
-      $error = $cust_bill_event->insert;
-      if ( $error ) {
-        #$dbh->rollback if $oldAutoCommit;
-        #return "error: $error";
+        warn "  do_event " .  $cust_bill . " ". (%options) .  "\n"
+          if $DEBUG > 1;
 
 
-        # gah, even with transactions.
-        $dbh->commit if $oldAutoCommit; #well.
-        my $e = 'WARNING: Event run but database not updated - '.
-                'error inserting cust_bill_event, invnum #'. $cust_bill->invnum.
-                ', eventpart '. $part_bill_event->eventpart.
-                ": $error";
-        warn $e;
-        return $e;
+        if (my $error = $part_bill_event->do_event($cust_bill, %options)) {
+         # gah, even with transactions.
+         $dbh->commit if $oldAutoCommit; #well.
+         return $error;
+       }
       }
 
       }
 
-
     }
 
   }
     }
 
   }
@@ -2335,9 +2284,10 @@ sub collect {
 
 =item retry_realtime
 
 
 =item retry_realtime
 
-Schedules realtime credit card / electronic check / LEC billing events for
-for retry.  Useful if card information has changed or manual retry is desired.
-The 'collect' method must be called to actually retry the transaction.
+Schedules realtime / batch  credit card / electronic check / LEC billing
+events for for retry.  Useful if card information has changed or manual
+retry is desired.  The 'collect' method must be called to actually retry
+the transaction.
 
 Implementation details: For each of this customer's open invoices, changes
 the status of the first "done" (with statustext error) realtime processing
 
 Implementation details: For each of this customer's open invoices, changes
 the status of the first "done" (with statustext error) realtime processing
@@ -2368,7 +2318,7 @@ sub retry_realtime {
         grep {
                #$_->part_bill_event->plan eq 'realtime-card'
                $_->part_bill_event->eventcode =~
         grep {
                #$_->part_bill_event->plan eq 'realtime-card'
                $_->part_bill_event->eventcode =~
-                   /\$cust_bill\->realtime_(card|ach|lec)/
+                   /\$cust_bill\->(batch|realtime)_(card|ach|lec)/
                  && $_->status eq 'done'
                  && $_->statustext
              }
                  && $_->status eq 'done'
                  && $_->statustext
              }
index e057334..d77b274 100644 (file)
@@ -3,7 +3,8 @@ package FS::cust_pay_batch;
 use strict;
 use vars qw( @ISA $DEBUG );
 use FS::Record qw(dbh qsearch qsearchs);
 use strict;
 use vars qw( @ISA $DEBUG );
 use FS::Record qw(dbh qsearch qsearchs);
-use Business::CreditCard;
+use FS::part_bill_event qw(due_events);
+use Business::CreditCard 0.28;
 
 @ISA = qw( FS::Record );
 
 
 @ISA = qw( FS::Record );
 
@@ -31,6 +32,8 @@ FS::cust_pay_batch - Object methods for batch cards
 
   $error = $record->check;
 
 
   $error = $record->check;
 
+  $error = $record->retriable;
+
 =head1 DESCRIPTION
 
 An FS::cust_pay_batch object represents a credit card transaction ready to be
 =head1 DESCRIPTION
 
 An FS::cust_pay_batch object represents a credit card transaction ready to be
@@ -143,19 +146,8 @@ sub check {
     or return "Illegal payby";
   $self->payby($1);
 
     or return "Illegal payby";
   $self->payby($1);
 
-  # FIXME
-  # there is no point in false laziness here
-  # we will effectively set "check_payinfo to 0"
-  # we can change that when we finish the refactor
-  
-  #my $cardnum = $self->cardnum;
-  #$cardnum =~ s/\D//g;
-  #$cardnum =~ /^(\d{13,16})$/
-  #  or return "Illegal credit card number";
-  #$cardnum = $1;
-  #$self->cardnum($cardnum);
-  #validate($cardnum) or return "Illegal credit card number";
-  #return "Unknown card type" if cardtype($cardnum) eq "Unknown";
+  #$error = FS::payby::payinfo_check($self->payby, \$self->payinfo);
+  #return $error if $error;
 
   if ( $self->exp eq '' ) {
     return "Expiration date required"
 
   if ( $self->exp eq '' ) {
     return "Expiration date required"
@@ -212,6 +204,54 @@ sub cust_main {
   qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 }
 
   qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 }
 
+=item retriable
+
+Marks the corresponding event (see L<FS::cust_bill_event>) for this batched
+credit card payment as retriable.  Useful if the corresponding financial
+institution account was declined for temporary reasons and/or a manual 
+retry is desired.
+
+Implementation details: For the named customer's invoice, changes the
+statustext of the 'done' (without statustext) event to 'retriable.'
+
+=cut
+
+sub retriable {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';        #Hmm
+  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 $cust_bill = qsearchs('cust_bill', { 'invnum' => $self->invnum } )
+    or return "event $self->eventnum references nonexistant invoice $self->invnum";
+
+  warn "cust_pay_batch->retriable working with self of " . $self->paybatchnum . " and invnum of " . $self->invnum;
+  my @cust_bill_event =
+    sort { $a->part_bill_event->seconds <=> $b->part_bill_event->seconds }
+      grep {
+        $_->part_bill_event->eventcode =~ /\$cust_bill->batch_card/
+         && $_->status eq 'done'
+         && ! $_->statustext
+       }
+      $cust_bill->cust_bill_event;
+  # complain loudly if scalar(@cust_bill_event) > 1 ?
+  my $error = $cust_bill_event[0]->retriable;
+  if ($error ) {
+    # gah, even with transactions.
+    $dbh->commit if $oldAutoCommit; #well.
+    return "error marking invoice event retriable: $error";
+  }
+  '';
+}
+
 =back
 
 =head1 SUBROUTINES
 =back
 
 =head1 SUBROUTINES
@@ -465,70 +505,19 @@ sub import_results {
 
       $new_cust_pay_batch->status('Declined');
 
 
       $new_cust_pay_batch->status('Declined');
 
-      #this should be configurable... if anybody else ever uses batches
-      # $cust_pay_batch->cust_main->suspend;
-
-      foreach my $part_bill_event (
-        sort {    $a->seconds   <=> $b->seconds
-               || $a->weight    <=> $b->weight
-               || $a->eventpart <=> $b->eventpart }
-          grep { ! qsearch( 'cust_bill_event', {
-                               'invnum'    => $cust_pay_batch->invnum,
-                               'eventpart' => $_->eventpart,
-                               'status'    => 'done',
-                                                                   } )
-               }
-            qsearch( {
-              'table'     => 'part_bill_event',
-              'hashref'   => { 'payby'    => 'DCLN',
-                               'disabled' => '',           },
-            } )
-      ) {
+      foreach my $part_bill_event ( due_events ( $new_cust_pay_batch,
+                                                 'DCLN',
+                                                '',
+                                                '') ) {
 
         # don't run subsequent events if balance<=0
         last if $cust_pay_batch->cust_main->balance <= 0;
 
 
         # don't run subsequent events if balance<=0
         last if $cust_pay_batch->cust_main->balance <= 0;
 
-        warn "  calling invoice event (". $part_bill_event->eventcode. ")\n"
-          if $DEBUG > 1;
-        my $cust_main = $cust_pay_batch->cust_main; #for callback
-
-        my $error;
-        {
-          local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
-          $error = eval $part_bill_event->eventcode;
-        }
-
-        my $status = '';
-        my $statustext = '';
-        if ( $@ ) {
-          $status = 'failed';
-          $statustext = $@;
-        } elsif ( $error ) {
-          $status = 'done';
-          $statustext = $error;
-        } else {
-          $status = 'done'
-        }
-
-       #add cust_bill_event
-       my $cust_bill_event = new FS::cust_bill_event {
-         'invnum'     => $cust_pay_batch->invnum,
-         'eventpart'  => $part_bill_event->eventpart,
-         '_date'      => time,
-         'status'     => $status,
-         'statustext' => $statustext,
-       };
-       $error = $cust_bill_event->insert;
-       if ( $error ) {
+       if (my $error = $part_bill_event->do_event($new_cust_pay_batch)) {
          # gah, even with transactions.
          $dbh->commit if $oldAutoCommit; #well.
          # gah, even with transactions.
          $dbh->commit if $oldAutoCommit; #well.
-          my $e = 'WARNING: Event run but database not updated - '.
-                  'error inserting cust_bill_event, invnum #'. $cust_pay_batch->invnum.
-                 ', eventpart '. $part_bill_event->eventpart.
-                  ": $error";
-          warn $e;
-          return $e;
-        }
+         return $error;
+       }
 
       }
 
 
       }
 
index 2aef5bc..473e0bd 100644 (file)
@@ -1,11 +1,13 @@
 package FS::part_bill_event;
 
 use strict;
 package FS::part_bill_event;
 
 use strict;
-use vars qw( @ISA );
-use FS::Record qw( qsearch qsearchs );
+use vars qw( @ISA $DEBUG @EXPORT_OK );
+use FS::Record qw( dbh qsearch qsearchs );
 use FS::Conf;
 
 use FS::Conf;
 
-@ISA = qw(FS::Record);
+@ISA = qw( FS::Record );
+@EXPORT_OK = qw( due_events );
+$DEBUG = 0;
 
 =head1 NAME
 
 
 =head1 NAME
 
@@ -26,6 +28,13 @@ FS::part_bill_event - Object methods for part_bill_event records
 
   $error = $record->check;
 
 
   $error = $record->check;
 
+  $error = $record->do_event( $direct_object );
+  
+  @events = due_events ( { 'record' => $event_triggering_record,
+                           'payby'  => $payby,
+                          'event_time => $_date,
+                          'extra_sql  => $extra } );
+
 =head1 DESCRIPTION
 
 An FS::part_bill_event object represents an invoice event definition -
 =head1 DESCRIPTION
 
 An FS::part_bill_event object represents an invoice event definition -
@@ -124,7 +133,7 @@ sub check {
 
     $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
 
 
     $c =~ /^\s*\$cust_main\->(suspend|cancel|invoicing_list_addpost|bill|collect)\(\);\s*("";)?\s*$/
 
-      or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\(\);\s*$/
+      or $c =~ /^\s*\$cust_bill\->(comp|realtime_(card|ach|lec)|batch_card|send)\((%options)*\);\s*$/
 
       or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/
 
 
       or $c =~ /^\s*\$cust_bill\->send(_if_newest)?\(\'[\w\-\s]+\'\s*(,\s*(\d+|\[\s*\d+(,\s*\d+)*\s*\])\s*,\s*'[\w\@\.\-\+]*'\s*)?\);\s*$/
 
@@ -197,6 +206,103 @@ sub templatename {
   }
 }
 
   }
 }
 
+=item due_events
+
+Returns the list of events due, if any, or false if there is none.
+Requires record and payby, but event_time and extra_sql are optional.
+
+=cut
+
+sub due_events {
+  my ($record, $payby, $event_time, $extra_sql) = @_;
+  my $interval = 0;
+  if ($record->_date){ 
+    $event_time = time unless $event_time;
+    $interval = $event_time - $record->_date;
+  }
+  sort {    $a->seconds   <=> $b->seconds
+         || $a->weight    <=> $b->weight
+        || $a->eventpart <=> $b->eventpart }
+    grep { $_->seconds <= ( $interval )
+           && ! qsearch( 'cust_bill_event', {
+                          'invnum' => $record->get($record->dbdef_table->primary_key),
+                          'eventpart' => $_->eventpart,
+                          'status' => 'done',
+                                                                        } )
+        }
+      qsearch( {
+        'table'     => 'part_bill_event',
+       'hashref'   => { 'payby'    => $payby,
+                        'disabled' => '',             },
+       'extra_sql' => $extra_sql,
+      } );
+
+
+}
+
+=item do_event
+
+Performs the event and returns any errors that occur.
+Requires a record on which to perform the event.
+Should only be performed inside a transaction.
+
+=cut
+
+sub do_event {
+  my ($self, $object, %options) = @_;
+  warn " calling event (". $self->eventcode. ") for " . $object->table . " " ,
+    $object->get($object->dbdef_table->primary_key) . "\n" if $DEBUG > 1;
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+
+  #  for "callback" -- heh
+  my $cust_main = $object->cust_main;
+  my $cust_bill;
+  if ($object->table eq 'cust_bill'){
+    $cust_bill = $object;
+  }
+  my $cust_pay_batch;
+  if ($object->table eq 'cust_pay_batch'){
+    $cust_pay_batch = $object;
+  }
+
+  my $error;
+  {
+    local $SIG{__DIE__}; # don't want Mason __DIE__ handler active
+    $error = eval $self->eventcode;
+  }
+
+  my $status = '';
+  my $statustext = '';
+  if ( $@ ) {
+    $status = 'failed';
+    $statustext = $@;
+  } elsif ( $error ) {
+    $status = 'done';
+    $statustext = $error;
+  } else {
+    $status = 'done';
+  }
+
+  #add cust_bill_event
+  my $cust_bill_event = new FS::cust_bill_event {
+#    'invnum'     => $object->get($object->dbdef_table->primary_key),
+    'invnum'     => $object->invnum,
+    'eventpart'  => $self->eventpart,
+    '_date'      => time,
+    'status'     => $status,
+    'statustext' => $statustext,
+  };
+  $error = $cust_bill_event->insert;
+  if ( $error ) {
+    my $e = 'WARNING: Event run but database not updated - '.
+            'error inserting cust_bill_event, invnum #'.  $object->invnum .
+           ', eventpart '. $self->eventpart.": $error";
+    warn $e;
+    return $e;
+  }
+  '';
+}
 
 =back
 
 
 =back
 
index 72a8766..2b54810 100644 (file)
@@ -1,9 +1,10 @@
 package FS::payby;
 
 use strict;
 package FS::payby;
 
 use strict;
-use vars qw(%hash);
+use vars qw(%hash @EXPORT_OK);
 use Tie::IxHash;
 
 use Tie::IxHash;
 
+
 =head1 NAME
 
 FS::payby - Object methods for payment type records
 =head1 NAME
 
 FS::payby - Object methods for payment type records
@@ -106,6 +107,11 @@ sub payby2longname {
   map { $_ => $hash{$_}->{longname} } $self->payby;
 }
 
   map { $_ => $hash{$_}->{longname} } $self->payby;
 }
 
+sub payby2bop {
+  { 'CARD' => 'CC'.
+    'CHEK' => 'ECHECK',};
+}
+
 sub cust_payby {
   my $self = shift;
   grep { ! exists $hash{$_}->{cust_main} } $self->payby;
 sub cust_payby {
   my $self = shift;
   grep { ! exists $hash{$_}->{cust_main} } $self->payby;
@@ -116,6 +122,27 @@ sub cust_payby2longname {
   map { $_ => $hash{$_}->{longname} } $self->cust_payby;
 }
 
   map { $_ => $hash{$_}->{longname} } $self->cust_payby;
 }
 
+sub payinfo_check{
+  my($payby, $payinforef) = @_;
+
+  if ($payby eq 'CARD') {
+    $$payinforef =~ s/\D//g;
+    if ($$payinforef){
+      $$payinforef =~ /^(\d{13,16})$/
+        or return "Illegal (mistyped?) credit card number (payinfo)";
+      $$payinforef = $1;
+      validate($$payinforef) or return "Illegal credit card number";
+      return "Unknown card type" if cardype($$payinforef) eq "Unknown";
+    } else {
+      $$payinforef="N/A";
+    }
+  } else {
+    $$payinforef =~ /^([\w \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=]*)$/
+    or return "Illegal text (payinfo)";
+    $$payinforef = $1;
+  }
+}
+
 =back
 
 =head1 BUGS
 =back
 
 =head1 BUGS
index 10f9ffa..db09177 100644 (file)
@@ -350,6 +350,8 @@ FS/CurrentUser.pm
 FS/svc_phone.pm
 t/svc_phone.t
 FS/h_svc_phone.pm
 FS/svc_phone.pm
 t/svc_phone.t
 FS/h_svc_phone.pm
+FS/cust_bill_pay_batch.pm
+t/cust_bill_pay_batch.t
 FS/cust_bill_pay_pkg.pm
 t/cust_bill_pay_pkg.t
 FS/cust_credit_bill_pkg.pm
 FS/cust_bill_pay_pkg.pm
 t/cust_bill_pay_pkg.t
 FS/cust_credit_bill_pkg.pm
diff --git a/FS/t/cust_bill_pay_batch.t b/FS/t/cust_bill_pay_batch.t
new file mode 100644 (file)
index 0000000..bc3a827
--- /dev/null
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::cust_bill_pay_batch;
+$loaded=1;
+print "ok 1\n";
diff --git a/httemplate/browse/cust_pay_batch.cgi b/httemplate/browse/cust_pay_batch.cgi
deleted file mode 100755 (executable)
index e40e958..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-<!-- mason kludge -->
-<% include("/elements/header.html","Credit card batch details", menubar( 'Main Menu' => $p,)) %>
-%
-%
-%die "No batch specified (bad URL)!" unless $cgi->keywords;
-%my($query) = $cgi->keywords;
-%$query =~ /^(\d+)$/;
-%my $batchnum = $1;
-%my $pay_batch = qsearchs('pay_batch',{'batchnum'=>$batchnum});
-%die "Batch not found!" unless $pay_batch;
-%
-%
-
-
-<FORM ACTION="<%$p%>misc/download-batch.cgi" METHOD="POST">
-Download batch in format <SELECT NAME="format">
-<OPTION VALUE="">Default batch mode</OPTION>
-<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>
-<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>
-<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>
-</SELECT><INPUT TYPE="hidden" NAME="batchnum" VALUE="<% $batchnum %>"><INPUT TYPE="submit" VALUE="Download"></FORM>
-<BR><BR>
-
-<FORM ACTION="<%$p%>misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">
-Upload results<BR>
-Filename <INPUT TYPE="file" NAME="batch_results"><BR>
-Format <SELECT NAME="format">
-<OPTION VALUE="">Default batch mode</OPTION>
-<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>
-<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>
-<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>
-</SELECT><BR>
-<INPUT TYPE="submit" VALUE="Upload"></FORM>
-<BR>
-%
-%  my $statement = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=".
-%                     $batchnum;
-%  my $sth = dbh->prepare($statement) or die dbh->errstr. "doing $statement";
-%  $sth->execute or die "Error executing \"$statement\": ". $sth->errstr;
-%  my $total = $sth->fetchrow_arrayref->[0];
-%
-%  my $c_statement = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=".
-%                       $batchnum;
-%  my $c_sth = dbh->prepare($c_statement)
-%    or die dbh->errstr. "doing $c_statement";
-%  $c_sth->execute or die "Error executing \"$c_statement\": ". $c_sth->errstr;
-%  my $cards = $c_sth->fetchrow_arrayref->[0];
-%
-
-<% $cards %> credit card payments batched<BR>
-$<% sprintf("%.2f", $total) %> total in batch<BR>
-
-<BR>
-<% &table() %>
-      <TR>
-        <TH>#</TH>
-        <TH><font size=-1>inv#</font></TH>
-        <TH COLSPAN=2>Customer</TH>
-        <TH>Card name</TH>
-        <TH>Card</TH>
-        <TH>Exp</TH>
-        <TH>Amount</TH>
-        <TH>Status</TH>
-      </TR>
-%
-%foreach my $cust_pay_batch ( sort { $a->paybatchnum <=> $b->paybatchnum }
-%                             qsearch('cust_pay_batch', {'batchnum'=>$batchnum} )
-%) {
-%  my $cardnum = $cust_pay_batch->payinfo;
-%  #$cardnum =~ s/.{4}$/xxxx/;
-%  $cardnum = 'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4));
-%
-%  $cust_pay_batch->exp =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
-%  my( $mon, $year ) = ( $2, $1 );
-%  $mon = "0$mon" if $mon < 10;
-%  my $exp = "$mon/$year";
-%
-%
-
-
-      <TR>
-        <TD><% $cust_pay_batch->paybatchnum %></TD>
-        <TD><A HREF="../view/cust_bill.cgi?<% $cust_pay_batch->invnum %>"><% $cust_pay_batch->invnum %></TD>
-        <TD><A HREF="../view/cust_main.cgi?<% $cust_pay_batch->custnum %>"><% $cust_pay_batch->custnum %></TD>
-        <TD><% $cust_pay_batch->get('last'). ', '. $cust_pay_batch->first %></TD>
-        <TD><% $cust_pay_batch->payname %></TD>
-        <TD><% $cardnum %></TD>
-        <TD><% $exp %></TD>
-        <TD align="right">$<% $cust_pay_batch->amount %></TD>
-        <TD><% $cust_pay_batch->status %></TD>
-      </TR>
-% } 
-
-
-    </TABLE>
-  </BODY>
-</HTML>
index 498d477..90292eb 100755 (executable)
@@ -36,6 +36,7 @@ function changed(what) {
 
 <BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
 <OPTION VALUE="">
 
 <BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
 <OPTION VALUE="">
+% foreach my $cust_bill ( @cust_bill ) { 
 
 % foreach my $cust_bill ( @cust_bill ) { 
   <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D", $cust_bill->_date) %> - $<% $cust_bill->owed %>
 
 % foreach my $cust_bill ( @cust_bill ) { 
   <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D", $cust_bill->_date) %> - $<% $cust_bill->owed %>
@@ -83,3 +84,4 @@ my @cust_bill = sort {    $a->_date  <=> $b->_date
                 qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } );
 </%init>
 
                 qsearch('cust_bill', { 'custnum' => $cust_pay->custnum } );
 </%init>
 
+
index 13d062c..803798e 100755 (executable)
@@ -25,6 +25,9 @@ Credit
     <TD ALIGN="right">Amount</TD>
     <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD>
   </TR>
     <TD ALIGN="right">Amount</TD>
     <TD BGCOLOR="#ffffff">$<INPUT TYPE="text" NAME="amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8></TD>
   </TR>
+%
+%#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
+%
 
 %
 %#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
 
 %
 %#print qq! <INPUT TYPE="checkbox" NAME="refund" VALUE="$refund">Also post refund!;
@@ -78,3 +81,4 @@ my $otaker = getotaker;
 
 my $p1 = popurl(1);
 </%init>
 
 my $p1 = popurl(1);
 </%init>
+
index 249ba31..26c1126 100755 (executable)
@@ -21,6 +21,12 @@ Credit #<B><% $crednum %></B>
 <SCRIPT>
 function changed(what) {
   cust_bill = what.options[what.selectedIndex].value;
 <SCRIPT>
 function changed(what) {
   cust_bill = what.options[what.selectedIndex].value;
+% foreach my $cust_bill ( @cust_bill ) {
+%  my $invnum = $cust_bill->invnum;
+%  my $changeto = $cust_bill->owed < $cust_credit->credited
+%                   ? $cust_bill->owed 
+%                   : $cust_credit->credited;
+%
 
 % foreach my $cust_bill ( @cust_bill ) {
 
 
 % foreach my $cust_bill ( @cust_bill ) {
 
@@ -38,6 +44,7 @@ function changed(what) {
 
 <BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
 <OPTION VALUE="">
 
 <BR>Invoice #<SELECT NAME="invnum" SIZE=1 onChange="changed(this)">
 <OPTION VALUE="">
+% foreach my $cust_bill ( @cust_bill ) { 
 
 % foreach my $cust_bill ( @cust_bill ) { 
   <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D",$cust_bill->_date) %> - $<% $cust_bill->owed %>
 
 % foreach my $cust_bill ( @cust_bill ) { 
   <OPTION<% $cust_bill->invnum eq $invnum ? ' SELECTED' : '' %> VALUE="<% $cust_bill->invnum %>"><% $cust_bill->invnum %> - <% time2str("%D",$cust_bill->_date) %> - $<% $cust_bill->owed %>
@@ -90,3 +97,4 @@ my @cust_bill = sort {    $a->_date  <=> $b->_date
                 qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } );
 </%init>
 
                 qsearch('cust_bill', { 'custnum' => $cust_credit->custnum } );
 </%init>
 
+
index a58f078..2439755 100755 (executable)
@@ -216,10 +216,16 @@ Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
 %
 %  'batch-card' => {
 %    'name' => 'Add card to the pending credit card batch',
 %
 %  'batch-card' => {
 %    'name' => 'Add card to the pending credit card batch',
-%    'code' => '$cust_bill->batch_card();',
+%    'code' => '$cust_bill->batch_card(%options);',
 %    'weight' => 40,
 %  },
 %
 %    'weight' => 40,
 %  },
 %
+%  'retriable' => {
+%    'name' => 'Mark batched card event as retriable',
+%    'code' => '$cust_pay_batch->retriable();',
+%    'weight' => 60,
+%  },
+%
 %  'send' => {
 %    'name' => 'Send invoice (email/print/fax)',
 %    'code' => '$cust_bill->send();',
 %  'send' => {
 %    'name' => 'Send invoice (email/print/fax)',
 %    'code' => '$cust_bill->send();',
@@ -430,3 +436,4 @@ Invoice Event #<% $hashref->{eventpart} ? $hashref->{eventpart} : "(NEW)" %>
   </BODY>
 </HTML>
 
   </BODY>
 </HTML>
 
+
index 2d698c7..20c051b 100755 (executable)
 %    my $berror = $new->bill;
 %    $new->apply_payments;
 %    $new->apply_credits;
 %    my $berror = $new->bill;
 %    $new->apply_payments;
 %    $new->apply_credits;
-%    $berror ||= $new->collect;
+%    $berror ||= $new->collect 'realtime' => 1;
 %    warn "Warning, error billing during backend-realtime: $berror" if $berror;
 %
 %  }
 %    warn "Warning, error billing during backend-realtime: $berror" if $berror;
 %
 %  }
 %} 
 %
 
 %} 
 %
 
+
index fadfffb..3a58d9a 100755 (executable)
@@ -1,4 +1,4 @@
-%
+                       %
 %
 %$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
 %my $custnum = $1;
 %
 %$cgi->param('custnum') =~ /^(\d*)$/ or die "Illegal custnum!";
 %my $custnum = $1;
@@ -7,11 +7,7 @@
 %
 %my $error = '';
 %if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) { 
 %
 %my $error = '';
 %if ( $cgi->param('payby') =~ /^(CARD|CHEK)$/ ) { 
-%  my %payby2bop = (
-%  'CARD' => 'CC',
-%  'CHEK' => 'ECHECK',
-%  );
-%  my $bop = $payby2bop{$1};
+%  my $bop = FS::payby::$payby2bop{$1};
 %  $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/
 %    or die "illegal refund amount ". $cgi->param('refund');
 %  my $refund = "$1$2";
 %  $cgi->param('refund') =~ /^(\d*)(\.\d{2})?$/
 %    or die "illegal refund amount ". $cgi->param('refund');
 %  my $refund = "$1$2";
index 1532a44..6e4cc26 100755 (executable)
@@ -1,5 +1,4 @@
 %
 %
-%
 %#untaint custnum
 %my($query) = $cgi->keywords;
 %$query =~ /^(\d*)$/;
 %#untaint custnum
 %my($query) = $cgi->keywords;
 %$query =~ /^(\d*)$/;
@@ -7,6 +6,8 @@
 %my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
 %die "Can't find customer!\n" unless $cust_main;
 %
 %my $cust_main = qsearchs('cust_main',{'custnum'=>$custnum});
 %die "Can't find customer!\n" unless $cust_main;
 %
+%my $conf = new FS::Conf;
+%
 %my $error = $cust_main->bill(
 %#                          'time'=>$time
 %                         );
 %my $error = $cust_main->bill(
 %#                          'time'=>$time
 %                         );
@@ -23,6 +24,7 @@
 %                               #'report_badcard'=> 'yes',
 %                               #'retry_card' => 'yes',
 %                               'retry' => 'yes',
 %                               #'report_badcard'=> 'yes',
 %                               #'retry_card' => 'yes',
 %                               'retry' => 'yes',
+%                               'realtime' => $conf->exists('realtime-backend'),
 %                              );
 %}
 %#&eidiot($error) if $error;
 %                              );
 %}
 %#&eidiot($error) if $error;
index 038aa20..ad88092 100644 (file)
@@ -1,17 +1,9 @@
 %
 %
-%
 %my $conf=new FS::Conf;
 %
 %#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
 %http_header('Content-Type' => 'text/plain' );
 %
 %my $conf=new FS::Conf;
 %
 %#http_header('Content-Type' => 'text/comma-separated-values' ); #IE chokes
 %http_header('Content-Type' => 'text/plain' );
 %
-%my $batchnum;
-%if ( $cgi->param('batchnum') =~ /^(\d+)$/ ) {
-%  $batchnum = $1;
-%} else {
-%  die "No batch number (bad URL) \n";
-%}
-%
 %my $format;
 %if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
 %  $format = $1;
 %my $format;
 %if ( $cgi->param('format') =~ /^([\w\- ]+)$/ ) {
 %  $format = $1;
 %local $FS::UID::AutoCommit = 0;
 %my $dbh = dbh;
 %
 %local $FS::UID::AutoCommit = 0;
 %my $dbh = dbh;
 %
-%my $pay_batch = qsearchs('pay_batch', {'batchnum'=>$batchnum, 'status'=>'O'} );
+%my $pay_batch = qsearchs('pay_batch', {'status'=>''} );
 %die "No pending batch. \n" unless $pay_batch;
 %
 %my %batchhash = $pay_batch->hash;
 %$batchhash{'status'} = 'I';
 %die "No pending batch. \n" unless $pay_batch;
 %
 %my %batchhash = $pay_batch->hash;
 %$batchhash{'status'} = 'I';
-%$batchhash{'download'} = time unless $batchhash{'download'};
 %my $new = new FS::pay_batch \%batchhash;
 %my $error = $new->replace($pay_batch);
 %die "error updating batch status: $error\n" if $error;
 %my $new = new FS::pay_batch \%batchhash;
 %my $error = $new->replace($pay_batch);
 %die "error updating batch status: $error\n" if $error;
 %my $batchtotal=0;
 %my $batchcount=0;
 %
 %my $batchtotal=0;
 %my $batchcount=0;
 %
-%my (@date)=localtime($new->download);
-%my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7] + 1);
-%my $cdate = sprintf("%02d", $date[3]).sprintf("%02d", $date[4] + 1).
-%            sprintf("%02d", $date[5] % 100);
+%my (@date)=localtime();
+%my $jdate = sprintf("%03d", $date[5] % 100).sprintf("%03d", $date[7]);
 %
 %if ($format eq "BoM") {
 %
 %
 %if ($format eq "BoM") {
 %
   %>
 %
 %
   %>
 %
 %
-%}elsif ($format eq "PAP"){
-%
-%  my($origid,$datacenter,$typecode,$shortname,$longname,$mybank,$myacct) =
-%    $conf->config("batchconfig-$format");
-%  
-<% sprintf( "H%10sD%3s%06u%-15s%09u%-12s%04u%19s\n",$origid,$typecode,$cdate,$shortname,$mybank,$myacct,$pay_batch->batchnum,"")
-
-  %>
-%
-%
 %}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
 %#  1;
 %}else{
 %}elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
 %#  1;
 %}else{
 %
 %    my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
 %    
 %
 %    my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
 %    
-<% sprintf( "D%010.0f%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->paybatchnum) %>
-%
-%
-%  } elsif ($format eq "PAP"){
-%
-%    my( $account, $aba ) = split( '@', $cust_pay_batch->payinfo );
-%    
-<% sprintf( "D%-23s%06u%-19s%09u%-12s%010.0f\n",$cust_pay_batch->payname,$cdate,$cust_pay_batch->paybatchnum,$aba,$account,$cust_pay_batch->amount*100) %>
+<% sprintf( "D%010u%09u%-12s%-29s%-19s\n",$cust_pay_batch->amount*100,$aba,$account,$cust_pay_batch->payname,$cust_pay_batch->invnum %>
 %
 %
 %  } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") {
 %
 %
 %  } elsif ($format eq "csv-td_canada_trust-merchant_pc_batch") {
 %if ($format eq "BoM") {
 %
 %  
 %if ($format eq "BoM") {
 %
 %  
-<% sprintf( "YD%08u%014.0f%56s\n",$batchcount,$batchtotal*100,"" ).
+<% sprintf( "YD%08u%014u%56s\n",$batchcount,$batchtotal*100,"" ).
         sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %>
 %
 %
         sprintf( "Z%014u%05u%014u%05u%41s\n",$batchtotal*100,$batchcount,"0","0","" ) %>
 %
 %
-%} elsif ($format eq "PAP"){
-%
-%  
-<% sprintf( "T%08u%014.0f%57s\n",$batchcount,$batchtotal*100,"" ) %>
-%
-%
 %} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
 %  #1;
 %} else {
 %} elsif ($format eq "csv-td_canada_trust-merchant_pc_batch"){
 %  #1;
 %} else {
index 188d1dd..e1fade4 100644 (file)
@@ -1,5 +1,4 @@
 %
 %
-%
 %#some false laziness w/MyAccount::process_payment
 %
 %$cgi->param('custnum') =~ /^(\d+)$/
 %#some false laziness w/MyAccount::process_payment
 %
 %$cgi->param('custnum') =~ /^(\d+)$/
 %$cgi->param('payby') =~ /^(CARD|CHEK)$/
 %  or die "illegal payby ". $cgi->param('payby');
 %my $payby = $1;
 %$cgi->param('payby') =~ /^(CARD|CHEK)$/
 %  or die "illegal payby ". $cgi->param('payby');
 %my $payby = $1;
-%my %payby2bop = (
-%  'CARD' => 'CC',
-%  'CHEK' => 'ECHECK',
-%);
 %my %payby2fields = (
 %  'CARD' => [ qw( address1 address2 city state zip ) ],
 %  'CHEK' => [ qw( ss ) ],
 %my %payby2fields = (
 %  'CARD' => [ qw( address1 address2 city state zip ) ],
 %  'CHEK' => [ qw( ss ) ],
@@ -87,7 +82,7 @@
 %  die "unknown payby $payby";
 %}
 %
 %  die "unknown payby $payby";
 %}
 %
-%my $error = $cust_main->realtime_bop( $payby2bop{$payby}, $amount,
+%my $error = $cust_main->realtime_bop( $FS::payby::payby2bop->{$payby}, $amount,
 %  'quiet'    => 1,
 %  'payinfo'  => $payinfo,
 %  'paydate'  => "$year-$month-01",
 %  'quiet'    => 1,
 %  'payinfo'  => $payinfo,
 %  'paydate'  => "$year-$month-01",
diff --git a/httemplate/search/cust_pay_batch.cgi b/httemplate/search/cust_pay_batch.cgi
new file mode 100755 (executable)
index 0000000..1b0bf6a
--- /dev/null
@@ -0,0 +1,145 @@
+%
+%
+%my ($count_query, $sql_query, $batchnum);
+%my $hashref = {};
+%my @search = ();
+%my $orderby = 'paybatchnum';
+%
+%if ( $cgi->param('batchnum') && $cgi->param('batchnum') =~ /^(\d+)$/ ) {
+%  push @search, "batchnum = $1";
+%  my $pay_batch = qsearchs('pay_batch', { 'batchnum' => $1 } );
+%  die "Batch $1 not found!" unless $pay_batch;
+%  $batchnum = $pay_batch->batchnum;
+%}
+%
+%if ( $cgi->param('payby')  ) {
+%  $cgi->param('payby') =~ /^(CARD|CHEK)$/
+%    or die "illegal payby " . $cgi->param('payby');
+%
+%  push @search, "cust_pay_batch.payby = '$1'";
+%}
+%
+%if ( not $cgi->param('dcln') ) {
+%  push @search, "cpb.status IS DISTINCT FROM 'Approved'";
+%}
+%
+%my ($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+%unless ($batchnum){
+%  push @search, "pay_batch.upload >= $beginning" if ($beginning);
+%  push @search, "pay_batch.upload <= $ending" if ($ending < 4294967295);#2^32-1
+%  $orderby = "pay_batch.download,paybatchnum";
+%}
+%
+%push @search, $FS::CurrentUser::CurrentUser->agentnums_sql;
+%my $search = ' WHERE ' . join(' AND ', @search);
+%
+%$count_query = 'SELECT COUNT(*) FROM cust_pay_batch AS cpb ' .
+%                  'LEFT JOIN cust_main USING ( custnum ) ' .
+%                  'LEFT JOIN pay_batch USING ( batchnum )' .
+%                $search;
+%
+%#grr
+%$sql_query = "SELECT paybatchnum,invnum,custnum,cpb.last,cpb.first," .
+%             "cpb.payname,cpb.payinfo,cpb.exp,amount,cpb.status " .
+%           "FROM cust_pay_batch AS cpb " .
+%             'LEFT JOIN cust_main USING ( custnum ) ' .
+%             'LEFT JOIN pay_batch USING ( batchnum ) ' .
+%             "$search ORDER BY $orderby";
+%
+%my $html_init = <<EOF;
+%<FORM ACTION="$p/misc/download-batch.cgi" METHOD="POST">
+%Download batch in format <SELECT NAME="format">
+%<OPTION VALUE="">Default batch mode</OPTION>
+%<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV file for TD Canada Trust Merchant PC Batch</OPTION>
+%<OPTION VALUE="PAP">80 byte file for TD Canada Trust PAP Batch</OPTION>
+%<OPTION VALUE="BoM">Bank of Montreal ECA batch</OPTION>
+%</SELECT><INPUT TYPE="hidden" NAME="batchnum" VALUE="$batchnum"><INPUT TYPE="submit" VALUE="Download"></FORM>
+%<BR><BR>
+%
+%<FORM ACTION="$p/misc/upload-batch.cgi" METHOD="POST" ENCTYPE="multipart/form-data">
+%Upload results<BR>
+%Filename <INPUT TYPE="file" NAME="batch_results"><BR>
+%Format <SELECT NAME="format">
+%<OPTION VALUE="">Default batch mode</OPTION>
+%<OPTION VALUE="csv-td_canada_trust-merchant_pc_batch">CSV results from TD Canada Trust Merchant PC Batch</OPTION>
+%<OPTION VALUE="PAP">264 byte results for TD Canada Trust PAP Batch</OPTION>
+%<OPTION VALUE="BoM">Bank of Montreal ECA results</OPTION>
+%</SELECT><BR>
+%<INPUT TYPE="submit" VALUE="Upload"></FORM>
+%<BR>
+%EOF
+%
+%if ($batchnum) {
+%  my $sth = dbh->prepare($count_query) or die dbh->errstr. "doing $count_query";
+%  $sth->execute or die "Error executing \"$count_query\": ". $sth->errstr;
+%  my $cards = $sth->fetchrow_arrayref->[0];
+%
+%  my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=". $batchnum;
+%  $sth = dbh->prepare($st) or die dbh->errstr. "doing $st";
+%  $sth->execute or die "Error executing \"$st\": ". $sth->errstr;
+%  my $total = $sth->fetchrow_arrayref->[0];
+%
+%  $html_init .= "$cards credit card payments batched<BR>\$" .
+%                sprintf("%.2f", $total) ." total in batch<BR>";
+%}
+%
+%
+<% include('elements/search.html',
+              'title'       => 'Batch payment details',
+              'name'        => 'batch details',
+             'menubar'     => ['Main Menu'  => $p,],
+             'query'       => $sql_query,
+             'count_query' => $count_query,
+              'html_init'   => $batchnum ? $html_init : '',
+             'header'      => [ '#',
+                                'Inv #',
+                                'Customer',
+                                'Customer',
+                                'Card Name',
+                                'Card',
+                                'Exp',
+                                'Amount',
+                                'Status',
+                              ],
+             'fields'      => [ sub {
+                                  shift->[0];
+                                },
+                                sub {
+                                  shift->[1];
+                                },
+                                sub {
+                                  shift->[2];
+                                },
+                                sub {
+                                  my $cpb = shift;
+                                  $cpb->[3] . ', ' . $cpb->[4];
+                                },
+                                sub {
+                                  shift->[5];
+                                },
+                                sub {
+                                  my $cardnum = shift->[6];
+                                   'x'x(length($cardnum)-4). substr($cardnum,(length($cardnum)-4));
+                                },
+                                sub {
+                                  shift->[7] =~ /^\d{2}(\d{2})[\/\-](\d+)[\/\-]\d+$/;
+                                   my( $mon, $year ) = ( $2, $1 );
+                                   $mon = "0$mon" if $mon < 10;
+                                   "$mon/$year";
+                                },
+                                sub {
+                                  shift->[8];
+                                },
+                                sub {
+                                  shift->[9];
+                                },
+                              ],
+             'align'       => 'lllllllrl',
+             'links'       => [ ['', sub{'#';}],
+                                ["${p}view/cust_bill.cgi?", sub{shift->[1];},],
+                                ["${p}view/cust_main.cgi?", sub{shift->[2];},],
+                                ["${p}view/cust_main.cgi?", sub{shift->[2];},],
+                              ],
+      )
+%>
+
diff --git a/httemplate/search/pay_batch.cgi b/httemplate/search/pay_batch.cgi
new file mode 100755 (executable)
index 0000000..fcfa8be
--- /dev/null
@@ -0,0 +1,93 @@
+%
+%
+%my %statusmap = ('I'=>'In Transit', 'O'=>'Open', 'R'=>'Resolved');
+%my $hashref = {};
+%my $count_query = 'SELECT COUNT(*) FROM pay_batch';
+%
+%my($begin, $end) = ( '', '' );
+%
+%my @where;
+%if ( $cgi->param('beginning')
+%     && $cgi->param('beginning') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+%  $begin = str2time($1);
+%  push @where, "download >= $begin";
+%}
+%if ( $cgi->param('ending')
+%      && $cgi->param('ending') =~ /^([ 0-9\-\/]{0,10})$/ ) {
+%  $end = str2time($1) + 86399;
+%  push @where, "download < $end";
+%}
+%
+%my @status;
+%if ( $cgi->param('open') ) {
+%  push @status, "O";
+%}
+%
+%if ( $cgi->param('intransit') ) {
+%  push @status, "I";
+%}
+%
+%if ( $cgi->param('resolved') ) {
+%  push @status, "R";
+%}
+%
+%push @where,
+%     scalar(@status) ? q!(status='! . join(q!' OR status='!, @status) . q!')!
+%                     : q!status='X'!;  # kludgy, X is unused at present
+%
+%my $extra_sql = scalar(@where) ? 'WHERE ' . join(' AND ', @where) : ''; 
+%
+%
+<% include( 'elements/search.html',
+                 'title'        => 'Credit Card Batches',
+                'menubar'      => [ 'Main Menu' => $p, ],
+                'name'         => 'batches',
+                'query'        => { 'table'     => 'pay_batch',
+                                    'hashref'   => $hashref,
+                                    'extra_sql' => "$extra_sql ORDER BY batchnum DESC",
+                                  },
+                'count_query'  => "$count_query $extra_sql",
+                'header'       => [ 'Batch',
+                                    'First Download',
+                                    'Last Upload',
+                                    'Item Count',
+                                    'Amount',
+                                    'Status',
+                                  ],
+                'align'        => 'lllrrl',
+                'fields'       => [ 'batchnum',
+                                     sub {
+                                      my $_date = shift->download;
+                                      $_date ? time2str("%a %b %e %T %Y", $_date) : '' 
+                                    },
+                                     sub {
+                                      my $_date = shift->upload;
+                                      $_date ? time2str("%a %b %e %T %Y", $_date) : '' 
+                                    },
+                                    sub {
+                                       my $st = "SELECT COUNT(*) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
+                                       my $sth = dbh->prepare($st)
+                                         or die dbh->errstr. "doing $st";
+                                       $sth->execute
+                                        or die "Error executing \"$st\": ". $sth->errstr;
+                                       $sth->fetchrow_arrayref->[0];
+                                    },
+                                    sub {
+                                       my $st = "SELECT SUM(amount) from cust_pay_batch WHERE batchnum=" . shift->batchnum;
+                                       my $sth = dbh->prepare($st)
+                                        or die dbh->errstr. "doing $st";
+                                       $sth->execute
+                                        or die "Error executing \"$st\": ". $sth->errstr;
+                                       $sth->fetchrow_arrayref->[0];
+                                    },
+                                     sub {
+                                      $statusmap{shift->status};
+                                    },
+                                  ],
+                'links'        => [ [ "${p}search/cust_pay_batch.cgi?batchnum=", 'batchnum',],
+                                  ],
+      )
+
+%>
+
+
diff --git a/httemplate/search/pay_batch.html b/httemplate/search/pay_batch.html
new file mode 100644 (file)
index 0000000..a966f68
--- /dev/null
@@ -0,0 +1,27 @@
+<% include('/elements/header.html', 'Batch criteria' ) %>
+
+<FORM ACTION="pay_batch.cgi" METHOD="GET">
+<INPUT TYPE="hidden" NAME="magic" VALUE="_date">
+
+<TABLE>
+  <% include( '/elements/tr-input-beginning_ending.html' ) %>
+  <TR>
+    <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="open" VALUE="1" CHECKED></TD>
+    <TD>Show open batches</TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="intransit" VALUE="1" CHECKED></TD>
+    <TD>Show in-transit batches</TD>
+  </TR>
+  <TR>
+    <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="resolved" VALUE="1" CHECKED></TD>
+    <TD>Show resolved batches</TD>
+  </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Batches">
+
+</FORM>
+
+<% include('/elements/footer.html') %>
diff --git a/httemplate/search/report_cust_pay_batch.html b/httemplate/search/report_cust_pay_batch.html
new file mode 100644 (file)
index 0000000..0dc4bc1
--- /dev/null
@@ -0,0 +1,37 @@
+<% include('/elements/header.html', 'Batch payment report' ) %>
+
+<FORM ACTION="cust_pay_batch.cgi" METHOD="GET">
+
+<TABLE>
+
+  <TR>
+    <TD ALIGN="right">Payments of type: </TD>
+    <TD>
+      <SELECT NAME="payby">
+        <OPTION VALUE="">all</OPTION>
+        <OPTION VALUE="CARD">credit card</OPTION>
+        <OPTION VALUE="CHEK">electronic check / ACH</OPTION>
+      </SELECT>
+    </TD>
+  </TR>
+
+  <% include( '/elements/tr-select-agent.html',
+                 $cgi->param('agentnum'),
+                 'label' => 'for agent: ',
+             )
+  %>
+
+  <% include( '/elements/tr-input-beginning_ending.html' ) %>
+
+  <TR>
+    <TD ALIGN="right"><INPUT TYPE="checkbox" NAME="dcln" VALUE="1" CHECKED></TD>
+    <TD>Include approved items</TD>
+  </TR>
+</TABLE>
+
+<BR>
+<INPUT TYPE="submit" VALUE="Get Report">
+
+</FORM>
+
+<% include('/elements/footer.html') %>