updated quick payment entry
authorivan <ivan>
Mon, 10 Oct 2005 12:20:57 +0000 (12:20 +0000)
committerivan <ivan>
Mon, 10 Oct 2005 12:20:57 +0000 (12:20 +0000)
Changes.1.5.8
FS/FS/cust_pay.pm
README.1.5.8
htetc/global.asa
htetc/handler.pl
httemplate/docs/upgrade10.html
httemplate/index.html
httemplate/misc/batch-cust_pay.html
httemplate/misc/process/batch-cust_pay.cgi [new file with mode: 0644]
httemplate/misc/xmlhttp-cust_main-search.cgi [new file with mode: 0644]
httemplate/search/cust_pay.cgi

index f8066cc..2b55c5c 100644 (file)
@@ -16,3 +16,8 @@
 - moved to XMLHttpRequest instead of hidden iframe transport for progress bar,
   should be more efficient and improve compatibility with Konq and maybe other
   browsers?
 - moved to XMLHttpRequest instead of hidden iframe transport for progress bar,
   should be more efficient and improve compatibility with Konq and maybe other
   browsers?
+- also use XMLHttpRequest for retreiving states rather than send a huge page
+  for customer add/edit, much faster
+- redo account view and edit pages, add ability to edit uid/gid if conf options
+  for it are turned on
+- redo quick payment entry page with ajax magic
index 0f872a4..a7d6990 100644 (file)
@@ -122,12 +122,13 @@ sub insert {
     $self->custnum($cust_bill->custnum );
   }
 
     $self->custnum($cust_bill->custnum );
   }
 
-  my $cust_main = $self->cust_main;
-  my $old_balance = $cust_main->balance;
 
   my $error = $self->check;
   return $error if $error;
 
 
   my $error = $self->check;
   return $error if $error;
 
+  my $cust_main = $self->cust_main;
+  my $old_balance = $cust_main->balance;
+
   $error = $self->SUPER::insert;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
   $error = $self->SUPER::insert;
   if ( $error ) {
     $dbh->rollback if $oldAutoCommit;
@@ -398,6 +399,61 @@ sub check {
   $self->SUPER::check;
 }
 
   $self->SUPER::check;
 }
 
+=item batch_insert CUST_PAY_OBJECT, ...
+
+Class method which inserts multiple payments.  Takes a list of FS::cust_pay
+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.
+
+For example:
+
+  my @errors = FS::cust_pay->batch_insert(@cust_pay);
+  my $num_errors = scalar(grep $_, @errors);
+  if ( $num_errors == 0 ) {
+    #success; all payments were inserted
+  } else {
+    #failure; no payments were inserted.
+  }
+
+=cut
+
+sub batch_insert {
+  my $self = shift; #class method
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  my $errors = 0;
+  
+  my @errors = map {
+    my $error = $_->insert;
+    if ( $error ) { 
+      $errors++;
+    } else {
+      $_->cust_main->apply_payments;
+    }
+    $error;
+  } @_;
+
+  if ( $errors ) {
+    $dbh->rollback if $oldAutoCommit;
+  } else {
+    $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  }
+
+  @errors;
+
+}
+
 =item cust_bill_pay
 
 Returns all applications to invoices (see L<FS::cust_bill_pay>) for this
 =item cust_bill_pay
 
 Returns all applications to invoices (see L<FS::cust_bill_pay>) for this
index fb17365..209ab3f 100644 (file)
@@ -1,4 +1,6 @@
 
 
+install JSON
+
 install DBIx::DBSchema 0.27
 install HTML::Widgets:SelectLayers 0.05
 install Business::CreditCard 0.28
 install DBIx::DBSchema 0.27
 install HTML::Widgets:SelectLayers 0.05
 install Business::CreditCard 0.28
index 1a81c4b..bb30608 100644 (file)
@@ -14,6 +14,7 @@ use Time::Duration;
 use Tie::IxHash;
 use URI::Escape;
 use HTML::Entities;
 use Tie::IxHash;
 use URI::Escape;
 use HTML::Entities;
+use JSON;
 use IO::Handle;
 use IO::File;
 use IO::Scalar;
 use IO::Handle;
 use IO::File;
 use IO::Scalar;
