UI changes for credit applications include on the fly tax calculations #4729
authorjeff <jeff>
Wed, 28 Oct 2009 19:01:18 +0000 (19:01 +0000)
committerjeff <jeff>
Wed, 28 Oct 2009 19:01:18 +0000 (19:01 +0000)
FS/FS/cust_bill_pkg.pm
FS/FS/cust_credit.pm
FS/FS/cust_main.pm
httemplate/edit/elements/ApplicationCommon.html
httemplate/edit/process/cust_credit_bill.cgi
httemplate/edit/process/elements/ApplicationCommon.html

index 4058f1f..016b8bf 100644 (file)
@@ -627,7 +627,8 @@ sub disintegrate {
   }
 
   #split usage from recur
-  my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage );
+  my $usage = sprintf( "%.2f", $cust_bill_pkg{recur}->usage )
+    if exists($cust_bill_pkg{recur});
   warn "usage is $usage\n" if $DEBUG > 1;
   if ($usage) {
     my $cust_bill_pkg_usage =
index 6c3effa..fda10de 100644 (file)
@@ -306,6 +306,9 @@ sub check {
 
   return "amount must be > 0 " if $self->amount <= 0;
 
+  return "amount must be greater or equal to amount applied"
+    if $self->unapplied < 0;
+
   return "Unknown customer"
     unless qsearchs( 'cust_main', { 'custnum' => $self->custnum } );
 
index 1c62849..700e15a 100644 (file)
@@ -2623,6 +2623,141 @@ sub bill {
 
   }
 
+  my $listref_or_error =
+    $self->calculate_taxes( \@cust_bill_pkg, \%taxlisthash, $invoice_time);
+
+  unless ( ref( $listref_or_error ) ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $listref_or_error;
+  }
+
+  foreach my $taxline ( @$listref_or_error ) {
+    $total_setup = sprintf('%.2f', $total_setup+$taxline->setup );
+    push @cust_bill_pkg, $taxline;
+  }
+
+  #add tax adjustments
+  warn "adding tax adjustments...\n" if $DEBUG > 2;
+  foreach my $cust_tax_adjustment (
+    qsearch('cust_tax_adjustment', { 'custnum'    => $self->custnum,
+                                     'billpkgnum' => '',
+                                   }
+           )
+  ) {
+
+    my $tax = sprintf('%.2f', $cust_tax_adjustment->amount );
+
+    my $itemdesc = $cust_tax_adjustment->taxname;
+    $itemdesc = '' if $itemdesc eq 'Tax';
+
+    push @cust_bill_pkg, new FS::cust_bill_pkg {
+      'pkgnum'      => 0,
+      'setup'       => $tax,
+      'recur'       => 0,
+      'sdate'       => '',
+      'edate'       => '',
+      'itemdesc'    => $itemdesc,
+      'itemcomment' => $cust_tax_adjustment->comment,
+      'cust_tax_adjustment' => $cust_tax_adjustment,
+      #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
+    };
+
+  }
+
+  my $charged = sprintf('%.2f', $total_setup + $total_recur );
+
+  my @cust_bill = $self->cust_bill;
+  my $balance = $self->balance;
+  my $previous_balance = scalar(@cust_bill)
+                           ? ( $cust_bill[$#cust_bill]->billing_balance || 0 )
+                           : 0;
+
+  $previous_balance += $cust_bill[$#cust_bill]->charged
+    if scalar(@cust_bill);
+  #my $balance_adjustments =
+  #  sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged);
+
+  #create the new invoice
+  my $cust_bill = new FS::cust_bill ( {
+    'custnum'             => $self->custnum,
+    '_date'               => ( $invoice_time ),
+    'charged'             => $charged,
+    'billing_balance'     => $balance,
+    'previous_balance'    => $previous_balance,
+    'invoice_terms'       => $options{'invoice_terms'},
+  } );
+  $error = $cust_bill->insert;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return "can't create invoice for customer #". $self->custnum. ": $error";
+  }
+
+  foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
+    $cust_bill_pkg->invnum($cust_bill->invnum); 
+    my $error = $cust_bill_pkg->insert;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "can't create invoice line item: $error";
+    }
+  }
+    
+
+  foreach my $hook ( @precommit_hooks ) { 
+    eval {
+      &{$hook}; #($self) ?
+    };
+    if ( $@ ) {
+      $dbh->rollback if $oldAutoCommit;
+      return "$@ running precommit hook $hook\n";
+    }
+  }
+  
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+  ''; #no error
+}
+
+=item calculate_taxes LINEITEMREF TAXHASHREF INVOICE_TIME
+
+This is a weird one.  Perhaps it should not even be exposed.
+
+Generates tax line items (see L<FS::cust_bill_pkg>) for this customer.
+Usually used internally by bill method B<bill>.
+
+If there is an error, returns the error, otherwise returns reference to a
+list of line items suitable for insertion.
+
+=over 4
+
+=item LINEITEMREF
+
+An array ref of the line items being billed.
+
+=item TAXHASHREF
+
+A strange beast.  The keys to this hash are internal identifiers consisting
+of the name of the tax object type, a space, and its unique identifier ( e.g.
+ 'cust_main_county 23' ).  The values of the hash are listrefs.  The first
+item in the list is the tax object.  The remaining items are either line
+items or floating point values (currency amounts).
+
+The taxes are calculated on this entity.  Calculated exemption records are
+transferred to the LINEITEMREF items on the assumption that they are related.
+
+Read the source.
+
+=item INVOICE_TIME
+
+This specifies the date appearing on the associated invoice.  Some
+jurisdictions (i.e. Texas) have tax exemptions which are date sensitive.
+
+=back
+
+=cut
+sub calculate_taxes {
+  my ($self, $cust_bill_pkg, $taxlisthash, $invoice_time) = @_;
+
+  my @tax_line_items = ();
+
   warn "having a look at the taxes we found...\n" if $DEBUG > 2;
 
   # keys are tax names (as printed on invoices / itemdesc )
@@ -2641,20 +2776,18 @@ sub bill {
   # values are listrefs of cust_bill_pkg_tax_rate_location hashrefs
   my %tax_rate_location = ();
 
-  foreach my $tax ( keys %taxlisthash ) {
-    my $tax_object = shift @{ $taxlisthash{$tax} };
+  foreach my $tax ( keys %$taxlisthash ) {
+    my $tax_object = shift @{ $taxlisthash->{$tax} };
     warn "found ". $tax_object->taxname. " as $tax\n" if $DEBUG > 2;
-    warn " ". join('/', @{ $taxlisthash{$tax} } ). "\n" if $DEBUG > 2;
+    warn " ". join('/', @{ $taxlisthash->{$tax} } ). "\n" if $DEBUG > 2;
     my $hashref_or_error =
-      $tax_object->taxline( $taxlisthash{$tax},
+      $tax_object->taxline( $taxlisthash->{$tax},
                             'custnum'      => $self->custnum,
                             'invoice_time' => $invoice_time
                           );
-    unless ( ref($hashref_or_error) ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $hashref_or_error;
-    }
-    unshift @{ $taxlisthash{$tax} }, $tax_object;
+    return $hashref_or_error unless ref($hashref_or_error);
+
+    unshift @{ $taxlisthash->{$tax} }, $tax_object;
 
     my $name   = $hashref_or_error->{'name'};
     my $amount = $hashref_or_error->{'amount'};
@@ -2694,9 +2827,9 @@ sub bill {
   }
 
   #move the cust_tax_exempt_pkg records to the cust_bill_pkgs we will commit
-  my %packagemap = map { $_->pkgnum => $_ } @cust_bill_pkg;
-  foreach my $tax ( keys %taxlisthash ) {
-    foreach ( @{ $taxlisthash{$tax} }[1 ... scalar(@{ $taxlisthash{$tax} })] ) {
+  my %packagemap = map { $_->pkgnum => $_ } @$cust_bill_pkg;
+  foreach my $tax ( keys %$taxlisthash ) {
+    foreach ( @{ $taxlisthash->{$tax} }[1 ... scalar(@{ $taxlisthash->{$tax} })] ) {
       next unless ref($_) eq 'FS::cust_bill_pkg';
 
       push @{ $packagemap{$_->pkgnum}->_cust_tax_exempt_pkg }, 
@@ -2726,7 +2859,6 @@ sub bill {
     next unless $tax;
 
     $tax = sprintf('%.2f', $tax );
-    $total_setup = sprintf('%.2f', $total_setup+$tax );
   
     my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
                                                    'disabled'     => '',
@@ -2745,7 +2877,7 @@ sub bill {
 
     }
 
-    push @cust_bill_pkg, new FS::cust_bill_pkg {
+    push @tax_line_items, new FS::cust_bill_pkg {
       'pkgnum'   => 0,
       'setup'    => $tax,
       'recur'    => 0,
@@ -2759,88 +2891,9 @@ sub bill {
 
   }
 
-  #add tax adjustments
-  warn "adding tax adjustments...\n" if $DEBUG > 2;
-  foreach my $cust_tax_adjustment (
-    qsearch('cust_tax_adjustment', { 'custnum'    => $self->custnum,
-                                     'billpkgnum' => '',
-                                   }
-           )
-  ) {
-
-    my $tax = sprintf('%.2f', $cust_tax_adjustment->amount );
-    $total_setup = sprintf('%.2f', $total_setup+$tax );
-
-    my $itemdesc = $cust_tax_adjustment->taxname;
-    $itemdesc = '' if $itemdesc eq 'Tax';
-
-    push @cust_bill_pkg, new FS::cust_bill_pkg {
-      'pkgnum'      => 0,
-      'setup'       => $tax,
-      'recur'       => 0,
-      'sdate'       => '',
-      'edate'       => '',
-      'itemdesc'    => $itemdesc,
-      'itemcomment' => $cust_tax_adjustment->comment,
-      'cust_tax_adjustment' => $cust_tax_adjustment,
-      #'cust_bill_pkg_tax_location' => \@cust_bill_pkg_tax_location,
-    };
-
-  }
-
-  my $charged = sprintf('%.2f', $total_setup + $total_recur );
-
-  my @cust_bill = $self->cust_bill;
-  my $balance = $self->balance;
-  my $previous_balance = scalar(@cust_bill)
-                           ? ( $cust_bill[$#cust_bill]->billing_balance || 0 )
-                           : 0;
-
-  $previous_balance += $cust_bill[$#cust_bill]->charged
-    if scalar(@cust_bill);
-  #my $balance_adjustments =
-  #  sprintf('%.2f', $balance - $prior_prior_balance - $prior_charged);
-
-  #create the new invoice
-  my $cust_bill = new FS::cust_bill ( {
-    'custnum'             => $self->custnum,
-    '_date'               => ( $invoice_time ),
-    'charged'             => $charged,
-    'billing_balance'     => $balance,
-    'previous_balance'    => $previous_balance,
-    'invoice_terms'       => $options{'invoice_terms'},
-  } );
-  $error = $cust_bill->insert;
-  if ( $error ) {
-    $dbh->rollback if $oldAutoCommit;
-    return "can't create invoice for customer #". $self->custnum. ": $error";
-  }
-
-  foreach my $cust_bill_pkg ( @cust_bill_pkg ) {
-    $cust_bill_pkg->invnum($cust_bill->invnum); 
-    my $error = $cust_bill_pkg->insert;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "can't create invoice line item: $error";
-    }
-  }
-    
-
-  foreach my $hook ( @precommit_hooks ) { 
-    eval {
-      &{$hook}; #($self) ?
-    };
-    if ( $@ ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "$@ running precommit hook $hook\n";
-    }
-  }
-  
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
-  ''; #no error
+  \@tax_line_items;
 }
 
-
 sub _make_lines {
   my ($self, %params) = @_;
 
index b46a3c8..181430c 100644 (file)
@@ -39,10 +39,12 @@ Examples:
   )
 
 </%doc>
-<% include('/elements/header-popup.html', "Apply $src_thing$to" ) %>
+
+<% include('/elements/header-popup.html', "Apply $src_thing$to", '', 'onLoad="myOnLoadFunction();"') %>
 
 <% include('/elements/error.html') %>
 
+<P ID="ErrorMessage"></P>
 <FORM ACTION="<% $p1. $opt{'form_action'} %>" NAME="ApplicationForm" ID="ApplicationForm" METHOD=POST>
 
 <% $src_thing %> #<B><% $src_pkeyvalue %></B><BR>
@@ -57,18 +59,26 @@ Examples:
 
 <TR>
   <TD ALIGN="right">Amount: </TD>
-  <TD><B><% $money_char %><% $src->amount %></B></TD>
+  <TD ID="original_amount"><B><% $money_char %><% $src_amount %></B>
+  </TD>
+  <TD>
+% if ($use_sub_dst_thing && $can_change_credit) {
+    <INPUT TYPE="hidden" NAME="src_amount" VALUE="<% $src_amount %>" >
+    <BUTTON TYPE="button" NAME="expand_button" ID="expand_button" onClick="do_change_amount(this);">Change</BUTTON>
+% }
+  </TD>
+
 </TR>
 
 <TR>
   <TD ALIGN="right">Unapplied amount: </TD>
-  <TD><B><% $money_char %><% $unapplied %></B></TD>
+  <TD ID="unapplied_amount"><B><% $money_char %><% $unapplied %></B></TD>
 </TR>
 
 % if ( $src_table eq 'cust_credit' ) {
     <TR>
       <TD ALIGN="right">Reason: </TD>
-      <TD><B><% $src->reason %></B></TD>
+      <TD COLSPAN=2><B><% $src->reason %></B></TD>
     </TR>
 % }
 
@@ -81,10 +91,16 @@ function changed(what) {
 
   if ( dst == '' ) {
     what.form.submit.disabled=true;
+%if ($src_pkey eq 'crednum') {
+    what.form.tax_button.disabled=true;
+%}
     return true;
   }
 
   what.form.submit.disabled=false;
+%if ($src_pkey eq 'crednum') {
+  what.form.tax_button.disabled=false;
+%}
 
 % foreach my $dst ( @dst ) {
 
@@ -120,14 +136,14 @@ function changed(what) {
 %             my $owed = $taxX->owed;
 %             my $key = &{$key_generator}([ $cbp, 0, { $pkey => $taxX->$pkey } ]);
 %             my $toapp = exists($apphash{ $key }) ? $apphash{ $key }->[1] : 0;
-              <% &{$row_generator}( $cbp, $taxX->desc, $owed, $toapp, $taxX->$pkey ) %>
+              <% &{$row_generator}( $key, $cbp, $taxX->desc, $owed, $toapp, $taxX->$pkey ) %>
 %             $total_owed -= $owed;
 %             $amount -= $toapp;
 %           }
 %           $desc .= ' (default)';
 %         }
 %         if ( $total_owed > 0 ) {
-            <% &{$row_generator}($cbp, $desc, $total_owed, $amount, '') %>
+            <% &{$row_generator}($key, $cbp, $desc, $total_owed, $amount, '') %>
 %         }
 %       }
 %     }
@@ -143,17 +159,156 @@ function sub_changed(what) {
   var table = document.getElementById('ApplicationTable');
   var i = table.rows.length;
   while(i-- > 2) {
-    var amount_input = table.rows[i].getElementsByTagName('input').item(0);
-    amount += parseFloat( amount_input.value ) || 0; 
+    var inputs = table.rows[i].getElementsByTagName('input');
+    if (! inputs.length) {
+      continue;
+    }
+    amount += parseFloat( inputs.item(0).value ) || 0; 
   }
   what.form.amount.value = parseFloat(amount).toFixed(2);
   what.form.display_amount.value = parseFloat(amount).toFixed(2);
+  set_amount_color(what);
 
 }
+
+function set_amount_color(what) {
+  if (what.form.src_amount.value < what.form.amount.value) {
+    what.form.display_amount.style.color = '#ff0000';
+  } else {
+    what.form.display_amount.style.color = '#00ff00';
+  }
+}
+
 </SCRIPT>
 
 Apply to:
 
+% if ($use_sub_dst_thing && $src_pkey eq 'crednum') {
+<CENTER><BUTTON TYPE="button" NAME="tax_button" ID="tax_button" onClick="do_calculate_tax(this);" DISABLED>Calculate Tax</BUTTON></CENTER>
+<% include( '/elements/xmlhttp.html',
+            'url' =>  $p.'misc/xmlhttp-calculate_taxes.html',
+            'subs' => [ 'calculate_taxes' ],
+          )
+ %>
+<SCRIPT TYPE="text/javascript">
+
+function show_taxes(arg) {
+  var argsHash = eval('(' + arg + ')');
+
+  var button = document.getElementById('tax_button');
+  button.disabled = false;
+  button.innerHTML = 'Calculate Tax';
+
+  var error = argsHash['error'];
+
+  var paragraph = document.getElementById('ErrorMessage');
+  if (error) {
+    paragraph.innerHTML = 'Error: ' + error;
+    paragraph.style.color = '#ff0000';
+  } else {
+    paragraph.innerHTML = '';
+  }
+  var taxlines = argsHash['taxlines'];
+
+  var table = document.getElementById('ApplicationTable');
+
+  var aFoundRow = 0;
+  for (i = 0; taxlines[i]; i++) {
+    var itemdesc = taxlines[i][0];
+    var locnum   = taxlines[i][2];
+    if (taxlines[i][3]) {
+      locnum  = taxlines[i][3];
+    }
+
+    var found = 0;
+    for (var row = 2; table.rows[row]; row++) {
+      var inputs = table.rows[row].getElementsByTagName('input');
+      if (! inputs.length) {
+        while ( table.rows[row] ) {
+           table.deleteRow(row);
+        }
+        break;
+      }
+      if ( inputs.item(4).value == itemdesc && inputs.item(2).value == locnum )
+      {
+        inputs.item(0).value = taxlines[i][1];
+        aFoundRow = found = row;
+        break;
+      }
+    }
+    if (! found) {
+      var row = table.insertRow(table.rows.length);
+      var warning_cell = document.createElement('TD');
+      warning_cell.style.color = '#ff0000';
+      warning_cell.colSpan = 2;
+      warning_cell.innerHTML = 'Calculated Tax - ' + itemdesc + ' - ' +
+                               taxlines[i][1] + ' will not be applied';
+      row.appendChild(warning_cell);
+    }
+  }
+
+  if (aFoundRow) {
+    sub_changed(table.rows[aFoundRow].getElementsByTagName('input').item(0));
+  }
+    
+}
+
+function do_calculate_tax (what) {
+  what.innerHTML = 'Calculating....';
+  what.disabled = true;
+  var taxed_items = new Array();
+  var table = document.getElementById('ApplicationTable');
+  for (var row = 2; table.rows[row]; row++)
+  {
+    var inputs = table.rows[row].getElementsByTagName('input');
+    if ( !inputs.length ) {
+      break;
+    }
+    var taxed_item = new Array(
+      inputs.item(1).value, // billpkgnum
+      inputs.item(3).value, // s_or_r
+      inputs.item(0).value  // amount
+    );
+    taxed_items.push(taxed_item);
+  }
+
+  var args = new Array(
+    'crednum', '<% $src_pkeyvalue %>',
+    'items', taxed_items
+  );
+  calculate_taxes ( args, show_taxes );
+}
+
+function do_change_amount (what) {
+  var amount_cell = document.getElementById('original_amount');
+  var inputs = amount_cell.getElementsByTagName('input');
+  if (inputs.length) {
+    amount_cell.innerHTML = '<B><% $money_char %></B>' + inputs.item(0).value;
+  } else {
+    amount_cell.innerHTML = '<% $money_char %>';
+    var amount_input = document.createElement('INPUT');
+    amount_input.setAttribute('name', 'entered_amount');
+    amount_input.setAttribute('id',   'entered_amount');
+    amount_input.style.textAlign = 'right';
+    amount_input.setAttribute('size', 8);
+    amount_input.setAttribute('maxlength', 8);
+    amount_input.setAttribute('value', what.form.src_amount.value);
+    amount_input.setAttribute('onChange', "src_amount_changed(this);");
+    amount_cell.appendChild(amount_input);
+  }
+}
+
+function src_amount_changed (what) {
+  what.form.src_amount.value = what.value;
+  var unapplied_cell = document.getElementById('unapplied_amount');
+  unapplied_cell.innerHTML = '<B><% $money_char %>' + what.value + '</B>';
+  set_amount_color(what);
+}
+
+</SCRIPT>
+
+%}
+
 <TABLE ID="ApplicationTable" BGCOLOR="#cccccc" BORDER=0 CELLSPACING=0>
 
 <TR>
@@ -173,7 +328,7 @@ Apply to:
   <TD ALIGN="right">Amount: </TD>
   <TD><% $money_char %><INPUT TYPE="text" NAME="<% $use_sub_dst_thing ? 'display_' : '' %>amount" VALUE="<% $amount %>" SIZE=8 MAXLENGTH=8 <% $use_sub_dst_thing ? 'DISABLED' : '' %> STYLE="text-align:right;"></TD>
 % if ($use_sub_dst_thing) {
-  <INPUT TYPE="hidden" NAME="amount" VALUE="<% $amount %>" >
+    <INPUT TYPE="hidden" NAME="amount" VALUE="<% $amount %>" >
 % }
 </TR>
 
@@ -184,6 +339,14 @@ Apply to:
 
 </FORM>
 
+<SCRIPT TYPE="text/javascript">
+
+function myOnLoadFunction () {
+  <% $onload %>
+}
+
+</SCRIPT>
+
 <% include('/elements/footer.html') %>
 
 <%init>
@@ -209,13 +372,21 @@ my $use_sub_dst_thing = 0;
 $use_sub_dst_thing = 1
   if ( $dst_table eq 'cust_bill' && $conf->exists("${link_table}_pkg-manual") );
 
+my $can_change_credit = 0;
+$can_change_credit = 1
+  if ( $src_table eq 'cust_credit' && 
+       $FS::CurrentUser::CurrentUser->access_right('Post credit') &&
+       $FS::CurrentUser::CurrentUser->access_right('Delete credit')
+     );
+
 my $to = $dst_table eq 'cust_refund' ? ' to Refund' : '';
 
-my($src_pkeyvalue, $amount, $dst_pkeyvalue);
+my($src_pkeyvalue, $amount, $dst_pkeyvalue, $src_amount);
 if ( $cgi->param('error') ) {
   $src_pkeyvalue = $cgi->param($src_pkey);
   $amount    = $cgi->param('amount');
   $dst_pkeyvalue    = $cgi->param($dst_pkey);
+  $src_amount = $cgi->param('src_amount');
 } else {
   my($query) = $cgi->keywords;
   $query =~ /^(\d+)$/;
@@ -231,6 +402,8 @@ my $p1 = popurl(1);
 my $src = qsearchs($src_table, { $src_pkey => $src_pkeyvalue } );
 die "$src_thing $src_pkeyvalue not found!" unless $src;
 
+$src_amount = $src->amount unless $cgi->param('error');
+
 my $unapplied = $src->unapplied;
 
 my @dst = sort {    $a->_date     <=> $b->_date
@@ -240,9 +413,11 @@ my @dst = sort {    $a->_date     <=> $b->_date
           qsearch($dst_table, { 'custnum' => $src->custnum } );
 
 my $row_generator = sub {
-  my ($cust_bill_pkg, $desc, $owed, $amount, $taxXnum) = @_;
+  my ($key, $cust_bill_pkg, $desc, $owed, $amount, $taxXnum) = @_;
+  my ($num, $s_or_r, $taxlinenum) = split(':', $key);
   my $id = $cust_bill_pkg->pkgnum || 'Tax';
   my $billpkgnum = $cust_bill_pkg->billpkgnum;
+  my $s_or_r = $cust_bill_pkg->setup > 0 ? 'setup' : 'recur';
 
   $amount = sprintf("%.2f", $amount);
   qq!
@@ -277,6 +452,20 @@ my $row_generator = sub {
       taxnum_input.setAttribute('rownum', rownum);
       taxnum_input.setAttribute('value', "$taxXnum");
       amount_cell.appendChild(taxnum_input);
+      var s_or_r_input = document.createElement('INPUT');
+      s_or_r_input.setAttribute('name', 's_or_r'+rownum);
+      s_or_r_input.setAttribute('id',   's_or_r'+rownum);
+      s_or_r_input.setAttribute('type', 'hidden');
+      s_or_r_input.setAttribute('rownum', rownum);
+      s_or_r_input.setAttribute('value', "$s_or_r");
+      amount_cell.appendChild(s_or_r_input);
+      var itemdesc_input = document.createElement('INPUT');
+      itemdesc_input.setAttribute('name', 'itemdesc'+rownum);
+      itemdesc_input.setAttribute('id',   'itemdesc'+rownum);
+      itemdesc_input.setAttribute('type', 'hidden');
+      itemdesc_input.setAttribute('rownum', rownum);
+      itemdesc_input.setAttribute('value', "$desc");
+      amount_cell.appendChild(itemdesc_input);
       row.appendChild(pkg_cell);
       row.appendChild(amount_cell);
       rownum++;
@@ -294,4 +483,24 @@ my $key_generator = sub {
   join(':', $cust_bill_pkg->billpkgnum, $setup_or_recur, $taxlinenum);
 };
 
+my $onload = 'return true;';
+
+if ($cgi->param('error')) {
+
+  my $set_sub_amounts =
+    join(';', map { "myform.subamount$_.value = ". $cgi->param("subamount$_") }
+               grep { /.+/ }
+               map { /^subnum(\d+)$/ ? $1 : '' }
+               $cgi->param
+  );
+  $set_sub_amounts &&= "$set_sub_amounts;sub_changed(myform.subamount0)";
+
+  $onload = qq!
+    var myform = document.getElementById('ApplicationForm');
+    changed(myform.elements['$dst_pkey']);
+    $set_sub_amounts;
+    return true;
+  !;
+}
+
 </%init>
index c0f34ae..d3847dc 100755 (executable)
 die "access denied"
   unless $FS::CurrentUser::CurrentUser->access_right('Apply credit');
 
+if ( $cgi->param('src_amount') ) {
+  die "access denied"
+    unless ( $FS::CurrentUser::CurrentUser->access_right('Post credit') &&
+           $FS::CurrentUser::CurrentUser->access_right('Delete credit') );
+}
+
 </%init>
index 3cb7ae6..c7bdd3e 100644 (file)
@@ -34,6 +34,8 @@ Examples:
 
 my %opt = @_;
 
+my $error = '';
+
 my $src_thing = ucfirst($opt{'src_thing'});
 my $src_table = $opt{'src_table'};
 my $src_pkey = dbdef->table($src_table)->primary_key;
@@ -58,6 +60,10 @@ my @subitems = map { [ $cgi->param("subnum$_"), $cgi->param("subamount$_"), $cgi
 
 my %options = ();
 $options{subitems} = \@subitems if scalar(@subitems);
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+my $dbh = dbh;
  
 my $new;
 #  $new = new FS::cust_refund ( {
@@ -70,6 +76,11 @@ my $new;
 #  } );
 #} else {
 
+  if ($src->amount != $cgi->param('src_amount')) {
+    $src->amount($cgi->param('src_amount'));
+    $error = $src->replace;
+  }
+
   my $class = 'FS::'. $opt{link_table};
 
   $new = $class->new( {
@@ -82,6 +93,11 @@ my $new;
 
 
 $options{manual} = 1;
-my $error = $new->insert( %options );
+$error ||= $new->insert( %options );
 
+if ($error) {
+  $dbh->rollback if $oldAutoCommit;
+} else {
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
 </%init>