[freeside-commits] branch FREESIDE_4_BRANCH updated. 2ebbe972c9c8b14938c34b044fdf2635c4496963

Jonathan Prykop jonathan at 420.am
Tue Jun 16 16:38:17 PDT 2015


The branch, FREESIDE_4_BRANCH has been updated
       via  2ebbe972c9c8b14938c34b044fdf2635c4496963 (commit)
       via  d7415830b5b3bb6603ffd43beb26655baad95170 (commit)
       via  2e63285d3f95c11e879181e9bf972499cfa0c3e8 (commit)
      from  ab8058373c2cde0077a90743e8cf16c5d4c9a3fc (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 2ebbe972c9c8b14938c34b044fdf2635c4496963
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Tue Jun 16 01:25:51 2015 -0500

    RT#33484: Delete lines from quick payment entry [removed prefix, documented, added color handling]

diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html
index 090623f..76a7f12 100644
--- a/httemplate/elements/customer-table.html
+++ b/httemplate/elements/customer-table.html
@@ -37,6 +37,34 @@ Example:
 
          )
 
+Some incomplete notes for javascript programmers:
+
+On page load, existing rows are initialized by passing values to addRow
+based on existing cgi values.  An empty row (marked with the 'emptyrow' 
+attribute) is created by invoking addRow without values.  After that, 
+to keep the non-empty row count (totalrows) accurate, use newEmptyRow to 
+create the next row.  There should only be one empty row at a time.
+
+Global vars:
+total_el  - element for displaying total number of rows
+totalrows - total number of non-empty rows
+rownum - really more of a "next row" value, used by addRow
+allrows - array of tr elements, one for each row
+
+Don't confuse the global rownum with the element attribute rownum
+that is set as a reference point on some of the elements generated
+by this script.  They have different values.
+
+Some of the functions:
+updateTotalRow() - updates total_el based on value of totalrows
+addDeleteButton(searchrow) - adds delete button to searchrow
+newEmptyRow() - replaces old empty row
+deleteRow() - removes the row specified by this.rownum
+addRow(values) - adds a new row (marked as empty if values aren't specified)
+
+This mason element is currently only used by misc/batch-cust_pay.html, 
+and probably should be cleaned up more before being used by anything else.
+
 </%doc>
 
 <SCRIPT TYPE="text/javascript">
@@ -112,7 +140,7 @@ Example:
       }
   }
 
-  function <% $opt{prefix} %>search_invnum() {
+  function search_invnum() {
 
     this.style.color = '#000000'
 
@@ -129,7 +157,7 @@ Example:
     }
 
     if ( document.getElementById('row'+searchrow).emptyrow ) {
-      <% $opt{prefix} %>newEmptyRow(searchrow);
+      newEmptyRow(searchrow);
     }
     var customer = document.getElementById('customer'+searchrow);
     customer.value = 'searching...';
@@ -153,7 +181,7 @@ Example:
       update_customer(searchrow, customerArray);
 
 % if ( $opt{invnum_update_callback} ) {
-        <% $opt{invnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
+        <% $opt{invnum_update_callback} %>(searchrow)
 % }
 
     }
@@ -162,7 +190,7 @@ Example:
 
   }
 
-  function <% $opt{prefix} %>search_custnum() {
+  function search_custnum() {
 
     this.style.color = '#000000'
 
@@ -180,7 +208,7 @@ Example:
     }
 
     if ( document.getElementById('row'+searchrow).emptyrow ) {
-      <% $opt{prefix} %>newEmptyRow(searchrow);
+      newEmptyRow(searchrow);
     }
 
     var customer_obj = document.getElementById('customer'+searchrow);
@@ -214,7 +242,7 @@ Example:
 
         update_customer(searchrow, customerArrayArray[0]);
 % if ( $opt{custnum_update_callback} ) {
-          <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
+          <% $opt{custnum_update_callback} %>(searchrow)
 % }
 
       } else {
@@ -248,7 +276,7 @@ Example:
 
   }
 
-  function <% $opt{prefix} %>search_customer() {
+  function search_customer() {
 
     var customer_obj = this;
     var searchrow = this.getAttribute('rownum');
@@ -263,7 +291,7 @@ Example:
     }
 
     if ( document.getElementById('row'+searchrow).emptyrow ) {
-      <% $opt{prefix} %>newEmptyRow(searchrow);
+      newEmptyRow(searchrow);
     }
     
     var invnum = document.getElementById('invnum'+searchrow);
@@ -292,7 +320,7 @@ Example:
 
         update_customer(searchrow, customerArrayArray[0]);
 % if ( $opt{custnum_update_callback} ) {
-        <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
+        <% $opt{custnum_update_callback} %>(searchrow)
 % }
 
       } else {
@@ -355,7 +383,7 @@ Example:
       update_customer(searchrow, JSON.parse(custnum_balance_status));
 
 % if ( $opt{custnum_update_callback} ) {
-      <% $opt{custnum_update_callback} %>(searchrow, '<% $opt{prefix} %>')
+      <% $opt{custnum_update_callback} %>(searchrow)
 % }
 
     }
@@ -389,29 +417,30 @@ Example:
     num_open_invoices[rownum] = newval;
   }
 