@@ -47,7 +48,7 @@ use FS::cust_bill;
 use FS::cust_bill_pay;
 use FS::cust_credit;
 use FS::cust_credit_bill;
 use FS::cust_bill_pay;
 use FS::cust_credit;
 use FS::cust_credit_bill;
-use FS::cust_main;
+use FS::cust_main qw(smart_search);
 use FS::cust_main_county;
 use FS::cust_pay;
 use FS::cust_pkg;
 use FS::cust_main_county;
 use FS::cust_pay;
 use FS::cust_pkg;
index 737e5af..851b2e7 100644 (file)
@@ -99,6 +99,7 @@ sub handler
       use Tie::IxHash;
       use URI::Escape;
       use HTML::Entities;
       use Tie::IxHash;
       use URI::Escape;
       use HTML::Entities;
+      use JSON;
       use IO::Handle;
       use IO::File;
       use IO::Scalar;
       use IO::Handle;
       use IO::File;
       use IO::Scalar;
index fb51bfb..a4ed271 100644 (file)
@@ -9,6 +9,7 @@ If migrating from 1.5.7, see README.1.5.8 instead
 
 If migrating from 1.5.0pre6, see README.1.5.7 instead
 
 
 If migrating from 1.5.0pre6, see README.1.5.7 instead
 
