[freeside-commits] branch master updated. 43e6b2ea06434abead940939c9b04e00e3a72f32

Mark Wells mark at 420.am
Mon Apr 23 20:30:57 PDT 2012


The branch, master has been updated
       via  43e6b2ea06434abead940939c9b04e00e3a72f32 (commit)
      from  aa028b1f28a468df2a3d3a5ff03a856bba2c1254 (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 43e6b2ea06434abead940939c9b04e00e3a72f32
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Apr 23 20:27:17 2012 -0700

    manual control of quick payment application, #15861

diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm
index ef30809..e15bf01 100644
--- a/FS/FS/cust_pay.pm
+++ b/FS/FS/cust_pay.pm
@@ -760,6 +760,12 @@ objects.  Returns a list, each element representing the status of inserting the
 corresponding payment - empty.  If there is an error inserting any payment, the
 entire transaction is rolled back, i.e. all payments are inserted or none are.
 
+FS::cust_pay objects may have the pseudo-field 'apply_to', containing a 
+reference to an array of (uninserted) FS::cust_bill_pay objects.  If so,
+those objects will be inserted with the paynum of the payment, and for 
+each one, an error message or an empty string will be inserted into the 
+list of errors.
+
 For example:
 
   my @errors = FS::cust_pay->batch_insert(@cust_pay);
@@ -786,19 +792,35 @@ sub batch_insert {
   local $FS::UID::AutoCommit = 0;
   my $dbh = dbh;
 
-  my $errors = 0;
+  my $num_errors = 0;
   
-  my @errors = map {
-    my $error = $_->insert( 'manual' => 1 );
-    if ( $error ) { 
-      $errors++;
-    } else {
-      $_->cust_main->apply_payments;
+  my @errors;
+  foreach my $cust_pay (@_) {
+    my $error = $cust_pay->insert( 'manual' => 1 );
+    push @errors, $error;
+    $num_errors++ if $error;
+
+    if ( ref($cust_pay->get('apply_to')) eq 'ARRAY' ) {
+
+      foreach my $cust_bill_pay ( @{ $cust_pay->apply_to } ) {
+        if ( $error ) { # insert placeholders if cust_pay wasn't inserted
+          push @errors, '';
+        }
+        else {
+          $cust_bill_pay->set('paynum', $cust_pay->paynum);
+          my $apply_error = $cust_bill_pay->insert;
+          push @errors, $apply_error || '';
+          $num_errors++ if $apply_error;
+        }
+      }
+
+    } elsif ( !$error ) { #normal case: apply payments as usual
+      $cust_pay->cust_main->apply_payments;
     }
-    $error;
-  } @_;
 
-  if ( $errors ) {
+  }
+
+  if ( $num_errors ) {
     $dbh->rollback if $oldAutoCommit;
   } else {
     $dbh->commit or die $dbh->errstr if $oldAutoCommit;
diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html
index a517ece..b49bf02 100644
--- a/httemplate/elements/customer-table.html
+++ b/httemplate/elements/customer-table.html
@@ -356,7 +356,7 @@ Example:
 % my $row = 0;
 % for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { 
 
-    <TR>
+    <TR id="row<%$row%>" rownum="<%$row%>">
       <TD>
 	<INPUT TYPE      = "text"
                NAME      = "invnum<% $row %>"
@@ -458,19 +458,24 @@ Example:
 %     my $color = $opt{color}->[$col];
 %     my $font = $color ? qq(<FONT COLOR="$color">) : '';
 %     my $onchange = '';
-%     if ( $opt{footer}->[$col] eq '_TOTAL' ) {
+%     if ( $opt{onchange}->[$col] ) {
+%       $onchange = 'onchange="'.$opt{onchange}->[$col].'"';
+%     }
+%     elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
 %       $total[$col] += $value;
 %       $onchange = $opt{prefix}. "calc_total$col();";
 %       $onchange = qq(onchange="$onchange" onkeyup="$onchange");
 %     }
       <TD ALIGN="<% $align %>">
-%     if (! $types->[$col] || $types->[$col] eq 'text') {
-        <INPUT TYPE  = "text"
+%     my $type = $types->[$col] || 'text';
+%     if ($type eq 'text' or $type eq 'checkbox') {
+        <INPUT TYPE  = "<% $type %>"
                NAME  = "<% $name %>"
                ID    = "<% $name %>"
                SIZE  = "<% $size %>"
                STYLE = "text-align: <% $align %>;"
                VALUE = "<% $value %>"
+               rownum    = "<% $row %>"
                <% $onchange %>
         >
 %     } elsif ($types->[$col] eq 'immutable') {
@@ -485,7 +490,7 @@ Example:
     </TR>
 % } 
 
-<TR>
+<TR id="row_total">
   <TH COLSPAN=5 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL">
     Total <% $row ? $row-1 : 0 %>
     <% PL($opt{name_singular} || 'customer', ( $row ? $row-1 : 0 ) ) %>
@@ -559,7 +564,8 @@ Example:
     var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
     var tablebody = table.getElementsByTagName('tbody').item(0);
 
-    var row = table.insertRow(rownum+1);
+    var row = table.insertRow(table.rows.length - 1);
+    row.setAttribute('id', 'row'+rownum);
     
     var invnum_cell = document.createElement('TD');
 
@@ -676,7 +682,7 @@ Example:
 %       } else {
 %         $value = $param->{"$field$row"}; 
 %       }
-        var my_text = document.createTextNode('<% $value %>');
+        var my_text = document.createTextNode(<% $value |js_string %>);
         my_cell.appendChild(my_text);
 %     }
 
@@ -686,10 +692,17 @@ Example:
       my_input.setAttribute('id',   '<% $name %>'+<% $opt{prefix} %>rownum);
       my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
       my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
-%     if ($types->[$col] eq 'immutable') {
+      my_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+%     if ( $types->[$col] eq 'immutable' ) {
         my_input.setAttribute('type', 'hidden');
 %     }
-%     if ( $opt{footer}->[$col] eq '_TOTAL' ) {
+%     elsif ( $types->[$col] eq 'checkbox' ) {
+        my_input.setAttribute('type', 'checkbox');
+%     }
+%     if ( $opt{onchange}->[$col] ) {
+        my_input.onchange   = <% $opt{onchange}->[$col] %>;
+%     }
+%     elsif ( $opt{footer}->[$col] eq '_TOTAL' ) {
         my_input.onchange   = <% $opt{prefix} %>calc_total<%$col%>;
         my_input.onkeyup    = <% $opt{prefix} %>calc_total<%$col%>;
 %     }
@@ -713,6 +726,11 @@ Example:
           + ' <% PL($opt{name_singular} || 'customer') %>';
     }
 
+% if ( $opt{add_row_callback} ) {
+    <% $opt{add_row_callback} %>(<% $opt{prefix} %>rownum,
+                                 '<% $opt{prefix} %>');
+% }
+
     <% $opt{prefix} %>rownum++;
 
   }
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index 2e79865..45459f1 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -1,6 +1,9 @@
-<% include('/elements/header.html', 'Quick payment entry') %>
+<& /elements/header.html, {
+  title => 'Quick payment entry',
+  etc   => 'onload="preload()"'
+} &>
 
-<% include('/elements/error.html') %>
+<& /elements/error.html &>
 
 <SCRIPT TYPE="text/javascript">
 function warnUnload() {
@@ -14,6 +17,18 @@ function warnUnload() {
 }
 window.onbeforeunload = warnUnload;
 
+function add_row_callback(rownum, prefix) {
+  document.getElementById('enable_app'+rownum).disabled = true;
+}
+
+function custnum_update_callback(rownum, prefix) {
+  var custnum = document.getElementById('custnum'+rownum).value;
+  document.getElementById('enable_app'+rownum).disabled = (custnum == 0);
+% if ( $use_discounts ) {
+  select_discount_term(rownum, prefix);
+% }
+}
+
 function select_discount_term(row, prefix) {
   var custnum_obj = document.getElementById('custnum'+prefix+row);
   var select_obj = document.getElementById('discount_term'+prefix+row);
@@ -46,6 +61,265 @@ function select_discount_term(row, prefix) {
   discount_terms(custnum_obj.value, select_discount_term_update);
 
 }
+
+var invoices_for_row = new Object;
+
+function update_invoices(rownum, invoices) {
+  invoices_for_row[rownum] = new Object;
+  // only called before create_application_row
+  for ( var i=0; i<invoices.length; i++ ) {
+    invoices_for_row[rownum][ invoices[i].invnum ] = invoices[i];
+  }
+}
+
+function toggle_application_row(ev, next) {
+  if (!next) next = function(){}; //optional continuation
+  var rownum = this.getAttribute('rownum');
+  if ( this.checked ) {
+    var custnum = document.getElementById('custnum'+rownum).value;
+    if (!custnum) return;
+    lock_payment_row(rownum, true);
+    custnum_search_open( custnum, 
+      function(returned) {
+        update_invoices(rownum, JSON.parse(returned));
+        create_application_row(rownum, 0);
+        next.call(this, rownum);
+      }
+    );
+  }
+}
+
+function lock_payment_row(rownum, flag) {
+% foreach (qw(invnum custnum customer)) {
+  obj = document.getElementById('<% $_ %>'+rownum);
+  obj.readOnly = flag;
+% }
+  document.getElementById('enable_app'+rownum).disabled = flag;
+}
+
+function delete_application_row() {
+  var rownum = this.getAttribute('rownum');
+  var appnum = this.getAttribute('appnum');
+  var tr_app = document.getElementById('row'+rownum+'.'+appnum);
+  var select_invnum = document.getElementById('invnum'+rownum+'.'+appnum);
+  if ( select_invnum.value ) {
+    invoices_for_row[rownum][ select_invnum.value ] = select_invnum.curr_invoice;
+  }
+    
+  tr_app.parentNode.removeChild(tr_app);
+  if ( appnum > 0 ) {
+    document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = '';
+  }
+  else {
+    lock_payment_row(rownum, false);
+    document.getElementById('enable_app'+rownum).checked = false;
+  }
+}
+
+function amount_unapplied(rownum) {
+  var appnum = 0;
+  var total = 0;
+  var payment_amount = parseFloat(document.getElementById('paid'+rownum).value)
+                       || 0;
+  while (true) {
+    var input_amount = document.getElementById('amount'+rownum+'.'+appnum);
+    if ( input_amount ) {
+      total += parseFloat(input_amount.value || 0);
+      appnum++;
+    }
+    else {
+      return payment_amount - total;
+    }
+  }
+}
+
+var change_app_amount;
+
+function choose_app_invnum() {
+  var rownum = this.getAttribute('rownum');
+  var appnum = this.getAttribute('appnum');
+  var last_invoice = this.curr_invoice;
+  if ( last_invoice ) {
+    invoices_for_row[rownum][ last_invoice['invnum'] ] = last_invoice;
+  }
+
+  if ( this.value ) {
+    var this_invoice = invoices_for_row[rownum][this.value];
+    this.curr_invoice = invoices_for_row[rownum][this.value];
+    var span_owed = document.getElementById('owed'+rownum+'.'+appnum);
+    span_owed.innerHTML = this_invoice['owed'] + ' ';
+    delete invoices_for_row[rownum][this.value];
+
+    var input_amount = document.getElementById('amount'+rownum+'.'+appnum);
+    if ( input_amount.value == '' ) {
+      input_amount.value = 
+        Math.max(
+          0, Math.min( amount_unapplied(rownum), this_invoice['owed'])
+        ).toFixed(2);
+      // trigger onchange
+      change_app_amount.call(input_amount);
+    }
+  }
+}
+
+function focus_app_invnum() {
+% # invoice numbers just display as invoice numbers
+  var rownum = this.getAttribute('rownum');
+  var add_opt = function(obj, value) {
+    var o = document.createElement('OPTION');
+    o.text = value;
+    o.value = value;
+    obj.add(o);
+  }
+  this.options.length = 0;
+  var this_invoice = this.curr_invoice;
+  if ( this_invoice ) {
+    add_opt(this, this_invoice.invnum);
+  } else {
+    add_opt(this, '');
+  }
+  for ( var x in invoices_for_row[rownum] ) {
+    add_opt(this, invoices_for_row[rownum][x].invnum);
+  }
+}
+
+function change_app_amount() {
+  var rownum = this.getAttribute('rownum');
+  var appnum = this.getAttribute('appnum');
+%# maybe some kind of warning if amount_unapplied < 0?
+%# only spawn a new application row if there are open invoices left,
+%# and this is the highest-numbered application row for the customer,
+%# and the sum of the applied amounts is < the amount of the payment,
+  if ( Object.keys(invoices_for_row[rownum]).length > 0
+       && !document.getElementById( 'row'+rownum+'.'+(parseInt(appnum) + 1) )
+       && amount_unapplied(rownum) > 0 ) {
+
+    create_application_row(rownum, parseInt(appnum) + 1);
+
+  }
+}
+
+function create_application_row(rownum, appnum) {
+  var payment_row = document.getElementById('row'+rownum);
+  var tr_app = document.createElement('TR');
+  tr_app.setAttribute('rownum', rownum);
+  tr_app.setAttribute('appnum', appnum);
+  tr_app.setAttribute('id', 'row'+rownum+'.'+appnum);
+  
+  var td_invnum = document.createElement('TD');
+  td_invnum.setAttribute('colspan', 4);
+  td_invnum.style.textAlign = 'right';
+  td_invnum.appendChild(
+    document.createTextNode('<% mt('Apply to Invoice ') %>')
+  );
+  var select_invnum = document.createElement('SELECT');
+  select_invnum.setAttribute('rownum', rownum);
+  select_invnum.setAttribute('appnum', appnum);
+  select_invnum.setAttribute('id', 'invnum'+rownum+'.'+appnum);
+  select_invnum.setAttribute('name', 'invnum'+rownum+'.'+appnum);
+  select_invnum.style.textAlign = 'right';
+  select_invnum.style.width = '50px';
+  select_invnum.onchange = choose_app_invnum;
+  select_invnum.onfocus  = focus_app_invnum;
+  
+  td_invnum.appendChild(select_invnum);
+  tr_app.appendChild(td_invnum);
+
+  var td_owed = document.createElement('TD');
+  td_owed.style.textAlign= 'right';
+  var span_owed = document.createElement('SPAN');
+  span_owed.setAttribute('rownum', rownum);
+  span_owed.setAttribute('appnum', appnum);
+  span_owed.setAttribute('id', 'owed'+rownum+'.'+appnum);
+  td_owed.appendChild(span_owed);
+  tr_app.appendChild(td_owed);
+
+  var td_amount = document.createElement('TD');
+  td_amount.style.textAlign = 'right';
+  var input_amount = document.createElement('INPUT');
+  input_amount.size = 6;
+  input_amount.setAttribute('rownum', rownum);
+  input_amount.setAttribute('appnum', appnum);
+  input_amount.setAttribute('name', 'amount'+rownum+'.'+appnum);
+  input_amount.setAttribute('id', 'amount'+rownum+'.'+appnum);
+  input_amount.style.textAlign = 'right';
+  input_amount.onchange = change_app_amount;
+  td_amount.appendChild(input_amount);
+  tr_app.appendChild(td_amount);
+
+  var td_delete = document.createElement('TD');
+  td_delete.setAttribute('colspan', <% scalar(@fields)-2 %>);
+  var button_delete = document.createElement('INPUT');
+  button_delete.setAttribute('rownum', rownum);
+  button_delete.setAttribute('appnum', appnum);
+  button_delete.setAttribute('id', 'delete'+rownum+'.'+appnum);
+  button_delete.setAttribute('type', 'button');
+  button_delete.setAttribute('value', 'X');
+  button_delete.onclick = delete_application_row;
+  button_delete.style.color = '#ff0000';
+  button_delete.style.fontWeight = 'bold';
+  button_delete.style.paddingLeft = '2px';
+  button_delete.style.paddingRight = '2px';
+  td_delete.appendChild(button_delete);
+  tr_app.appendChild(td_delete);
+
+  var td_error = document.createElement('TD');
+  var span_error = document.createElement('SPAN');
+  span_error.setAttribute('rownum', rownum);
+  span_error.setAttribute('appnum', appnum);
+  span_error.setAttribute('id', 'error'+rownum+'.'+appnum);
+  span_error.style.color = '#ff0000';
+  td_error.appendChild(span_error);
+  tr_app.appendChild(td_error);
+
+  if ( appnum > 0 ) {
+    //remove delete button on the previous row
+    document.getElementById('delete'+rownum+'.'+(appnum-1)).style.display = 'none';
+  }
+  rownum++;
+  var next_row = document.getElementById('row'+rownum); // always exists
+  payment_row.parentNode.insertBefore(tr_app, next_row);
+
+}
+
+%# for error handling--ugly, but the alternative is translating the whole 
+%# process of creating rows into Mason
+var row_array = <% encode_json(\@rows) %>;
+function preload() {
+  var rownum;
+  var appnum;
+  for (rownum=0; rownum < row_array.length; rownum++) {
+    if ( row_array[rownum].length ) {
+      var enable = document.getElementById('enable_app'+rownum);
+      enable.checked = true;
+      var preload_row = function(r) {//continuation from toggle_application_row
+        for (appnum=0; appnum < row_array[r].length; appnum++) {
+          this_app = row_array[r][appnum];
+          var x = r + '.' + appnum;
+          //set invnum
+          var select_invnum = document.getElementById('invnum'+x);
+          focus_app_invnum.call(select_invnum);
+          for (i=0; i<select_invnum.options.length; i++) {
+            if (select_invnum.options[i].value == this_app.invnum) {
+              select_invnum.selectedIndex = i;
+            }
+          }
+          choose_app_invnum.call(select_invnum);
+          //set amount
+          var input_amount = document.getElementById('amount'+x);
+          input_amount.value = this_app.amount;
+
+          //set error
+          var span_error = document.getElementById('error'+x);
+          span_error.innerHTML = this_app.error;
+          change_app_amount.call(input_amount); //creates next row
+        } //for appnum
+      }; //preload_row function
+      toggle_application_row.call(enable, null, preload_row);
+    } // if row_array[rownum].length
+  } //for rownum
+}
+
 </SCRIPT>
 
 <% include('/elements/xmlhttp.html',
@@ -57,21 +331,26 @@ function select_discount_term(row, prefix) {
 <FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.btnsubmit.disabled=true;window.onbeforeunload = null;">
 
 <!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> -->
+<& /elements/xmlhttp.html,
+    url => $p.'misc/xmlhttp-cust_bill-search.html',
+    subs => ['custnum_search_open']
+&>
 
-<% include( "/elements/customer-table.html",
-              name_singular => 'payment',
-              header  => \@header,
-              fields  => \@fields,
-              type    => \@types,
-              align   => \@align,
-              size    => \@sizes,
-              color   => \@colors,
-              param   => \%param,
-              footer  => \@footer,
-              footer_align => \@footer_align,
-              custnum_update_callback => $custnum_update_callback,
-          )
-%>
+<& /elements/customer-table.html,
+    name_singular => 'payment',
+    header  => \@header,
+    fields  => \@fields,
+    type    => \@types,
+    align   => \@align,
+    size    => \@sizes,
+    color   => \@colors,
+    param   => \%param,
+    footer  => \@footer,
+    footer_align => \@footer_align,
+    onchange => \@onchange,
+    custnum_update_callback => 'custnum_update_callback',
+    add_row_callback => 'add_row_callback',
+&>
 
 <BR>
 <INPUT TYPE="button" VALUE="Post payment batch" name="btnsubmit" onclick="window.onbeforeunload = null; document.OneTrueForm.submit(); this.disabled = true;">
@@ -105,7 +384,8 @@ my @colors  = ( '', '' );
 my %param   = ();
 my @footer  = ( '_TOTAL', '' );
 my @footer_align = ( 'r', 'r' );
-my $custnum_update_callback = '';
+my @onchange = ( '', '' );;
+my $use_discounts = '';
 
 if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
   #push @header, 'Discount';
@@ -117,9 +397,20 @@ if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
   push @colors, '';
   push @footer, '';
   push @footer_align, '';
-  $custnum_update_callback = 'select_discount_term';
+  push @onchange, '';
+  $use_discounts = 'Y';
 }
 
+push @header, 'Allocate';
+push @fields, 'enable_app';
+push @types, 'checkbox';
+push @align, 'c';
+push @sizes, '0';
+push @colors, '';
+push @footer, '';
+push @footer_align, '';
+push @onchange, 'toggle_application_row';
+
 #push @header, 'Error';
 push @header, '';
 push @fields, 'error';
@@ -129,7 +420,34 @@ push @sizes, '0';
 push @colors, '#ff0000';
 push @footer, '';
 push @footer_align, '';
+push @onchange, '';
 
 $m->comp('/elements/handle_uri_query');
 
+# set up for preloading
+my @rows;
+my @row_errors;
+if ( $cgi->param('error') ) {
+  my $param = $cgi->Vars;
+  my $enum = 0; #errors numbered separately
+  for( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+    $rows[$row] = [];
+    $row_errors[$row] = $param->{"error$enum"};
+    $enum++;
+    for( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+      next if !$param->{"invnum$row.$app"};
+      my %this_app = map { $_ => ($param->{$_.$row.'.'.$app} || '') } 
+        qw( invnum amount );
+      $this_app{'error'} = $param->{"error$enum"} || '';
+      $param->{"error$enum"} = ''; # don't pass this error through
+      $rows[$row][$app] = \%this_app;
+      $enum++;
+    }
+  }
+  for( my $row = 0; $row < @row_errors; $row++ ) {
+    $param->{"error$row"} = $row_errors[$row];
+  }
+}
+#warn Dumper {rows => \@rows, row_errors => \@row_errors };
+
 </%init>
diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi
index a6b90ea..3b06f3a 100644
--- a/httemplate/misc/process/batch-cust_pay.cgi
+++ b/httemplate/misc/process/batch-cust_pay.cgi
@@ -1,51 +1,69 @@
-%  die "access denied"
-%    unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
-%
-%  my $param = $cgi->Vars;
-%
-%  #my $paybatch = $param->{'paybatch'};
-%  my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
-%
-%  my @cust_pay = ();
-%  #my $row = 0;
-%  #while ( exists($param->{"custnum$row"}) ) {
-%  for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
-%    my $custnum = $param->{"custnum$row"};
-%    my $cust_main;
-%    if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
-%      $cust_main = qsearchs({ 
-%        'table'     => 'cust_main',
-%        'hashref'   => { 'custnum' => $1 },
-%        'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-%      });
-%    }
-%    if ( length($custnum) and !$cust_main ) { # not found, try agent_custid
-%      $cust_main = qsearchs({ 
-%        'table'     => 'cust_main',
-%        'hashref'   => { 'agent_custid' => $custnum },
-%        'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
-%      });
-%    }
-%    $custnum = $cust_main->custnum if $cust_main;
-%    # if !$cust_main, then this will throw an error on batch_insert
-%
-%    push @cust_pay, new FS::cust_pay {
-%                      'custnum'        => $custnum,
-%                      'paid'           => $param->{"paid$row"},
-%                      'payby'          => 'BILL',
-%                      'payinfo'        => $param->{"payinfo$row"},
-%                      'discount_term'  => $param->{"discount_term$row"},
-%                      'paybatch'       => $paybatch,
-%                    }
-%      if    $param->{"custnum$row"}
-%         || $param->{"paid$row"}
-%         || $param->{"payinfo$row"};
-%    #$row++;
-%  }
-%
-%  my @errors = FS::cust_pay->batch_insert(@cust_pay);
-%  my $num_errors = scalar(grep $_, @errors);
-%
+<%init>
+my $DEBUG = 1;
+die "access denied"
+  unless $FS::CurrentUser::CurrentUser->access_right('Post payment batch');
+
+my $param = $cgi->Vars;
+warn Dumper($param) if $DEBUG;
+
+#my $paybatch = $param->{'paybatch'};
+my $paybatch = time2str('webbatch-%Y/%m/%d-%T'. "-$$-". rand() * 2**32, time);
+
+my @cust_pay = ();
+#my $row = 0;
+#while ( exists($param->{"custnum$row"}) ) {
+for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
+  my $custnum = $param->{"custnum$row"};
+  my $cust_main;
+  if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
+    $cust_main = qsearchs({ 
+      'table'     => 'cust_main',
+      'hashref'   => { 'custnum' => $1 },
+      'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+    });
+  }
+  if ( length($custnum) and !$cust_main ) { # not found, try agent_custid
+    $cust_main = qsearchs({ 
+      'table'     => 'cust_main',
+      'hashref'   => { 'agent_custid' => $custnum },
+      'extra_sql' => ' AND '. $FS::CurrentUser::CurrentUser->agentnums_sql,
+    });
+  }
+  $custnum = $cust_main->custnum if $cust_main;
+  # if !$cust_main, then this will throw an error on batch_insert
+
+  my $cust_pay = new FS::cust_pay {
+                    'custnum'        => $custnum,
+                    'paid'           => $param->{"paid$row"},
+                    'payby'          => 'BILL',
+                    'payinfo'        => $param->{"payinfo$row"},
+                    'discount_term'  => $param->{"discount_term$row"},
+                    'paybatch'       => $paybatch,
+                  }
+    if    $param->{"custnum$row"}
+       || $param->{"paid$row"}
+       || $param->{"payinfo$row"};
+  next if !$cust_pay;
+  #$row++;
+
+  # payment applications, if any
+  my @cust_bill_pay = ();
+  for ( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+    next if !$param->{"invnum$row.$app"};
+    push @cust_bill_pay, new FS::cust_bill_pay {
+                            'invnum'  => $param->{"invnum$row.$app"},
+                            'amount'  => $param->{"amount$row.$app"}
+                          };
+  }
+  $cust_pay->set('apply_to', \@cust_bill_pay) if scalar(@cust_bill_pay) > 0;
+
+  push @cust_pay, $cust_pay;
+
+}
+
+my @errors = FS::cust_pay->batch_insert(@cust_pay);
+my $num_errors = scalar(grep $_, @errors);
+</%init>
 %  if ( $num_errors ) {
 %
 %    $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : '').
@@ -65,4 +83,3 @@
 %    
 <% $cgi->redirect(popurl(3). "search/cust_pay.html?magic=paybatch;paybatch=$paybatch") %>
 % } 
-
diff --git a/httemplate/misc/xmlhttp-cust_bill-search.html b/httemplate/misc/xmlhttp-cust_bill-search.html
new file mode 100644
index 0000000..46f15d1
--- /dev/null
+++ b/httemplate/misc/xmlhttp-cust_bill-search.html
@@ -0,0 +1,18 @@
+<% encode_json(\@return) %>
+<%init>
+
+my $curuser = $FS::CurrentUser::CurrentUser;
+die 'access denied' unless $curuser->access_right('View invoices');
+my @return;
+if ( $cgi->param('sub') eq 'custnum_search_open' ) { 
+  my $custnum = $cgi->param('arg');
+  #warn "searching invoices for $custnum\n";
+  my $cust_main = FS::cust_main->by_key($custnum);
+  @return = map { 
+    +{ $_->hash, 
+      'owed' => $_->owed }
+  } $cust_main->open_cust_bill
+    if $curuser->agentnums_href->{ $cust_main->agentnum };
+}
+
+</%init>

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

Summary of changes:
 FS/FS/cust_pay.pm                             |   42 +++-
 httemplate/elements/customer-table.html       |   36 ++-
 httemplate/misc/batch-cust_pay.html           |  354 +++++++++++++++++++++++--
 httemplate/misc/process/batch-cust_pay.cgi    |  115 +++++----
 httemplate/misc/xmlhttp-cust_bill-search.html |   18 ++
 5 files changed, 479 insertions(+), 86 deletions(-)
 create mode 100644 httemplate/misc/xmlhttp-cust_bill-search.html




More information about the freeside-commits mailing list