-  function <% $opt{prefix} %>updateTotalRow () {
-    if ( <% $opt{prefix} %>totalrows == 1 ) {
-      <% $opt{prefix} %>total_el.innerHTML =
+  // updates display of total rows based on value of totalrows
+  function updateTotalRow () {
+    if ( totalrows == 1 ) {
+      total_el.innerHTML =
         'Total '
-          + <% $opt{prefix} %>totalrows
+          + totalrows
           + ' <% $opt{name_singular} || 'customer' %>';
     } else {
-      <% $opt{prefix} %>total_el.innerHTML =
+      total_el.innerHTML =
         'Total '
-          + <% $opt{prefix} %>totalrows
+          + totalrows
           + ' <% PL($opt{name_singular} || 'customer') %>';
     }
   }
 
-  var <% $opt{prefix} %>total_el, <% $opt{prefix} %>rownum, <% $opt{prefix} %>totalrows, <% $opt{prefix} %>allrows;
+  var total_el, rownum, totalrows, allrows;
 
-  function <% $opt{prefix} %>addDeleteButton (searchrow) {
+  function addDeleteButton (searchrow) {
     var td_delete = document.getElementById('delete'+searchrow);
     var button_delete = document.createElement('INPUT');
     button_delete.setAttribute('rownum', searchrow);
     button_delete.setAttribute('type', 'button');
     button_delete.setAttribute('value', 'X');
-    button_delete.onclick = <% $opt{prefix} %>deleteRow;
+    button_delete.onclick = deleteRow;
     button_delete.style.color = '#ff0000';
     button_delete.style.fontWeight = 'bold';
     button_delete.style.paddingLeft = '2px';
@@ -419,24 +448,24 @@ Example:
     td_delete.appendChild(button_delete);
   }
 
-  function <% $opt{prefix} %>newEmptyRow (searchrow) {
+  function newEmptyRow (searchrow) {
     // add delete button to current row
-    <% $opt{prefix} %>addDeleteButton(searchrow);
+    addDeleteButton(searchrow);
     // mark current row as non-empty
     var oldemptyrow = document.getElementById('row'+searchrow);
     oldemptyrow.emptyrow = false;
     // update totalrows
-    <% $opt{prefix} %>totalrows++
-    <% $opt{prefix} %>updateTotalRow();
+    totalrows++
+    updateTotalRow();
     // add a new empty row
-    <% $opt{prefix} %>addRow();
+    addRow();
   }
 
-  function <% $opt{prefix} %>deleteRow() {
+  function deleteRow() {
     var thisrownum = this.getAttribute('rownum');
 % if ( $opt{delete_row_callback} ) {
     // callback
-    <% $opt{delete_row_callback} %>(thisrownum,'<% $opt{prefix} %>');
+    <% $opt{delete_row_callback} %>(thisrownum);
 % }
     // remove the actual row
     var thisrow = document.getElementById('row'+thisrownum);
@@ -448,25 +477,25 @@ Example:
       newrows.push(allrows[i]);
     }
     allrows = newrows;
-    <% $opt{prefix} %>totalrows--; // should never be deleting empty rows
-    <% $opt{prefix} %>updateTotalRow();
+    totalrows--; // should never be deleting empty rows
+    updateTotalRow();
     // recalculate column totals, if any
 % my $col = 0;
 % foreach my $footer ( @{$opt{footer}} ) {
 %   if ($footer eq '_TOTAL' ) {
-    <% $opt{prefix} %>calc_total<% $col %>()
+    calc_total<% $col %>()
 %   }
 %   $col++;
 % }
   }
 
-  function <% $opt{prefix} %>addRow(values) {
+  function addRow(values) {
 
-    var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
+    var table = document.getElementById('OneTrueTable');
     var tablebody = table.getElementsByTagName('tbody').item(0);
 
     var row = table.insertRow(table.rows.length - 1);
-    var thisrownum = values ? values.rownum : <% $opt{prefix} %>rownum;
+    var thisrownum = values ? values.rownum : rownum;
     row.setAttribute('id', 'row'+thisrownum);
     row.emptyrow = values ? false : true;
     
@@ -481,7 +510,7 @@ Example:
       invnum_input.setAttribute('rownum', thisrownum);
       invnum_input.value = values ? values.invnum : '';
       invnum_input.onfocus = clearhint_invnum;
-      invnum_input.onchange = <% $opt{prefix} %>search_invnum;
+      invnum_input.onchange = search_invnum;
       invnum_cell.appendChild(invnum_input);
 
     row.appendChild(invnum_cell);
@@ -497,7 +526,7 @@ Example:
       display_custnum_input.setAttribute('rownum', thisrownum);
       display_custnum_input.value = values ? values.custnum : '';
       display_custnum_input.onfocus = clearhint_custnum;
-      display_custnum_input.onchange = <% $opt{prefix} %>search_custnum;
+      display_custnum_input.onchange = search_custnum;
       custnum_cell.appendChild(display_custnum_input);
 
       var custnum_input = document.createElement('INPUT');
@@ -552,7 +581,7 @@ Example:
       customer_input.value = values ? values.customer : '';
       customer_input.onfocus = clearhint_customer;
       customer_input.onclick = clearhint_customer;
-      customer_input.onchange = <% $opt{prefix} %>search_customer;
+      customer_input.onchange = search_customer;
       customer_cell.appendChild(customer_input);
 
       var customer_select = document.createElement('SELECT');
@@ -602,6 +631,9 @@ Example:
 
       var my_cell = document.createElement('TD');
       my_cell.setAttribute('align', '<% $align{ $opt{align}->[$col] || 'l' } %>');
+%     if ($opt{'color'}->[$col]) {
+      my_cell.style.color = '<% $opt{color}->[$col] %>';
+%     }
 
 %     if ($types->[$col] eq 'immutable') {
         var my_text = document.createTextNode(values ? values.<% $field %> : '');
@@ -626,8 +658,8 @@ Example:
         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%>;
+        my_input.onchange   = calc_total<%$col%>;
+        my_input.onkeyup    = calc_total<%$col%>;
 %     }
       my_cell.appendChild(my_input);
 
@@ -640,24 +672,23 @@ Example:
     td_delete.setAttribute('id', 'delete'+thisrownum);
     row.appendChild(td_delete);
     if (values) {
-      <% $opt{prefix} %>addDeleteButton(thisrownum);
+      addDeleteButton(thisrownum);
     }
 
     update_num_open(thisrownum, (values ? values.num_open : '0'));
 
 % if ( $opt{add_row_callback} ) {
-    <% $opt{add_row_callback} %>(thisrownum,
-                                 '<% $opt{prefix} %>', values);
+    <% $opt{add_row_callback} %>(thisrownum, values);
 % }
 
     // update the total number of rows display
     allrows.push(thisrownum);
-    if (values) <% $opt{prefix} %>totalrows++;
-    <% $opt{prefix} %>updateTotalRow();
+    if (values) totalrows++;
+    updateTotalRow();
 
     // update the next available row number
-    if (thisrownum >= <% $opt{prefix} %>rownum) {
-      <% $opt{prefix} %>rownum = thisrownum + 1;
+    if (thisrownum >= rownum) {
+      rownum = thisrownum + 1;
     }
 
   } // end of addRow
@@ -665,7 +696,7 @@ Example:
 
 </SCRIPT>
 
-<TABLE ID="<% $opt{prefix} %>OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+<TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
 
 <TR>
   <TH>Inv #</TH>
@@ -680,7 +711,7 @@ Example:
 
 % my @rownums = sort { $a <=> $b } map /^custnum(\d+)$/, keys %$param;
 <TR id="row_total">
-  <TH COLSPAN=5 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL">
+  <TH COLSPAN=5 ID="_TOTAL_TOTAL">
     Total <% @rownums || 0 %>
     <% PL($opt{name_singular} || 'customer', ( @rownums || 0 ) ) %>
   </TH>
@@ -702,17 +733,17 @@ Example:
 
 <SCRIPT TYPE="text/javascript">
 
-<% $opt{prefix} %>total_el =
-  document.getElementById("<% $opt{'prefix'} %>_TOTAL_TOTAL");
+total_el =
+  document.getElementById("_TOTAL_TOTAL");
 
-<% $opt{prefix} %>rownum = 1; // really more of a "next row", used by addrow
-<% $opt{prefix} %>totalrows = 0; // will not include empty rows
-<% $opt{prefix} %>allrows = []; // will include empty rows
+rownum = 1; // really more of a "next row", used by addrow
+totalrows = 0; // will not include empty rows
+allrows = []; // will include empty rows
 
 % foreach my $row ( @rownums ) {
 %   if ( grep($param->{$_.$row},qw(invnum display_custnum custnum status statuscolor customer balance),@{$opt{fields}} ) ) {
 
-<% $opt{prefix} %>addRow({
+addRow({
   rownum:<% $row %>,
   num_open:<% $param->{"num_open$row"} |js_string %>,
   invnum:<% $param->{"invnum$row"} |js_string %>,
@@ -738,27 +769,27 @@ Example:
 %   }
 % }
 
-<% $opt{prefix} %>addRow();
+addRow();
 
 % my $col = 0;
 % foreach my $footer ( @{$opt{footer}} ) {
 %   if ($footer eq '_TOTAL' ) {
 %     my $name = $opt{fields}->[$col];
 %     $name = ref($name) ? "column$col" : $name;
-      var <% $opt{prefix} %>th_el = document.getElementById("<%$name%>_TOTAL");
-      function <% $opt{prefix} %>calc_total<% $col %>() {
+      var th_el = document.getElementById("<%$name%>_TOTAL");
+      function calc_total<% $col %>() {
         var row = 0;
         var total = 0;
-        for (i = 0; i < <% $opt{prefix} %>allrows.length; i++) {
-          var value = document.getElementById("<%$name%>"+<% $opt{prefix} %>allrows[i]).value;
+        for (i = 0; i < allrows.length; i++) {
+          var value = document.getElementById("<%$name%>"+allrows[i]).value;
           value = parseFloat(value);
           if ( ! isNaN(value) ) {
             total = total + value;
           }
         }
-        <% $opt{prefix} %>th_el.innerHTML = ' ' + total.toFixed(2);
+        th_el.innerHTML = ' ' + total.toFixed(2);
       }
-      <% $opt{prefix} %>calc_total<% $col %>()
+      calc_total<% $col %>()
 %   }
 %   $col++;
 % }
@@ -775,10 +806,6 @@ Example:
 my(%opt) = @_;
 my $conf = new FS::Conf;
 
-## caution when using prefix, it isn't consistently applied to tag id/name
-$opt{prefix} = '' unless defined $opt{prefix};
-$opt{prefix} .= '_' if $opt{prefix};
-
 my $types = $opt{'type'} ? [ @{$opt{'type'}} ] : [];
 my $sizes = $opt{'size'} ? [ @{$opt{'size'}} ] : [];
 
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index fb3ec04..3b0ebc1 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -26,15 +26,15 @@ function warnUnload() {
 }
 window.onbeforeunload = warnUnload;
 
-function add_row_callback(rownum, prefix, values) {
+function add_row_callback(rownum, values) {
   if (values) {
-    custnum_update_callback(rownum, prefix);
+    custnum_update_callback(rownum);
   } else {
     document.getElementById('enable_app'+rownum).disabled = true;
   }
 }
 
-function delete_row_callback(rownum, prefix) {
+function delete_row_callback(rownum) {
   var i = 0;
   var delbutton = document.getElementById('delete'+rownum+'.'+i);
   var delrows = [];
@@ -49,7 +49,7 @@ function delete_row_callback(rownum, prefix) {
   }
 }
 
-function custnum_update_callback(rownum, prefix) {
+function custnum_update_callback(rownum) {
   var custnum = document.getElementById('custnum'+rownum).value;
   // if there is a custnum and more than one open invoice, enable
   // (and check) the box
@@ -58,17 +58,17 @@ function custnum_update_callback(rownum, prefix) {
   enable_app_checkbox.disabled = show_applications;
 
 % if ( $use_discounts ) {
-  select_discount_term(rownum, prefix);
+  select_discount_term(rownum);
 % }
 }
 
-function invnum_update_callback(rownum, prefix) {
-  custnum_update_callback(rownum, prefix);
+function invnum_update_callback(rownum) {
+  custnum_update_callback(rownum);
 }
 
-function select_discount_term(row, prefix) {
-  var custnum_obj = document.getElementById('custnum'+prefix+row);
-  var select_obj = document.getElementById('discount_term'+prefix+row);
+function select_discount_term(row) {
+  var custnum_obj = document.getElementById('custnum'+row);
+  var select_obj = document.getElementById('discount_term'+row);
 
   var value = '';
   if (select_obj.type == 'hidden') {
@@ -436,6 +436,7 @@ my @footer_align = ( 'r', 'r' );
 my @onchange = ( '', '' );;
 my $use_discounts = '';
 
+# Not entirely sure this works anymore...
 if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM part_pkg_discount') ) {
   #push @header, 'Discount';
   push @header, '';

commit d7415830b5b3bb6603ffd43beb26655baad95170
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Tue Jun 9 16:23:58 2015 -0500

    RT#33484: Delete lines from quick payment entry

diff --git a/httemplate/elements/customer-table.html b/httemplate/elements/customer-table.html
index 83abad0..090623f 100644
--- a/httemplate/elements/customer-table.html
+++ b/httemplate/elements/customer-table.html
@@ -128,8 +128,8 @@ Example:
       return;
     }
 
-    if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) {
-      <% $opt{prefix} %>addRow();
+    if ( document.getElementById('row'+searchrow).emptyrow ) {
+      <% $opt{prefix} %>newEmptyRow(searchrow);
     }
     var customer = document.getElementById('customer'+searchrow);
     customer.value = 'searching...';
@@ -179,8 +179,8 @@ Example:
       return;
     }
 
-    if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) {
-      <% $opt{prefix} %>addRow();
+    if ( document.getElementById('row'+searchrow).emptyrow ) {
+      <% $opt{prefix} %>newEmptyRow(searchrow);
     }
 
     var customer_obj = document.getElementById('customer'+searchrow);
@@ -262,8 +262,8 @@ Example:
       return;
     }
 
-    if ( ( <% $opt{prefix} %>rownum - searchrow ) == 1 ) {
-      <% $opt{prefix} %>addRow();
+    if ( document.getElementById('row'+searchrow).emptyrow ) {
+      <% $opt{prefix} %>newEmptyRow(searchrow);
     }
     
     var invnum = document.getElementById('invnum'+searchrow);
@@ -385,253 +385,101 @@ Example:
   }
 
   function update_num_open(rownum, newval) {
+    document.getElementById('num_open'+rownum).value = newval;
     num_open_invoices[rownum] = newval;
   }
 
+  function <% $opt{prefix} %>updateTotalRow () {
+    if ( <% $opt{prefix} %>totalrows == 1 ) {
+      <% $opt{prefix} %>total_el.innerHTML =
+        'Total '
+          + <% $opt{prefix} %>totalrows
+          + ' <% $opt{name_singular} || 'customer' %>';
+    } else {
+      <% $opt{prefix} %>total_el.innerHTML =
+        'Total '
+          + <% $opt{prefix} %>totalrows
+          + ' <% PL($opt{name_singular} || 'customer') %>';
+    }
+  }
 
-</SCRIPT>
+  var <% $opt{prefix} %>total_el, <% $opt{prefix} %>rownum, <% $opt{prefix} %>totalrows, <% $opt{prefix} %>allrows;
+
+  function <% $opt{prefix} %>addDeleteButton (searchrow) {
+    var td_delete = document.getElementById('delete'+searchrow);
+    var button_delete = document.createElement('INPUT');
+    button_delete.setAttribute('rownum', searchrow);
+    button_delete.setAttribute('type', 'button');
+    button_delete.setAttribute('value', 'X');
+    button_delete.onclick = <% $opt{prefix} %>deleteRow;
+    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);
+  }
 
-<TABLE ID="<% $opt{prefix} %>OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+  function <% $opt{prefix} %>newEmptyRow (searchrow) {
+    // add delete button to current row
+    <% $opt{prefix} %>addDeleteButton(searchrow);
+    // mark current row as non-empty
+    var oldemptyrow = document.getElementById('row'+searchrow);
+    oldemptyrow.emptyrow = false;
+    // update totalrows
+    <% $opt{prefix} %>totalrows++
+    <% $opt{prefix} %>updateTotalRow();
+    // add a new empty row
+    <% $opt{prefix} %>addRow();
+  }
 
-<TR>
-  <TH>Inv #</TH>
-  <TH>Cust #</TH>
-  <TH>Status</TH>
-  <TH>Customer</TH>
-  <TH>Balance</TH>
-% foreach my $header ( @{$opt{header}} ) {
-    <TH><% $header %></TH>
+  function <% $opt{prefix} %>deleteRow() {
+    var thisrownum = this.getAttribute('rownum');
+% if ( $opt{delete_row_callback} ) {
+    // callback
+    <% $opt{delete_row_callback} %>(thisrownum,'<% $opt{prefix} %>');
 % }
-</TR>
-% my $row = 0;
-% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { 
-
-    <TR id="row<%$row%>" rownum="<%$row%>">
-      <TD>
-	<INPUT TYPE      = "text"
-               NAME      = "invnum<% $row %>"
-               ID        = "invnum<% $row %>"
-               SIZE      = 8
-               MAXLENGTH = 12
-               STYLE     = "text-align:right;"
-               VALUE     = "<% $param->{"invnum$row"} %>"
-               rownum    = "<% $row %>"
-        >
-        <SCRIPT TYPE="text/javascript">
-          var invnum_input<% $row %> = document.getElementById("invnum<% $row %>");
-          invnum_input<% $row %>.onfocus = clearhint_invnum;
-          invnum_input<% $row %>.onchange = <% $opt{prefix} %>search_invnum;
-        </SCRIPT>
-      </TD>
-
-      <TD>
-        <INPUT TYPE      = "text"
-               NAME      = "display_custnum<% $row %>"
-               ID        = "display_custnum<% $row %>"
-               SIZE      = 8
-               MAXLENGTH = 12
-               STYLE     = "text-align:right;"
-               VALUE     = "<% $param->{"display_custnum$row"} %>"
-               rownum    = "<% $row %>"
-        >
-        <INPUT TYPE      = "hidden"
-               NAME      = "custnum<% $row %>"
-               ID        = "custnum<% $row %>"
-               VALUE     = "<% $param->{"custnum$row"} %>"
-               rownum    = "<% $row %>"
-        >
-        <SCRIPT TYPE="text/javascript">
-          var custnum_input<% $row %> = document.getElementById("display_custnum<% $row %>");
-          custnum_input<% $row %>.onfocus = clearhint_custnum;
-          custnum_input<% $row %>.onchange = <% $opt{prefix} %>search_custnum;
-        </SCRIPT>
-      </TD>
-      
-      <TD STYLE="text-align: center">
-        <SPAN
-               ID        = "status<% $row %>_text"
-               rownum    = "<% $row %>"
-               STYLE     = "font-weight: bold;
-                            color: <%$param->{"statuscolor$row"} || '#000000'%>"
-
-        ><% $param->{"status$row"} %></SPAN>
-        <INPUT TYPE      = "hidden"
-               NAME      = "status<% $row %>"
-               ID        = "status<% $row %>"
-               VALUE     = "<% $param->{"status$row"} %>"
-               rownum    = "<% $row %>"
-        >
-        <INPUT TYPE      = "hidden"
-               NAME      = "statuscolor<% $row %>"
-               ID        = "statuscolor<% $row %>"
-               VALUE     = "<% $param->{"statuscolor$row"} %>"
-               rownum    = "<% $row %>"
-        >
-      </TD>
-
-      <TD>
-        <INPUT TYPE="text" NAME="customer<% $row %>" ID="customer<% $row %>" SIZE=64 VALUE="<% $param->{"customer$row"} %>" rownum="<% $row %>">
-          <SCRIPT TYPE="text/javascript">
-            var customer_input<% $row %> = document.getElementById("customer<% $row %>");
-            customer_input<% $row %>.onfocus = clearhint_customer;
-            customer_input<% $row %>.onclick = clearhint_customer;
-            customer_input<% $row %>.onchange = <% $opt{prefix} %>search_customer;
-          </SCRIPT>
-        <SELECT NAME="cust_select<% $row %>" ID="cust_select<% $row %>" rownum="<% $row %>" STYLE="color:#ff0000; display:none">
-        </SELECT>
-          <SCRIPT TYPE="text/javascript">
-            var customer_select<% $row %> = document.getElementById("cust_select<% $row %>");
-            customer_select<% $row %>.onchange = select_customer;
-          </SCRIPT>
-      </TD>
-
-      <TD STYLE="text-align:right">
-        <% $money_char %>
-        <SPAN 
-               ID        = "balance<% $row %>_text"
-               rownum    = "<% $row %>"
-        ><% $param->{"balance$row"} %></SPAN>
-         
-        <INPUT TYPE      = "hidden"
-               NAME      = "balance<% $row %>"
-               ID        = "balance<% $row %>"
-               VALUE     = "<% $param->{"balance$row"} %>"
-               rownum    = "<% $row %>"
-        >
-      </TD>
-
-%   my $col = 0;
-%   foreach my $field ( @{$opt{fields}} ) {
-%     my $value;
-%     if ( ref($field) eq 'CODE' ) {
-%       $value = &{$field}($row,$param);
-%     } else {
-%       $value = $param->{"$field$row"}; 
-%     }
-%     my $name  = (ref($field) eq 'CODE') ? "column${col}_$row" : "$field$row";
-%     my $align = $align{ $opt{align}->[$col] || 'l' };
-%     my $size  = $sizes->[$col]  || 10;
-%     my $color = $opt{color}->[$col];
-%     my $font = $color ? qq(<FONT COLOR="$color">) : '';
-%     my $onchange = '';
-%     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 %>">
-%     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') {
-        <% $font %><% $value %><% $font ? '</FONT>' : '' %>
-        <INPUT TYPE="hidden" ID="<% $name %>" NAME="<% $name %>" VALUE="<% $value %>" >
-%     } else {
-        Cannot represent unknown type: <% $types->[$col] %>
-%     }
-      </TD>
-%     $col++;
-%   }
-    </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 ) ) %>
-  </TH>
-% my $col = 0;
-% foreach my $footer ( @{$opt{footer}} ) {
-%   my $align = $align{ $opt{'footer_align'}->[$col] || 'c' };
-%   if ($footer eq '_TOTAL' ) {
-%     my $id = $opt{'fields'}->[$col];
-%     $id = ref($id) ? "column${col}_TOTAL" : "${id}_TOTAL";
-      <TH ALIGN="<% $align %>" ID="<% $id %>"> <% sprintf('%.2f', $total[$col] ) %></TH>
-%   } else {
-      <TH ALIGN="<% $align %>"><% $footer %></TH>
-%   }
-%   $col++;
-% }
-</TR>
-
-</TABLE>
-
-<SCRIPT TYPE="text/javascript">
+    // remove the actual row
+    var thisrow = document.getElementById('row'+thisrownum);
+    thisrow.parentNode.removeChild(thisrow);
+    // remove row from tally of all rows
+    var newrows = [];
+    for (i = 0; i < allrows.length; i++) {
+      if (allrows[i] == thisrownum) continue;
+      newrows.push(allrows[i]);
+    }
+    allrows = newrows;
+    <% $opt{prefix} %>totalrows--; // should never be deleting empty rows
+    <% $opt{prefix} %>updateTotalRow();
+    // recalculate column totals, if any
 % my $col = 0;
 % foreach my $footer ( @{$opt{footer}} ) {
 %   if ($footer eq '_TOTAL' ) {
-%     my $name = $opt{fields}->[$col];
-%     $name = ref($name) ? "column$col" : $name;
-      var <% $opt{prefix}.$name %>_CACHE = new Array ();
-      var <% $opt{prefix} %>th_el = document.getElementById("<%$name%>_TOTAL");
-      function <% $opt{prefix} %>calc_total<% $col %>() {
-        var row = 0;
-        var total = 0;
-        for ( var row = 0;
-              
-              ( <% $opt{prefix}.$name%>_CACHE[row] =
-                                        <% $opt{prefix}.$name%>_CACHE[row]
-                                     || document.getElementById("<%$name%>"+row)
-              ) != null;
-              
-              row++
-            )
-        {
-          var value = <%$name%>_CACHE[row].value;
-          value = parseFloat(value);
-          if ( ! isNaN(value) ) {
-            total = total + value;
-          }
-        }
-        <% $opt{prefix} %>th_el.innerHTML = ' ' + total.toFixed(2);
-
-      }
+    <% $opt{prefix} %>calc_total<% $col %>()
 %   }
 %   $col++;
 % }
-</SCRIPT>
-
-<% include('/elements/xmlhttp.html',
-              'url'  => $p. 'misc/xmlhttp-cust_main-search.cgi',
-              'subs' => [qw( custnum_search smart_search invnum_search )],
-           )
-%>
-
-<SCRIPT TYPE="text/javascript">
-
-  var <% $opt{prefix} %>total_el =
-    document.getElementById("<% $opt{'prefix'} %>_TOTAL_TOTAL");
-
-  var <% $opt{prefix} %>rownum = <% $row %>;
+  }
 
-  function <% $opt{prefix} %>addRow() {
+  function <% $opt{prefix} %>addRow(values) {
 
     var table = document.getElementById('<% $opt{prefix} %>OneTrueTable');
     var tablebody = table.getElementsByTagName('tbody').item(0);
 
     var row = table.insertRow(table.rows.length - 1);
-    row.setAttribute('id', 'row'+rownum);
+    var thisrownum = values ? values.rownum : <% $opt{prefix} %>rownum;
+    row.setAttribute('id', 'row'+thisrownum);
+    row.emptyrow = values ? false : true;
     
     var invnum_cell = document.createElement('TD');
 
       var invnum_input = document.createElement('INPUT');
-      invnum_input.setAttribute('name', 'invnum'+<% $opt{prefix} %>rownum);
-      invnum_input.setAttribute('id',   'invnum'+<% $opt{prefix} %>rownum);
+      invnum_input.setAttribute('name', 'invnum'+thisrownum);
+      invnum_input.setAttribute('id',   'invnum'+thisrownum);
       invnum_input.style.textAlign = 'right';
       invnum_input.setAttribute('size', 8);
       invnum_input.setAttribute('maxlength', 12);
-      invnum_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      invnum_input.setAttribute('rownum', thisrownum);
+      invnum_input.value = values ? values.invnum : '';
       invnum_input.onfocus = clearhint_invnum;
       invnum_input.onchange = <% $opt{prefix} %>search_invnum;
       invnum_cell.appendChild(invnum_input);
@@ -641,21 +489,23 @@ Example:
     var custnum_cell = document.createElement('TD');
 
       var display_custnum_input = document.createElement('INPUT');
-      display_custnum_input.setAttribute('name', 'display_custnum'+<% $opt{prefix} %>rownum);
-      display_custnum_input.setAttribute('id',   'display_custnum'+<% $opt{prefix} %>rownum);
+      display_custnum_input.setAttribute('name', 'display_custnum'+thisrownum);
+      display_custnum_input.setAttribute('id',   'display_custnum'+thisrownum);
       display_custnum_input.style.textAlign = 'right';
       display_custnum_input.setAttribute('size', 8);
       display_custnum_input.setAttribute('maxlength', 12);
-      display_custnum_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      display_custnum_input.setAttribute('rownum', thisrownum);
+      display_custnum_input.value = values ? values.custnum : '';
       display_custnum_input.onfocus = clearhint_custnum;
       display_custnum_input.onchange = <% $opt{prefix} %>search_custnum;
       custnum_cell.appendChild(display_custnum_input);
 
       var custnum_input = document.createElement('INPUT');
       custnum_input.type = 'hidden';
-      custnum_input.setAttribute('name', 'custnum'+<% $opt{prefix} %>rownum);
-      custnum_input.setAttribute('id',   'custnum'+<% $opt{prefix} %>rownum);
-      custnum_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      custnum_input.setAttribute('name', 'custnum'+thisrownum);
+      custnum_input.setAttribute('id',   'custnum'+thisrownum);
+      custnum_input.setAttribute('rownum', thisrownum);
+      custnum_input.value = values ? values.custnum : '';
       custnum_cell.appendChild(custnum_input);
 
     row.appendChild(custnum_cell);
@@ -664,23 +514,29 @@ Example:
       status_cell.style.textAlign = 'center';
         
       var status_span = document.createElement('SPAN');
-      status_span.setAttribute('id', 'status'+<% $opt{prefix} %>rownum+'_text');
+      status_span.setAttribute('id', 'status'+thisrownum+'_text');
       status_span.style.fontWeight = 'bold';
-      status_span.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      status_span.style.color = values ? values.statuscolor : '';
+      status_span.setAttribute('rownum', thisrownum);
+      status_span.appendChild(
+        document.createTextNode(values ? values.status : '')
+      );
       status_cell.appendChild(status_span);
         
       var status_input = document.createElement('INPUT');
       status_input.setAttribute('type', 'hidden');
-      status_input.setAttribute('name', 'status'+<% $opt{prefix} %>rownum);
-      status_input.setAttribute('id',   'status'+<% $opt{prefix} %>rownum);
-      status_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      status_input.setAttribute('name', 'status'+thisrownum);
+      status_input.setAttribute('id',   'status'+thisrownum);
+      status_input.setAttribute('rownum', thisrownum);
+      status_input.value = values ? values.status : '';
       status_cell.appendChild(status_input);
 
       var statuscolor_input = document.createElement('INPUT');
       statuscolor_input.setAttribute('type', 'hidden');
-      statuscolor_input.setAttribute('name', 'statuscolor'+<% $opt{prefix} %>rownum);
-      statuscolor_input.setAttribute('id',   'statuscolor'+<% $opt{prefix} %>rownum);
-      statuscolor_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      statuscolor_input.setAttribute('name', 'statuscolor'+thisrownum);
+      statuscolor_input.setAttribute('id',   'statuscolor'+thisrownum);
+      statuscolor_input.setAttribute('rownum', thisrownum);
+      statuscolor_input.value = values ? values.statuscolor : '';
       status_cell.appendChild(statuscolor_input);
 
     row.appendChild(status_cell);
@@ -688,20 +544,21 @@ Example:
     var customer_cell = document.createElement('TD');
 
       var customer_input = document.createElement('INPUT');
-      customer_input.setAttribute('name', 'customer'+<% $opt{prefix} %>rownum);
-      customer_input.setAttribute('id',   'customer'+<% $opt{prefix} %>rownum);
+      customer_input.setAttribute('name', 'customer'+thisrownum);
+      customer_input.setAttribute('id',   'customer'+thisrownum);
       customer_input.setAttribute('size', 64);
       customer_input.setAttribute('value', '(last name or company)' );
-      customer_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      customer_input.setAttribute('rownum', thisrownum);
+      customer_input.value = values ? values.customer : '';
       customer_input.onfocus = clearhint_customer;
       customer_input.onclick = clearhint_customer;
       customer_input.onchange = <% $opt{prefix} %>search_customer;
       customer_cell.appendChild(customer_input);
 
       var customer_select = document.createElement('SELECT');
-      customer_select.setAttribute('name', 'cust_select'+<% $opt{prefix} %>rownum);
-      customer_select.setAttribute('id',   'cust_select'+<% $opt{prefix} %>rownum);
-      customer_select.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      customer_select.setAttribute('name', 'cust_select'+thisrownum);
+      customer_select.setAttribute('id',   'cust_select'+thisrownum);
+      customer_select.setAttribute('rownum', thisrownum);
       customer_select.style.color = '#ff0000';
       customer_select.style.display = 'none';
       customer_select.onchange = select_customer;
@@ -715,21 +572,29 @@ Example:
       balance_cell.appendChild(document.createTextNode('<%$money_char%>'));
 
       var balance_span = document.createElement('SPAN');
-      balance_span.setAttribute('id', 'balance'+<% $opt{prefix} %>rownum+'_text');
-      balance_span.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      balance_span.setAttribute('id', 'balance'+thisrownum+'_text');
+      balance_span.setAttribute('rownum', thisrownum);
       balance_cell.appendChild(balance_span);
 
       balance_cell.appendChild(
-        document.createTextNode(String.fromCharCode(160)) // 
+        document.createTextNode(String.fromCharCode(160) + (values ? values.balance : '')) // 
       );
 
       var balance_input = document.createElement('INPUT');
       balance_input.setAttribute('type', 'hidden');
-      balance_input.setAttribute('name', 'balance'+<% $opt{prefix} %>rownum);
-      balance_input.setAttribute('id',   'balance'+<% $opt{prefix} %>rownum);
-      balance_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      balance_input.setAttribute('name', 'balance'+thisrownum);
+      balance_input.setAttribute('id',   'balance'+thisrownum);
+      balance_input.setAttribute('rownum', thisrownum);
+      balance_input.value = values ? values.balance : '';
       balance_cell.appendChild(balance_input);
 
+      var num_open_input = document.createElement('INPUT');
+      num_open_input.setAttribute('type', 'hidden');
+      num_open_input.setAttribute('name', 'num_open'+thisrownum);
+      num_open_input.setAttribute('id',   'num_open'+thisrownum);
+      num_open_input.setAttribute('rownum', thisrownum);
+      balance_cell.appendChild(num_open_input);
+
     row.appendChild(balance_cell);
 
 %   my $col = 0;
@@ -739,29 +604,24 @@ Example:
       my_cell.setAttribute('align', '<% $align{ $opt{align}->[$col] || 'l' } %>');
 
 %     if ($types->[$col] eq 'immutable') {
-%       my $value;
-%       if ( ref($field) eq 'CODE' ) {
-%         $value = &{$field}($row,$param);
-%       } else {
-%         $value = $param->{"$field$row"}; 
-%       }
-        var my_text = document.createTextNode(<% $value |js_string %>);
+        var my_text = document.createTextNode(values ? values.<% $field %> : '');
         my_cell.appendChild(my_text);
 %     }
 
 %     my $name  = (ref($field) eq 'CODE') ? "column${col}_" : $field;
       var my_input = document.createElement('INPUT');
-      my_input.setAttribute('name', '<% $name %>'+<% $opt{prefix} %>rownum);
-      my_input.setAttribute('id',   '<% $name %>'+<% $opt{prefix} %>rownum);
+      my_input.setAttribute('name', '<% $name %>'+thisrownum);
+      my_input.setAttribute('id',   '<% $name %>'+thisrownum);
       my_input.style.textAlign = '<% $align{ $opt{align}->[$col] || 'l' } %>';
       my_input.setAttribute('size', <% $sizes->[$col] || 10 %>);
-      my_input.setAttribute('rownum', <% $opt{prefix} %>rownum);
+      my_input.setAttribute('rownum', thisrownum);
 %     if ( $types->[$col] eq 'immutable' ) {
-        my_input.setAttribute('type', 'hidden');
-%     }
-%     elsif ( $types->[$col] eq 'checkbox' ) {
-        my_input.setAttribute('type', 'checkbox');
+      my_input.setAttribute('type', 'hidden');
+%     } elsif ( $types->[$col] eq 'checkbox' ) {
+      my_input.setAttribute('type', 'checkbox');
+      my_input.checked = (values && values.<% $field %>) ? true : false;
 %     }
+      my_input.value = (values && values.<% $field %>) || '';
 %     if ( $opt{onchange}->[$col] ) {
         my_input.onchange   = <% $opt{onchange}->[$col] %>;
 %     }
@@ -776,38 +636,146 @@ Example:
 %     $col++;
 %   }
 
-    //update the total # of rows display
-    if ( <% $opt{prefix} %>rownum == 1 ) {
-      <% $opt{prefix} %>total_el.innerHTML =
-        'Total '
-          + <% $opt{prefix} %>rownum
-          + ' <% $opt{name_singular} || 'customer' %>';
-    } else {
-      <% $opt{prefix} %>total_el.innerHTML =
-        'Total '
-          + <% $opt{prefix} %>rownum
-          + ' <% PL($opt{name_singular} || 'customer') %>';
+    var td_delete = document.createElement('TD');
+    td_delete.setAttribute('id', 'delete'+thisrownum);
+    row.appendChild(td_delete);
+    if (values) {
+      <% $opt{prefix} %>addDeleteButton(thisrownum);
     }
 
+    update_num_open(thisrownum, (values ? values.num_open : '0'));
+
 % if ( $opt{add_row_callback} ) {
-    <% $opt{add_row_callback} %>(<% $opt{prefix} %>rownum,
-                                 '<% $opt{prefix} %>');
+    <% $opt{add_row_callback} %>(thisrownum,
+                                 '<% $opt{prefix} %>', values);
 % }
 
-    <% $opt{prefix} %>rownum++;
+    // update the total number of rows display
+    allrows.push(thisrownum);
+    if (values) <% $opt{prefix} %>totalrows++;
+    <% $opt{prefix} %>updateTotalRow();
 
-  }
+    // update the next available row number
+    if (thisrownum >= <% $opt{prefix} %>rownum) {
+      <% $opt{prefix} %>rownum = thisrownum + 1;
+    }
 
-% unless ($cgi->param('error')) {
-  <% $opt{prefix} %>addRow();
+  } // end of addRow
+
+
+</SCRIPT>
+
+<TABLE ID="<% $opt{prefix} %>OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
+
+<TR>
+  <TH>Inv #</TH>
+  <TH>Cust #</TH>
+  <TH>Status</TH>
+  <TH>Customer</TH>
+  <TH>Balance</TH>
+% foreach my $header ( @{$opt{header}} ) {
+    <TH><% $header %></TH>
+% }
+</TR>
+
+% my @rownums = sort { $a <=> $b } map /^custnum(\d+)$/, keys %$param;
+<TR id="row_total">
+  <TH COLSPAN=5 ID="<% $opt{'prefix'} %>_TOTAL_TOTAL">
+    Total <% @rownums || 0 %>
+    <% PL($opt{name_singular} || 'customer', ( @rownums || 0 ) ) %>
+  </TH>
+% my $col = 0;
+% foreach my $footer ( @{$opt{footer}} ) {
+%   my $align = $align{ $opt{'footer_align'}->[$col] || 'c' };
+%   if ($footer eq '_TOTAL' ) {
+%     my $id = $opt{'fields'}->[$col];
+%     $id = ref($id) ? "column${col}_TOTAL" : "${id}_TOTAL";
+      <TH ALIGN="<% $align %>" ID="<% $id %>"> <% sprintf('%.2f', $total[$col] ) %></TH>
+%   } else {
+      <TH ALIGN="<% $align %>"><% $footer %></TH>
+%   }
+%   $col++;
+% }
+</TR>
+
+</TABLE>
+
+<SCRIPT TYPE="text/javascript">
+
+<% $opt{prefix} %>total_el =
+  document.getElementById("<% $opt{'prefix'} %>_TOTAL_TOTAL");
+
+<% $opt{prefix} %>rownum = 1; // really more of a "next row", used by addrow
+<% $opt{prefix} %>totalrows = 0; // will not include empty rows
+<% $opt{prefix} %>allrows = []; // will include empty rows
+
+% foreach my $row ( @rownums ) {
+%   if ( grep($param->{$_.$row},qw(invnum display_custnum custnum status statuscolor customer balance),@{$opt{fields}} ) ) {
+
+<% $opt{prefix} %>addRow({
+  rownum:<% $row %>,
+  num_open:<% $param->{"num_open$row"} |js_string %>,
+  invnum:<% $param->{"invnum$row"} |js_string %>,
+  display_custnum:<% $param->{"display_custnum$row"} |js_string %>,
+  custnum:<% $param->{"custnum$row"} |js_string %>,
+  status:<% $param->{"status$row"} |js_string %>,
+  statuscolor:<% $param->{"statuscolor$row"} |js_string %>,
+  customer:<% $param->{"customer$row"} |js_string %>,
+  balance:<% $param->{"balance$row"} |js_string %>,
+%     my $col = 0;
+%     foreach my $field ( @{$opt{fields}} ) {
+%       my $value;
+%       if ( ref($field) eq 'CODE' ) {
+%         $value = &{$field}($row,$param) || '';
+%       } else {
+%         $value = $param->{"$field$row"} || '';
+%       }
+%       my $name  = (ref($field) eq 'CODE') ? "column${col}" : "$field";
+  <% $name %>:<% $value |js_string %>,
+%       $col++;
+%     }
+});
+%   }
+% }
+
+<% $opt{prefix} %>addRow();
+
+% my $col = 0;
+% foreach my $footer ( @{$opt{footer}} ) {
+%   if ($footer eq '_TOTAL' ) {
+%     my $name = $opt{fields}->[$col];
+%     $name = ref($name) ? "column$col" : $name;
+      var <% $opt{prefix} %>th_el = document.getElementById("<%$name%>_TOTAL");
+      function <% $opt{prefix} %>calc_total<% $col %>() {
+        var row = 0;
+        var total = 0;
+        for (i = 0; i < <% $opt{prefix} %>allrows.length; i++) {
+          var value = document.getElementById("<%$name%>"+<% $opt{prefix} %>allrows[i]).value;
+          value = parseFloat(value);
+          if ( ! isNaN(value) ) {
+            total = total + value;
+          }
+        }
+        <% $opt{prefix} %>th_el.innerHTML = ' ' + total.toFixed(2);
+      }
+      <% $opt{prefix} %>calc_total<% $col %>()
+%   }
+%   $col++;
 % }
 </SCRIPT>
 
+<% include('/elements/xmlhttp.html',
+              'url'  => $p. 'misc/xmlhttp-cust_main-search.cgi',
+              'subs' => [qw( custnum_search smart_search invnum_search )],
+           )
+%>
+
 <%init>
 
 my(%opt) = @_;
 my $conf = new FS::Conf;
 
+## caution when using prefix, it isn't consistently applied to tag id/name
 $opt{prefix} = '' unless defined $opt{prefix};
 $opt{prefix} .= '_' if $opt{prefix};
 
diff --git a/httemplate/misc/batch-cust_pay.html b/httemplate/misc/batch-cust_pay.html
index cc1a26a..fb3ec04 100644
--- a/httemplate/misc/batch-cust_pay.html
+++ b/httemplate/misc/batch-cust_pay.html
@@ -26,8 +26,27 @@ function warnUnload() {
 }
 window.onbeforeunload = warnUnload;
 
-function add_row_callback(rownum, prefix) {
-  document.getElementById('enable_app'+rownum).disabled = true;
+function add_row_callback(rownum, prefix, values) {
+  if (values) {
+    custnum_update_callback(rownum, prefix);
+  } else {
+    document.getElementById('enable_app'+rownum).disabled = true;
+  }
+}
+
+function delete_row_callback(rownum, prefix) {
+  var i = 0;
+  var delbutton = document.getElementById('delete'+rownum+'.'+i);
+  var delrows = [];
+  while (delbutton) {
+    delrows[i] = delbutton;
+    i++;
+    delbutton = document.getElementById('delete'+rownum+'.'+i);
+  }
+  delrows = delrows.reverse();
+  for (i = 0; i < delrows.length; i++) {
+    delrows[i].onclick();
+  }
 }
 
 function custnum_update_callback(rownum, prefix) {
@@ -313,17 +332,17 @@ function create_application_row(rownum, appnum) {
 
 %# for error handling--ugly, but the alternative is translating the whole 
 %# process of creating rows into Mason
-var row_array = <% encode_json(\@rows) %>;
+var row_obj = <% encode_json(\%rows) %>;
 function preload() {
   var rownum;
   var appnum;
-  for (rownum=0; rownum < row_array.length; rownum++) {
-    if ( row_array[rownum].length ) {
+  for (rownum in row_obj) {
+    if ( row_obj[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];
+        for (appnum=0; appnum < row_obj[r].length; appnum++) {
+          this_app = row_obj[r][appnum];
           var x = r + '.' + appnum;
           //set invnum
           var select_invnum = document.getElementById('invnum'+x);
@@ -345,7 +364,7 @@ function preload() {
         } //for appnum
       }; //preload_row function
       toggle_application_row.call(enable, null, preload_row);
-    } // if row_array[rownum].length
+    } // if (row_obj[rownum].length
   } //for rownum
 }
 
@@ -380,6 +399,7 @@ function preload() {
     custnum_update_callback => 'custnum_update_callback',
     invnum_update_callback => 'invnum_update_callback',
     add_row_callback => 'add_row_callback',
+    delete_row_callback => 'delete_row_callback',
 &>
 
 <BR>
@@ -387,13 +407,12 @@ function preload() {
 
 </FORM>
 
-%if ( $cgi->param('error') ) {
-<SCRIPT TYPE="text/javascript">
-%  for ( my $row = 0; defined($cgi->param("custnum$row")); $row++ ) {
-     select_discount_term(<% $row %>, '');
-%  }
-</SCRIPT>
-%}
+% #XXX I think this can go away completely, but need to test with $use_discount
+% ###not perl <SCRIPT TYPE="text/javascript">
+% #foreach my $row ( keys %rows ) {
+% ###not perl   select_discount_term(<% $row %>, '');
+% #}
+% ###not perl </SCRIPT>
 
 <% include('/elements/footer.html') %>
 
@@ -455,29 +474,31 @@ push @onchange, '';
 $m->comp('/elements/handle_uri_query');
 
 # set up for preloading
-my @rows;
-my @row_errors;
+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"};
+  my @invrows = grep /^invnum\d+\.\d+$/, keys %$param; #pare down possibilities
+  foreach my $row ( sort { $a <=> $b } map /^custnum(\d+)$/, keys %$param ) {
+#  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++ ) {
+    foreach my $app ( map /^invnum$row\.(\d+)$/, @invrows ) {
       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;
+      $rows{$row}[$app] = \%this_app;
       $enum++;
     }
   }
-  for( my $row = 0; $row < @row_errors; $row++ ) {
-    $param->{"error$row"} = $row_errors[$row];
+  foreach my $row (keys %rows) {
+    $param->{"error$row"} = $row_errors{$row};
   }
 }
-#warn Dumper {rows => \@rows, row_errors => \@row_errors };
+#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 1105af9..bb4b973 100644
--- a/httemplate/misc/process/batch-cust_pay.cgi
+++ b/httemplate/misc/process/batch-cust_pay.cgi
@@ -12,7 +12,8 @@ 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 @invrows = grep(/^invnum\d+\.\d+$/, keys %$param);
+foreach my $row ( map /^custnum(\d+)$/, keys %$param ) {
   my $custnum = $param->{"custnum$row"};
   my $cust_main;
   if ( $custnum =~ /^(\d+)$/ and $1 <= 2147483647 ) {
@@ -48,7 +49,8 @@ for ( my $row = 0; exists($param->{"custnum$row"}); $row++ ) {
 
   # payment applications, if any
   my @cust_bill_pay = ();
-  for ( my $app = 0; exists($param->{"invnum$row.$app"}); $app++ ) {
+  foreach my $app ( sort {$a <=> $b} map /^invnum$row\.(\d+)$/, @invrows ) {
+#  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"},

commit 2e63285d3f95c11e879181e9bf972499cfa0c3e8
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Tue Jun 9 15:47:56 2015 -0500

    RT#33484 [actually a bug fix to d4b8e45c3589f3c6489442798f5fba439d5a29c9]

diff --git a/FS/FS/cust_main/Billing_Discount.pm b/FS/FS/cust_main/Billing_Discount.pm
index b2852f6..117bf31 100644
--- a/FS/FS/cust_main/Billing_Discount.pm
+++ b/FS/FS/cust_main/Billing_Discount.pm
@@ -47,7 +47,7 @@ sub _discount_pkgs_and_bill {
   push @where,
     "NOT EXISTS (
        SELECT 1 FROM cust_bill_pkg_discount
-         WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_discount.billpkgnu:
+         WHERE cust_bill_pkg.billpkgnum = cust_bill_pkg_discount.billpkgnum
     )";
 
   my $extra_sql = 'WHERE '. join(' AND ', @where);

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

Summary of changes:
 FS/FS/cust_main/Billing_Discount.pm        |    2 +-
 httemplate/elements/customer-table.html    |  595 ++++++++++++++--------------
 httemplate/misc/batch-cust_pay.html        |   86 ++--
 httemplate/misc/process/batch-cust_pay.cgi |    6 +-
 4 files changed, 354 insertions(+), 335 deletions(-)




More information about the freeside-commits mailing list