+install JSON
 install DBD::Pg 1.32, 1.41 or later (not 1.40) (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with <a href="http://420.am/~ivan/DBD-Pg-1.22-fixvercmp.patch">this patch</a> and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm)
 install DBIx::DBSchema 0.26
 install Net::SSH 0.08
 install DBD::Pg 1.32, 1.41 or later (not 1.40) (or, if you're using a Perl version before 5.6, you could try installing DBD::Pg 1.22 with <a href="http://420.am/~ivan/DBD-Pg-1.22-fixvercmp.patch">this patch</a> and commenting out the "use DBD::Pg 1.32" at the top of DBIx/DBSchema/DBD/Pg.pm)
 install DBIx::DBSchema 0.26
 install Net::SSH 0.08
index 82a0281..b4b8506 100644 (file)
     <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
     <TR><TH BGCOLOR="#cccccc">Bookkeeping / Collections</TH></TR>
     <TR><TD>
     <TABLE CELLSPACING=2 CELLPADDING=0 BORDER=0 WIDTH="100%" BGCOLOR="#eeeeee">
     <TR><TH BGCOLOR="#cccccc">Bookkeeping / Collections</TH></TR>
     <TR><TD>
-      <BR><A HREF="search/cust_main-quickpay.html">Quick payment entry</A>
+      <BR><A HREF="misc/batch-cust_pay.html">Quick payment entry</A>
       <BR>
       <BR><FORM ACTION="search/cust_main.cgi" METHOD="GET">Credit card # <INPUT TYPE="hidden" NAME="card_on" VALUE="1"><INPUT TYPE="text" NAME="card"><INPUT TYPE="submit" VALUE="Search"></FORM>
       <FORM ACTION="search/cust_bill.html" METHOD="GET">Invoice # <INPUT TYPE="text" NAME="invnum" SIZE="8"><INPUT TYPE="submit" VALUE="Search"></FORM>
       <BR>
       <BR><FORM ACTION="search/cust_main.cgi" METHOD="GET">Credit card # <INPUT TYPE="hidden" NAME="card_on" VALUE="1"><INPUT TYPE="text" NAME="card"><INPUT TYPE="submit" VALUE="Search"></FORM>
       <FORM ACTION="search/cust_bill.html" METHOD="GET">Invoice # <INPUT TYPE="text" NAME="invnum" SIZE="8"><INPUT TYPE="submit" VALUE="Search"></FORM>
index b09876b..ac7f7ff 100644 (file)
 <%= header( 'Quick payment entry',
             menubar(
 <%= header( 'Quick payment entry',
             menubar(
-                     'Main Menu' => popurl(1),
+                     'Main Menu' => $p, #popurl(1),
+                     'Old-style quick payment entry' =>
+                       $p. 'search/cust_main-quickpay.html',
                    ),
                    ),
-            'onLoad="addRow()"',
+            ( $cgi->param('error') ? '' : 'onload="addRow()"' ),
           )
 %>
 
 <% if ( $cgi->param('error') ) { %>
           )
 %>
 
 <% if ( $cgi->param('error') ) { %>
-  <FONT SIZE="+1" COLOR="#ff0000">Error: <%= $cgi->param('error') %></FONT>
+  <FONT SIZE="+1" COLOR="#ff0000"><%= $cgi->param('error') %></FONT><BR><BR>
 <% } %>
 
 <% } %>
 
-<SCRIPT TYPE="text/javascript">
-
-  var rownum = 0;
 
 
-  function addRow() {
+<FORM ACTION="process/batch-cust_pay.cgi" NAME="OneTrueForm" METHOD="POST" onsubmit="document.OneTrueForm.submit.disabled=true;">
 
 
-    var table = document.getElementById('OneTrueTable');
-    var tablebody = table.getElementsByTagName('tbody').item(0);
+<!-- <B>Batch</B> <INPUT TYPE="text" NAME="paybatch"><BR><BR> -->
 
 
-    var row = document.createElement('TR');
-
-    var custnum_cell = document.createElement('TD');
-    var custnum_input = document.createElement('INPUT');
-    custnum_input.setAttribute('name', 'custnum'+rownum);
-    custnum_input.setAttribute('size', 8);
-    custnum_input.setAttribute('maxlength', 7);
-    custnum_input.setAttribute('rownum', rownum);
-    custnum_input.onchange = search_custnum;
-
-    custnum_cell.appendChild(custnum_input);
-    row.appendChild(custnum_cell);
-
-    var customer_cell = document.createElement('TD');
-    var customer_input = document.createElement('INPUT');
-    custnum_input.setAttribute('name', 'customer'+rownum);
-    customer_input.setAttribute('size', 32);
-    customer_input.setAttribute('value', '(last name or company)' );
-    customer_input.onfocus = clearhint;
-    customer_input.setAttribute('rownum', rownum);
-    customer_input.onchange = search_customer;
-    customer_cell.appendChild(customer_input);
-    row.appendChild(customer_cell);
+<SCRIPT TYPE="text/javascript">
 
 
-    var paid_cell = document.createElement('TD');
-    var paid_text = document.createTextNode('$');
-    var paid_input = document.createElement('INPUT');
-    paid_input.setAttribute('name', 'paid'+rownum);
-    paid_input.setAttribute('size', 8);
-    paid_input.setAttribute('maxlength', 8);
-    paid_cell.appendChild(paid_text);
-    paid_cell.appendChild(paid_input);
-    row.appendChild(paid_cell);
+  function clearhint_custnum() {
 
 
-    var payinfo_cell = document.createElement('TD');
-    var payinfo_input = document.createElement('INPUT');
-    payinfo_input.setAttribute('name', 'payinfo'+rownum);
-    payinfo_input.setAttribute('size', 10);
-    payinfo_cell.appendChild(payinfo_input);
-    row.appendChild(payinfo_cell);
+    //this.style.color = '#000000';
 
 
-    tablebody.appendChild(row);
-
-    rownum++;
+    if ( this.value == 'Not found' || this.value == 'Multiple' ) {
+      this.value = '';
+      this.style.color = '#000000';
+    }
 
   }
 
 
   }
 
-  function clearhint() {
+  function clearhint_customer() {
 
 
-    if ( this.value == '(last name or company)' )
+    this.style.color = '#000000';
+
+    if ( this.value == '(last name or company)' || this.value == 'Not found' )
       this.value = '';
 
   }
 
   function search_custnum() {
 
       this.value = '';
 
   }
 
   function search_custnum() {
 
+    this.style.color = '#000000'
+
+    var custnum_obj = this;
     var searchrow = this.getAttribute('rownum');
     var searchrow = this.getAttribute('rownum');
+    var custnum = this.value;
+
+    if ( custnum == 'searching...' || custnum == 'Not found' || custnum == '' )
+      return;
+
+    if ( this.getAttribute('magic') == 'nosearch' ) {
+      this.setAttribute('magic', '');
+      return;
+    }
+
+    if ( ( rownum - searchrow ) == 1 ) {
+      addRow();
+    }
+    var customer = document.getElementById('customer'+searchrow);
+    customer.value = 'searching...';
+    customer.disabled = true;
+    customer.style.color = '#000000';
+    customer.style.backgroundColor = '#dddddd';
+
+    var customer_select = document.getElementById('cust_select'+searchrow);
+
+    //alert('search for custnum ' + custnum + ', row#' + searchrow );
+
+    customer.style.display = '';
+    customer_select.style.display = 'none';
+
+    function search_custnum_update(name) {
+
+      var name = eval('(' + name + ')' );
+
+      customer.disabled = false;
+      customer.style.backgroundColor = '#ffffff';
+
+      if ( name.length > 0 ) {
+        //alert('custnum found: ' + name);
+        customer.value = name;
+        customer.setAttribute('magic', 'nosearch');
+      } else {
+        customer.value = 'Not found';
+        customer.style.color = '#ff0000';
+        custnum_obj.style.color = '#ff0000';
 
 
-    alert('search for custnum row#' + searchrow );
+      }
+
+    }
+
+    custnum_search( custnum, search_custnum_update );
 
   }
 
   function search_customer() {
 
 
   }
 
   function search_customer() {
 
+    var customer_obj = this;
     var searchrow = this.getAttribute('rownum');
     var searchrow = this.getAttribute('rownum');
+    var customer = this.value;
+
+    if ( customer == 'searching...' || customer == 'Not found' || customer == '' )
+      return;
+
+    if ( this.getAttribute('magic') == 'nosearch' ) {
+      this.setAttribute('magic', '');
+      return;
+    }
+
+    if ( ( rownum - searchrow ) == 1 ) {
+      addRow();
+    }
+
+    var custnum_obj = document.getElementById('custnum'+searchrow);
+    custnum_obj.value = 'searching...';
+    custnum_obj.disabled = true;
+    custnum_obj.style.color = '#000000';
+    custnum_obj.style.backgroundColor = '#dddddd';
+
+    var customer_select = document.getElementById('cust_select'+searchrow);
+
+    //alert('search for customer ' + customer + ', row#' + searchrow );
+
+    function search_customer_update(customers) {
+
+      //alert('customers returned: ' + customers);
+
+      var customerArray = eval('(' + customers + ')');
+
+      custnum_obj.disabled = false;
+      custnum_obj.style.backgroundColor = '#ffffff';
+
+      if ( customerArray.length == 0 ) {
+
+        custnum_obj.value = 'Not found';
+        custnum_obj.style.color = '#ff0000';
+        customer_obj.style.color = '#ff0000';
+
+        customer_obj.style.display = '';
+        customer_select.style.display = 'none';
+
+
+      } else if ( customerArray.length == 1 ) {
 
 
-    alert('search for customer row#' + searchrow );
+        //alert('one customer found: ' + customerArray[0]);
+
+        custnum_obj.value = customerArray[0][0];
+        customer_obj.value = customerArray[0][1];
+
+        customer_obj.style.display = '';
+        customer_select.style.display = 'none';
+
+
+      } else {
+
+        custnum_obj.value = 'Multiple'; // or something
+        custnum_obj.style.color = '#ff0000';
+
+        //alert('multiple customers found, have to create select dropdown');
+
+        //blank the current list
+        for ( var i = customer_select.length; i >= 0; i-- )
+          customer_select.options[i] = null;
+
+        opt(customer_select, '', 'Multiple customers match "' + customer + '" - select one', '#ff0000');
+
+        //add the multiple customers
+        for ( var s = 0; s < customerArray.length; s++ )
+          opt(customer_select, customerArray[s][0], customerArray[s][1], '#000000');
+
+        opt(customer_select, 'cancel', '(Edit search string)', '#000000');
+
+        customer_obj.style.display = 'none';
+
+        customer_select.style.display = '';
+
+      }
+
+    }
+
+    smart_search( customer, search_customer_update );
 
   }
 
 
   }
 
-</SCRIPT>
+  function select_customer() {
 
 
-<FORM ACTION="<%= $p %>process/batch-cust_pay.cgi" METHOD=POST>
+    var custnum = this.options[this.selectedIndex].value;
+    var customer = this.options[this.selectedIndex].text;
 
 
-<B>Batch <INPUT TYPE="text" NAME="paybatch"><BR><BR>
+    var searchrow = this.getAttribute('rownum');
+    var custnum_obj = document.getElementById('custnum'+searchrow);
+    var customer_obj = document.getElementById('customer'+searchrow);
+
+    if ( custnum == '' ) {
+      //this.style.color = '#ff0000';
+
+    } else if ( custnum == 'cancel' ) {
+
+      custnum_obj.value = '';
+      custnum_obj.style.color = '#000000';
+
+      this.style.display = 'none';
+      customer_obj.style.display = '';
+      customer_obj.focus();
+
+    } else {
+
+
+      custnum_obj.value = custnum;
+      custnum_obj.style.color = '#000000';
+
+      customer_obj.value = customer;
+      customer_obj.style.color = '#000000';
+
+      this.style.display = 'none';
+      customer_obj.style.display = '';
+
+    }
+
+  }
+
+  function opt(what,value,text,color) {
+    var optionName = new Option(text, value, false, false);
+    optionName.style.color = color;
+    var length = what.length;
+    what.options[length] = optionName;
+  }
+
+</SCRIPT>
 
 <TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
 
 
 <TABLE ID="OneTrueTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
 
   <TH>Customer</TH>
   <TH>Amount</TH>
   <TH>Check #</TH>
   <TH>Customer</TH>
   <TH>Amount</TH>
   <TH>Check #</TH>
+  <TH BGCOLOR="#e8e8e8"></TH>
 </TR>
 
 </TR>
 
-<!-- 
-<TR>
-  <TD>
-    <INPUT TYPE="text" NAME="custnum0" SIZE=8 MAXLENGTH=7 onChange="alert('search for custnum')" >
-  </TD>
-  <TD>
-    <INPUT TYPE="text" NAME="customer0" SIZE=32 VALUE="(last name or company)" onChange="alert('search for customer')" >
-  </TD>
-  <TD>
-    $<INPUT TYPE="text" NAME="paid0" SIZE=8 MAXLENGTH=8>
-  </TD>
-  <TD>
-    <INPUT TYPE="text" NAME="payinfo" SIZE=10>
-  </TD>
-</TR>
--->
+<% my $row = 0;
+   if ( $cgi->param('error') ) {
+     my $param = $cgi->Vars;
+%>
+
+  <% for ( $row = 0; exists($param->{"custnum$row"}); $row++ ) { %>
+
+    <TR>
+
+      <TD>
+        <INPUT TYPE="text" NAME="custnum<%= $row %>" ID="custnum<%= $row %>" SIZE=8 MAXLENGTH=12 VALUE="<%= $param->{"custnum$row"} %>" rownum="<%= $row %>">
+          <SCRIPT TYPE="text/javascript">
+            var custnum_input<%= $row %> = document.getElementById("custnum<%= $row %>");
+            custnum_input<%= $row %>.onfocus = clearhint_custnum;
+            custnum_input<%= $row %>.onchange = search_custnum;
+          </SCRIPT>
+      </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 = 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>
+        $<INPUT TYPE="text" NAME="paid<%= $row %>" SIZE=8 MAXLENGTH=8 VALUE="<%= $param->{"paid$row"} %>" >
+      </TD>
+
+      <TD>
+        <INPUT TYPE="text" NAME="payinfo<%= $row %>" SIZE=10 VALUE="<%= $param->{"payinfo$row"} %>" >
+      </TD>
+
+      <TD BGCOLOR="#e8e8e8">
+      <% if ( $param->{"error$row"} ) { %>
+        <FONT SIZE="-1" COLOR="#ff0000">Error: <%= $param->{"error$row"} %></FONT>
+      <% } %>
+      </TD>
+
+    </TR>
+
+  <% } %>
+
+<% } %>
 
 </TABLE>
 
 
 </TABLE>
 
-<BR>
-<INPUT TYPE="button" VALUE="TEST addrow" onClick="addRow()">
+<!-- <BR>
+<INPUT TYPE="button" VALUE="TEST addrow" onclick="addRow()"> -->
 
 <BR>
 
 <BR>
-<INPUT TYPE="submit" VALUE="Post payments">
+<INPUT TYPE="submit" NAME="submit" VALUE="Post payments">
 
 </FORM>
 
 </FORM>
+
+
+<%= include('/elements/xmlhttp.html',
+              'url'  => $p. 'misc/xmlhttp-cust_main-search.cgi',
+              'subs' => [qw( custnum_search smart_search )],
+           )
+%>
+
+<SCRIPT TYPE="text/javascript">
+
+  var rownum = <%= $row %>;
+
+  function addRow() {
+
+    var table = document.getElementById('OneTrueTable');
+    var tablebody = table.getElementsByTagName('tbody').item(0);
+
+    var row = document.createElement('TR');
+
+    var custnum_cell = document.createElement('TD');
+
+      var custnum_input = document.createElement('INPUT');
+      custnum_input.setAttribute('name', 'custnum'+rownum);
+      custnum_input.setAttribute('id',   'custnum'+rownum);
+      custnum_input.setAttribute('size', 8);
+      custnum_input.setAttribute('maxlength', 12);
+      custnum_input.setAttribute('rownum', rownum);
+      custnum_input.onfocus = clearhint_custnum;
+      custnum_input.onchange = search_custnum;
+      custnum_cell.appendChild(custnum_input);
+
+    row.appendChild(custnum_cell);
+
+    var customer_cell = document.createElement('TD');
+
+      var customer_input = document.createElement('INPUT');
+      customer_input.setAttribute('name', 'customer'+rownum);
+      customer_input.setAttribute('id',   'customer'+rownum);
+      customer_input.setAttribute('size', 64);
+      customer_input.setAttribute('value', '(last name or company)' );
+      customer_input.setAttribute('rownum', rownum);
+      customer_input.onfocus = clearhint_customer;
+      customer_input.onclick = clearhint_customer;
+      customer_input.onchange = search_customer;
+      customer_cell.appendChild(customer_input);
+
+      var customer_select = document.createElement('SELECT');
+      customer_select.setAttribute('name', 'cust_select'+rownum);
+      customer_select.setAttribute('id',   'cust_select'+rownum);
+      customer_select.setAttribute('rownum', rownum);
+      customer_select.style.color = '#ff0000';
+      customer_select.style.display = 'none';
+      customer_select.onchange = select_customer;
+      customer_cell.appendChild(customer_select);
+
+    row.appendChild(customer_cell);
+
+    var paid_cell = document.createElement('TD');
+
+      var paid_text = document.createTextNode('$');
+      paid_cell.appendChild(paid_text);
+
+      var paid_input = document.createElement('INPUT');
+      paid_input.setAttribute('name', 'paid'+rownum);
+      paid_input.setAttribute('size', 8);
+      paid_input.setAttribute('maxlength', 8);
+      paid_cell.appendChild(paid_input);
+
+    row.appendChild(paid_cell);
+
+    var payinfo_cell = document.createElement('TD');
+      var payinfo_input = document.createElement('INPUT');
+      payinfo_input.setAttribute('name', 'payinfo'+rownum);
+      payinfo_input.setAttribute('size', 10);
+      payinfo_cell.appendChild(payinfo_input);
+    row.appendChild(payinfo_cell);
+
+    var error_cell = document.createElement('TD');
+    error_cell.style.backgroundColor = '#e8e8e8';
+    row.appendChild(error_cell);
+
+    tablebody.appendChild(row);
+
+    rownum++;
+
+  }
+
+</SCRIPT>
+
 </BODY>
 </HTML>
 </BODY>
 </HTML>
diff --git a/httemplate/misc/process/batch-cust_pay.cgi b/httemplate/misc/process/batch-cust_pay.cgi
new file mode 100644 (file)
index 0000000..1cc6c3b
--- /dev/null
@@ -0,0 +1,40 @@
+<%
+  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++ ) {
+    push @cust_pay, new FS::cust_pay {
+                                       'custnum'  => $param->{"custnum$row"},
+                                       'paid'     => $param->{"paid$row"},
+                                       'payby'    => 'BILL',
+                                       'payinfo'  => $param->{"payinfo$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);
+
+  if ( $num_errors ) {
+
+    $cgi->param('error', "$num_errors error". ($num_errors>1 ? 's' : '') );
+
+    my $erow=0;
+    $cgi->param('error'. $erow++, shift @errors) while @errors;
+
+    %><%= $cgi->redirect($p.'batch-cust_pay.html?'. $cgi->query_string)
+
+  %><% } else {
+
+    %><%= $cgi->redirect(popurl(3). "search/cust_pay.cgi?magic=paybatch;paybatch=$paybatch") %>
+
+  <% } %>
diff --git a/httemplate/misc/xmlhttp-cust_main-search.cgi b/httemplate/misc/xmlhttp-cust_main-search.cgi
new file mode 100644 (file)
index 0000000..8dbd5a4
--- /dev/null
@@ -0,0 +1,21 @@
+<%
+   my $sub = $cgi->param('sub');
+   if ( $sub eq 'custnum_search' ) {
+     my $custnum = $cgi->param('arg');
+     my $cust_main = qsearchs('cust_main', { 'custnum' => $custnum } );
+
+     %>"<%= $cust_main ? $cust_main->name : '' %>"
+
+<% } elsif ( $sub eq 'smart_search' ) {
+
+     my $string = $cgi->param('arg');
+     my @cust_main = smart_search( 'search' => $string );
+     my $return = [ map [ $_->custnum, $_->name ], @cust_main ];
+
+     %><%= objToJson($return) %>
+
+<% } %>
+
+
index da3d125..89da742 100755 (executable)
@@ -1,60 +1,78 @@
 <%
    my $title = 'Payment Search Results';
    my( $count_query, $sql_query );
 <%
    my $title = 'Payment Search Results';
    my( $count_query, $sql_query );
-   if ( $cgi->param('magic') && $cgi->param('magic') eq '_date' ) {
-   
-     my @search = ();
+   if ( $cgi->param('magic') ) {
 
 
-     if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
-       push @search, "agentnum = $1"; # $search{'agentnum'} = $1;
-       my $agent = qsearchs('agent', { 'agentnum' => $1 } );
-       die "unknown agentnum $1" unless $agent;
-       $title = $agent->agent. " $title";
-     }
+     my @search = ();
+     my $orderby;
+     if ( $cgi->param('magic') eq '_date' ) {
    
    
-     if ( $cgi->param('payby') ) {
-       $cgi->param('payby') =~ /^(CARD|CHEK|BILL)(-(VisaMC|Amex|Discover))?$/
-         or die "illegal payby ". $cgi->param('payby');
-       push @search, "cust_pay.payby = '$1'";
-       if ( $3 ) {
-         if ( $3 eq 'VisaMC' ) {
-           #avoid posix regexes for portability
-           push @search,
-             " (    substring(cust_pay.payinfo from 1 for 1) = '4'  ".
-             "   OR substring(cust_pay.payinfo from 1 for 2) = '51' ".
-             "   OR substring(cust_pay.payinfo from 1 for 2) = '52' ".
-             "   OR substring(cust_pay.payinfo from 1 for 2) = '53' ".
-             "   OR substring(cust_pay.payinfo from 1 for 2) = '54' ".
-             "   OR substring(cust_pay.payinfo from 1 for 2) = '54' ".
-             "   OR substring(cust_pay.payinfo from 1 for 2) = '55' ".
-             " ) ";
-         } elsif ( $3 eq 'Amex' ) {
-           push @search,
-             " (    substring(cust_pay.payinfo from 1 for 2 ) = '34' ".
-             "   OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ".
-             " ) ";
-         } elsif ( $3 eq 'Discover' ) {
-           push @search,
-             " substring(cust_pay.payinfo from 1 for 4 ) = '6011' ";
-         } else {
-           die "unknown card type $3";
+  
+       if ( $cgi->param('agentnum') && $cgi->param('agentnum') =~ /^(\d+)$/ ) {
+         push @search, "agentnum = $1"; # $search{'agentnum'} = $1;
+         my $agent = qsearchs('agent', { 'agentnum' => $1 } );
+         die "unknown agentnum $1" unless $agent;
+         $title = $agent->agent. " $title";
+       }
+     
+       if ( $cgi->param('payby') ) {
+         $cgi->param('payby') =~ /^(CARD|CHEK|BILL)(-(VisaMC|Amex|Discover))?$/
+           or die "illegal payby ". $cgi->param('payby');
+         push @search, "cust_pay.payby = '$1'";
+         if ( $3 ) {
+           if ( $3 eq 'VisaMC' ) {
+             #avoid posix regexes for portability
+             push @search,
+               " (    substring(cust_pay.payinfo from 1 for 1) = '4'  ".
+               "   OR substring(cust_pay.payinfo from 1 for 2) = '51' ".
+               "   OR substring(cust_pay.payinfo from 1 for 2) = '52' ".
+               "   OR substring(cust_pay.payinfo from 1 for 2) = '53' ".
+               "   OR substring(cust_pay.payinfo from 1 for 2) = '54' ".
+               "   OR substring(cust_pay.payinfo from 1 for 2) = '54' ".
+               "   OR substring(cust_pay.payinfo from 1 for 2) = '55' ".
+               " ) ";
+           } elsif ( $3 eq 'Amex' ) {
+             push @search,
+               " (    substring(cust_pay.payinfo from 1 for 2 ) = '34' ".
+               "   OR substring(cust_pay.payinfo from 1 for 2 ) = '37' ".
+               " ) ";
+           } elsif ( $3 eq 'Discover' ) {
+             push @search,
+               " substring(cust_pay.payinfo from 1 for 4 ) = '6011' ";
+           } else {
+             die "unknown card type $3";
+           }
          }
        }
          }
        }
-     }
+  
+       my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
+       push @search, "_date >= $beginning ",
+                     "_date <= $ending";
+  
+       $orderby = '_date';
+   
+     } elsif ( $cgi->param('magic') eq 'paybatch' ) {
+
+       $cgi->param('paybatch') =~ /^([\w\/\:\-\.]+)$/
+         or die "illegal paybatch: ". $cgi->param('paybatch');
 
 
-     my($beginning, $ending) = FS::UI::Web::parse_beginning_ending($cgi);
-     push @search, "_date >= $beginning ",
-                   "_date <= $ending";
+       push @search, "paybatch = '$1'";
+
+       $orderby = "LOWER(company || ' ' || last || ' ' || first )";
+
+     } else {
+       die "unknown search magic: ". $cgi->param('magic');
+     }
 
      my $search = '';
      if ( @search ) {
        $search = ' WHERE '. join(' AND ', @search);
      }
 
      my $search = '';
      if ( @search ) {
        $search = ' WHERE '. join(' AND ', @search);
      }
-
+  
      $count_query = "SELECT COUNT(*), SUM(paid) ".
                     "FROM cust_pay LEFT JOIN cust_main USING ( custnum )".
                     $search;
      $count_query = "SELECT COUNT(*), SUM(paid) ".
                     "FROM cust_pay LEFT JOIN cust_main USING ( custnum )".
                     $search;
-   
+
      $sql_query = {
        'table'     => 'cust_pay',
        'select'    => join(', ',
      $sql_query = {
        'table'     => 'cust_pay',
        'select'    => join(', ',
                         FS::UI::Web::cust_sql_fields(),
                       ),
        'hashref'   => {},
                         FS::UI::Web::cust_sql_fields(),
                       ),
        'hashref'   => {},
-       'extra_sql' => "$search ORDER BY _date",
+       'extra_sql' => "$search ORDER BY $orderby",
        'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
      };
        'addl_from' => 'LEFT JOIN cust_main USING ( custnum )',
      };
-   
+
    } else {
    
      $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo";
    } else {
    
      $cgi->param('payinfo') =~ /^\s*(\d+)\s*$/ or die "illegal payinfo";