[freeside-commits] branch FREESIDE_3_BRANCH updated. 65d6c927e8062a04ec262a241ed39f945f998ee9

Jonathan Prykop jonathan at 420.am
Mon Mar 2 16:18:50 PST 2015


The branch, FREESIDE_3_BRANCH has been updated
       via  65d6c927e8062a04ec262a241ed39f945f998ee9 (commit)
       via  c880cbc2d246e961149c5b1f4f9b0c4c00753e2b (commit)
      from  5de6205d74e89fb3227402662d6848c8d52eadb7 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 65d6c927e8062a04ec262a241ed39f945f998ee9
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Mon Mar 2 17:53:51 2015 -0600

    RT#30825: Modernize Bulk payment importing [error handling for mismatched agentnum]

diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index 7edf6f1..fae94c3 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -1184,40 +1184,49 @@ sub process_batch_import {
   my $hashcb = sub {
     my %hash = @_;
     my $custnum = $hash{'custnum'};
+    my $agentnum = $hash{'agentnum'};
     my $agent_custid = $hash{'agent_custid'};
     #standardize date
     $hash{'_date'} = parse_datetime($hash{'_date'})
       if $hash{'_date'} && $hash{'_date'} =~ /\D/;
+    #remove custnum_prefix
+    my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix');
+    my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8;
+    if (
+      $custnum_prefix 
+      && $custnum =~ /^$custnum_prefix(0*([1-9]\d*))$/
+      && length($1) == $custnum_length 
+    ) {
+      $custnum = $2;
+    }
+    # check agentnum against custnum and
     # translate agent_custid into regular custnum
     if ($custnum && $agent_custid) {
       die "can't specify both custnum and agent_custid\n";
-    } elsif ($agent_custid) {
+    } elsif ($agentnum || $agent_custid) {
       # here is the agent virtualization
       my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql;
-      my $agentnum = $hash{'agentnum'};
-      my %search = (
-        'agent_custid' => $agent_custid,
-        'agentnum'     => $agentnum,
-      );
+      my %search;
+      $search{'agentnum'} = $agentnum
+        if $agentnum;
+      $search{'agent_custid'} = $agent_custid
+        if $agent_custid;
+      $search{'custnum'} = $custnum
+        if $custnum;
       my $cust_main = qsearchs({
         'table'     => 'cust_main',
         'hashref'   => \%search,
         'extra_sql' => $extra_sql,
       });
-      die "can't find customer with agent_custid $agent_custid\n"
+      die "can't find customer with" .
+        ($agentnum ? " agentnum $agentnum" : '') .
+        ($custnum  ? " custnum $custnum" : '') .
+        ($agent_custid ? " agent_custid $agent_custid" : '') . "\n"
         unless $cust_main;
+      die "mismatched customer number\n"
+        if $custnum && ($custnum ne $cust_main->custnum);
       $custnum = $cust_main->custnum;
     }
-    #remove custnum_prefix
-    my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix');
-    my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8;
-    if (
-      $custnum_prefix 
-      && $custnum =~ /^$custnum_prefix(0*([1-9]\d*))$/
-      && length($1) == $custnum_length 
-    ) {
-      $custnum = $2;
-    }
     $hash{'custnum'} = $custnum;
     delete($hash{'agent_custid'});
     return %hash;
diff --git a/httemplate/elements/select-payby.html b/httemplate/elements/select-payby.html
index 2018874..a976b77 100644
--- a/httemplate/elements/select-payby.html
+++ b/httemplate/elements/select-payby.html
@@ -3,7 +3,7 @@
         <% $onchange %>
 >
 
-% unless ( $opt{'multiple'} || $opt{'no_all'} ) {
+% unless ( $opt{'multiple'} || $opt{'disable_empty'} ) {
     <OPTION VALUE="" <% '' eq $value ? 'SELECTED' : '' %> ><% mt('all') |h %> 
 % }
 
diff --git a/httemplate/misc/cust_pay-import.cgi b/httemplate/misc/cust_pay-import.cgi
index ee0154d..102f842 100644
--- a/httemplate/misc/cust_pay-import.cgi
+++ b/httemplate/misc/cust_pay-import.cgi
@@ -41,7 +41,7 @@ Import a file containing customer payments.
 
 <% include( '/elements/tr-select-payby.html',
      'paybys' => \%paybys,
-     'no_all' => 1,
+     'disable_empty' => 1,
      'label'  => '<B>Payment type</B>',
    )
 %>

commit c880cbc2d246e961149c5b1f4f9b0c4c00753e2b
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Sat Feb 28 00:04:43 2015 -0600

    RT#30825: Modernize Bulk payment importing

diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index a1c8776..88d452a 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -1686,6 +1686,7 @@ sub process_batch_import {
     format_xml_formats         => $opt->{format_xml_formats},
     format_asn_formats         => $opt->{format_asn_formats},
     format_row_callbacks       => $opt->{format_row_callbacks},
+    format_hash_callbacks      => $opt->{format_hash_callbacks},
     #per-import
     job                        => $job,
     file                       => $file,
@@ -1694,6 +1695,7 @@ sub process_batch_import {
     params                     => { map { $_ => $param->{$_} } @pass_params },
     #?
     default_csv                => $opt->{default_csv},
+    preinsert_callback         => $opt->{preinsert_callback},
     postinsert_callback        => $opt->{postinsert_callback},
     insert_args_callback       => $opt->{insert_args_callback},
   );
@@ -1732,6 +1734,8 @@ Class method for batch imports.  Available params:
 
 =item format_row_callbacks
 
+=item format_hash_callbacks - After parsing, before object creation
+
 =item fields - Alternate way to specify import, specifying import fields directly as a listref
 
 =item preinsert_callback
@@ -1773,7 +1777,7 @@ sub batch_import {
 
   my( $type, $header, $sep_char,
       $fixedlength_format, $xml_format, $asn_format,
-      $parser_opt, $row_callback, @fields );
+      $parser_opt, $row_callback, $hash_callback, @fields );
 
   my $postinsert_callback = '';
   $postinsert_callback = $param->{'postinsert_callback'}
@@ -1829,6 +1833,11 @@ sub batch_import {
         ? $param->{'format_row_callbacks'}{ $param->{'format'} }
         : '';
 
+    $hash_callback =
+      $param->{'format_hash_callbacks'}
+        ? $param->{'format_hash_callbacks'}{ $param->{'format'} }
+        : '';
+
     @fields = @{ $formats->{ $format } };
 
   } elsif ( $param->{'fields'} ) {
@@ -1838,6 +1847,7 @@ sub batch_import {
     $sep_char = ',';
     $fixedlength_format = '';
     $row_callback = '';
+    $hash_callback = '';
     @fields = @{ $param->{'fields'} };
 
   } else {
@@ -2063,6 +2073,8 @@ sub batch_import {
       $hash{custnum} = $2;
     }
 
+    %hash = &{$hash_callback}(%hash) if $hash_callback;
+
     #my $table   = $param->{table};
     my $class = "FS::$table";
 
diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index c2e0955..7edf6f1 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -227,6 +227,12 @@ sub insert {
         $dbh->rollback if $oldAutoCommit;
         return "Unknown cust_bill.invnum: ". $self->invnum;
       };
+    if ($self->custnum && ($cust_bill->custnum ne $self->custnum)) {
+      $dbh->rollback if $oldAutoCommit;
+      return "Invoice custnum ".$cust_bill->custnum
+        ." does not match specified custnum ".$self->custnum
+        ." for invoice ".$self->invnum;
+    }
     $self->custnum($cust_bill->custnum );
   }
 
@@ -1166,6 +1172,87 @@ sub process_upgrade_paybatch {
 
 =over 4 
 
+=item process_batch_import
+
+=cut
+
+sub process_batch_import {
+  my $job = shift;
+
+  #agent_custid isn't a cust_pay field, see hash callback
+  my $format = [ qw(custnum agent_custid paid payinfo invnum) ];
+  my $hashcb = sub {
+    my %hash = @_;
+    my $custnum = $hash{'custnum'};
+    my $agent_custid = $hash{'agent_custid'};
+    #standardize date
+    $hash{'_date'} = parse_datetime($hash{'_date'})
+      if $hash{'_date'} && $hash{'_date'} =~ /\D/;
+    # translate agent_custid into regular custnum
+    if ($custnum && $agent_custid) {
+      die "can't specify both custnum and agent_custid\n";
+    } elsif ($agent_custid) {
+      # here is the agent virtualization
+      my $extra_sql = ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql;
+      my $agentnum = $hash{'agentnum'};
+      my %search = (
+        'agent_custid' => $agent_custid,
+        'agentnum'     => $agentnum,
+      );
+      my $cust_main = qsearchs({
+        'table'     => 'cust_main',
+        'hashref'   => \%search,
+        'extra_sql' => $extra_sql,
+      });
+      die "can't find customer with agent_custid $agent_custid\n"
+        unless $cust_main;
+      $custnum = $cust_main->custnum;
+    }
+    #remove custnum_prefix
+    my $custnum_prefix = $conf->config('cust_main-custnum-display_prefix');
+    my $custnum_length = $conf->config('cust_main-custnum-display_length') || 8;
+    if (
+      $custnum_prefix 
+      && $custnum =~ /^$custnum_prefix(0*([1-9]\d*))$/
+      && length($1) == $custnum_length 
+    ) {
+      $custnum = $2;
+    }
+    $hash{'custnum'} = $custnum;
+    delete($hash{'agent_custid'});
+    return %hash;
+  };
+
+  my $opt = { 'table'   => 'cust_pay',
+              'params'  => [ '_date', 'agentnum', 'payby', 'paybatch' ],
+              'formats' => {
+                'simple-csv' => $format,
+                'simple-xls' => $format,
+              },
+              'format_types' => {
+                'simple-csv' => 'csv',
+                'simple-xls' => 'xls',
+              },
+              'default_csv' => 1,
+              'format_hash_callbacks' => { 
+                'simple-csv' => $hashcb,
+                'simple-xls' => $hashcb,
+              },
+              'postinsert_callback' => sub {
+                 my $cust_pay = shift;
+                 my $cust_main = $cust_pay->cust_main ||
+                   return "can't find customer to which payments apply";
+                 my $error = $cust_main->apply_payments_and_credits;
+                 return $error
+                   ? "can't apply payments to customer ".$cust_pay->custnum."$error"
+                   : '';
+              },
+            };
+
+  FS::Record::process_batch_import( $job, $opt, @_ );
+
+}
+
 =item batch_import HASHREF
 
 Inserts new payments.
diff --git a/FS/FS/payby.pm b/FS/FS/payby.pm
index 2e2a664..11bc184 100644
--- a/FS/FS/payby.pm
+++ b/FS/FS/payby.pm
@@ -239,6 +239,28 @@ sub cust_payby2longname {
   map { $_ => $hash{$_}->{longname} } $self->cust_payby;
 }
 
+=item payment_payby
+
+Returns all values of payby that can be used by payments.
+
+=cut
+
+sub payment_payby {
+  my $self = shift;
+  grep { ! exists $hash{$_}->{cust_pay} } $self->payby;
+}
+
+=item payment_payby2longname
+
+Returns hash, keys are L</payment_payby> types, values are payby longname.
+
+=cut
+
+sub payment_payby2longname {
+  my $self = shift;
+  map { $_ => $hash{$_}->{longname} } $self->payment_payby;
+}
+
 =back
 
 =head1 BUGS
diff --git a/httemplate/elements/select-payby.html b/httemplate/elements/select-payby.html
index b2d5421..2018874 100644
--- a/httemplate/elements/select-payby.html
+++ b/httemplate/elements/select-payby.html
@@ -3,7 +3,7 @@
         <% $onchange %>
 >
 
-% unless ( $opt{'multiple'} ) {
+% unless ( $opt{'multiple'} || $opt{'no_all'} ) {
     <OPTION VALUE="" <% '' eq $value ? 'SELECTED' : '' %> ><% mt('all') |h %> 
 % }
 
diff --git a/httemplate/misc/cust_pay-import.cgi b/httemplate/misc/cust_pay-import.cgi
index 05a6c4f..ee0154d 100644
--- a/httemplate/misc/cust_pay-import.cgi
+++ b/httemplate/misc/cust_pay-import.cgi
@@ -1,22 +1,31 @@
 <& /elements/header.html, 'Batch Payment Import' &>
 
-Import a CSV file containing customer payments.
+Import a file containing customer payments.
 <BR><BR>
 
-<FORM ACTION="process/cust_pay-import.cgi" METHOD="post" ENCTYPE="multipart/form-data">
+
+<% include( '/elements/form-file_upload.html',
+     'name'      => 'OneTrueForm',
+     'action'    => 'process/cust_pay-import.cgi', #progress-init target
+     'fields'    => [ 'agentnum', '_date', 'paybatch', 'format', 'payby' ],
+     'num_files' => 1,
+     'url' => popurl(2)."search/cust_pay.html?magic=paybatch;paybatch=$paybatch",
+     'message' => 'Batch Payment Imported',
+   )
+%>
 
 <% &ntable("#cccccc", 2) %>
 
+<INPUT TYPE="hidden" NAME="paybatch" VALUE="<% $paybatch | h %>">
+
 <& /elements/tr-select-agent.html,
-     #'curr_value' => '', #$agentnum,
      'label'       => "<B>Agent</B>",
      'empty_label' => 'Select agent',
 &>
 
 <& /elements/tr-input-date-field.html, {
      'name'  => '_date',
-     #'value' => '',
-     'label' => 'Date',
+     'label' => '<B>Date</B>',
    }
 &>
 
@@ -24,18 +33,26 @@ Import a CSV file containing customer payments.
   <TH ALIGN="right">Format</TH>
   <TD>
     <SELECT NAME="format">
-      <OPTION VALUE="simple">Simple
-<!--      <OPTION VALUE="extended" SELECTED>Extended -->
+      <OPTION VALUE="simple-csv">Comma-separated (.csv)</OPTION>
+      <OPTION VALUE="simple-xls">Excel (.xls)</OPTION>
     </SELECT>
   </TD>
 </TR>
 
-<TR>
-  <TH ALIGN="right">CSV filename</TH>
-  <TD><INPUT TYPE="file" NAME="csvfile"></TD>
-</TR>
+<% include( '/elements/tr-select-payby.html',
+     'paybys' => \%paybys,
+     'no_all' => 1,
+     'label'  => '<B>Payment type</B>',
+   )
+%>
+
+<% include( '/elements/file-upload.html',
+             'field'    => 'file',
+             'label'    => 'Filename',
+   )
+%>
 
-<TR><TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"><INPUT TYPE="submit" VALUE="Import CSV file"></TD></TR>
+<TR><TD COLSPAN=2 ALIGN="center" STYLE="padding-top:6px"><INPUT TYPE="submit" VALUE="Import file"></TD></TR>
 
 </TABLE>
 
@@ -43,11 +60,10 @@ Import a CSV file containing customer payments.
 
 <BR>
 
-Simple file format is CSV, with the following field order: <i>custnum, agent_custid, amount, checknum</i>
+Simple file format is CSV or XLS, with the following field order: <i>custnum, agent_custid, amount, checknum, invnum</i>
 <BR><BR>
 
-<!-- Extended file format is not yet defined</i>
-<BR><BR> -->
+<!-- Extended file format is not yet defined -->
 
 Field information:
 
@@ -68,3 +84,9 @@ Field information:
 <BR>
 
 <& /elements/footer.html &>
+
+<%init>
+my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+my %paybys;
+tie %paybys, 'Tie::IxHash', FS::payby->payment_payby2longname();
+</%init>
diff --git a/httemplate/misc/process/cust_pay-import.cgi b/httemplate/misc/process/cust_pay-import.cgi
index 7711773..2981da4 100644
--- a/httemplate/misc/process/cust_pay-import.cgi
+++ b/httemplate/misc/process/cust_pay-import.cgi
@@ -1,23 +1,5 @@
-<% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %> 
+<% $server->process %>
 <%init>
-
-my $fh = $cgi->upload('csvfile');
-
-# webbatch?  I suppose
-my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
-
-my $error = defined($fh)
-  ? FS::cust_pay::batch_import( {
-      'filehandle' => $fh,
-      'format'     => scalar($cgi->param('format')),
-
-      'agentnum'   => scalar($cgi->param('agentnum')),
-      '_date'      => scalar($cgi->param('_date')),
-      'paybatch'   => $paybatch,
-    } )
-  : 'No file';
-
-errorpage($error)
-  if ( $error );
-
+my $server = new FS::UI::Web::JSRPC 'FS::cust_pay::process_batch_import', $cgi; 
 </%init>
+

-----------------------------------------------------------------------

Summary of changes:
 FS/FS/Record.pm                             |   14 +++-
 FS/FS/cust_pay.pm                           |   96 +++++++++++++++++++++++++++
 FS/FS/payby.pm                              |   22 ++++++
 httemplate/elements/select-payby.html       |    2 +-
 httemplate/misc/cust_pay-import.cgi         |   52 ++++++++++-----
 httemplate/misc/process/cust_pay-import.cgi |   24 +------
 6 files changed, 172 insertions(+), 38 deletions(-)




More information about the freeside-commits mailing list