[freeside-commits] branch master updated. 09f899143460b0e99388ef007ff262f9a5e80203

Mark Wells mark at 420.am
Thu Jun 18 12:24:28 PDT 2015


The branch, master has been updated
       via  09f899143460b0e99388ef007ff262f9a5e80203 (commit)
      from  d928d26f1d623720d2f0c854a9d0b38210d66d2d (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 09f899143460b0e99388ef007ff262f9a5e80203
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Jun 17 19:06:38 2015 -0700

    make "credit lineitems" feature work with new tax workflow, #18676, #25718, #31639

diff --git a/FS/FS/cust_credit.pm b/FS/FS/cust_credit.pm
index f63d86f..01ee89d 100644
--- a/FS/FS/cust_credit.pm
+++ b/FS/FS/cust_credit.pm
@@ -705,6 +705,102 @@ sub credited_sql {
   unapplied_sql();
 }
 
+=item calculate_tax_adjustment PARAMS
+
+Calculate the amount of tax that needs to be credited as part of a lineitem
+credit.
+
+PARAMS must include:
+
+- billpkgnums: arrayref identifying the line items to credit
+- setuprecurs: arrayref of 'setup' or 'recur', indicating which part of
+  the lineitem charge is being credited
+- amounts: arrayref of the amounts to credit on each line item
+- custnum: the customer all of these invoices belong to, for error checking
+
+Returns a hash containing:
+- subtotal: the total non-tax amount to be credited (the sum of the 'amounts')
+- taxtotal: the total tax amount to be credited
+- taxlines: an arrayref of hashrefs for each tax line to be credited, each with:
+  - table: "cust_bill_pkg_tax_location" or "cust_bill_pkg_tax_rate_location"
+  - num: the key within that table
+  - credit: the credit amount to apply to that line
+
+=cut
+
+sub calculate_tax_adjustment {
+  my ($class, %arg) = @_;
+
+  my $error;
+  my @taxlines;
+  my $subtotal = 0;
+  my $taxtotal = 0;
+
+  my (%cust_bill_pkg, %cust_bill);
+
+  for (my $i = 0; ; $i++) {
+    my $billpkgnum = $arg{billpkgnums}[$i]
+      or last;
+    my $setuprecur = $arg{setuprecurs}[$i];
+    my $amount = $arg{amounts}[$i];
+    next if $amount == 0;
+    $subtotal += $amount;
+    my $cust_bill_pkg = $cust_bill_pkg{$billpkgnum}
+                    ||= FS::cust_bill_pkg->by_key($billpkgnum)
+      or die "lineitem #$billpkgnum not found\n";
+
+    my $invnum = $cust_bill_pkg->invnum;
+    $cust_bill{ $invnum } ||= FS::cust_bill->by_key($invnum);
+    $cust_bill{ $invnum}->custnum == $arg{custnum}
+      or die "lineitem #$billpkgnum not found\n";
+
+    # calculate credit ratio.
+    # (First deduct any existing credits applied to this line item, to avoid
+    # rounding errors.)
+    my $charged = $cust_bill_pkg->get($setuprecur);
+    my $previously_credited =
+      $cust_bill_pkg->credited( '', '', setuprecur => $setuprecur) || 0;
+
+    $charged -= $previously_credited;
+    if ($charged < $amount) {
+      $error = "invoice #$invnum: tried to credit $amount, but only $charged was charged";
+      last;
+    }
+    my $ratio = $amount / $charged;
+
+    # gather taxes that apply to the selected item
+    foreach my $table (
+      qw(cust_bill_pkg_tax_location cust_bill_pkg_tax_rate_location)
+    ) {
+      foreach my $tax_link (
+        qsearch($table, { taxable_billpkgnum => $billpkgnum })
+      ) {
+        my $tax_amount = $tax_link->amount;
+        # deduct existing credits applied to the tax, for the same reason as
+        # above
+        foreach ($tax_link->cust_credit_bill_pkg) {
+          $tax_amount -= $_->amount;
+        }
+        my $tax_credit = sprintf('%.2f', $tax_amount * $ratio);
+        my $pkey = $tax_link->get($tax_link->primary_key);
+        push @taxlines, {
+          table   => $table,
+          num     => $pkey,
+          credit  => $tax_credit,
+        };
+        $taxtotal += $tax_credit;
+
+      } #foreach cust_bill_pkg_tax_(rate_)?location
+    }
+  } # foreach $billpkgnum
+
+  return (
+    subtotal => sprintf('%.2f', $subtotal),
+    taxtotal => sprintf('%.2f', $taxtotal),
+    taxlines => \@taxlines,
+  );
+}
+
 =item credit_lineitems
 
 Example:
@@ -726,6 +822,8 @@ Example:
 
 =cut
 
+use Data::Dumper; #XXX
+
 #maybe i should just be an insert with extra args instead of a class method
 sub credit_lineitems {
   my( $class, %arg ) = @_;
@@ -784,8 +882,12 @@ sub credit_lineitems {
   my %cust_credit_bill = ();
   my %cust_bill_pkg = ();
   my %cust_credit_bill_pkg = ();
-  my %taxlisthash = ();
   my %unapplied_payments = (); #invoice numbers, and then billpaynums
+
+  # determine the tax adjustments
+  my %tax_adjust = $class->calculate_tax_adjustment(%arg);
+
+  warn Dumper \%arg;
   foreach my $billpkgnum ( @{$arg{billpkgnums}} ) {
     my $setuprecur = shift @{$arg{setuprecurs}};
     my $amount = shift @{$arg{amounts}};
@@ -799,22 +901,21 @@ sub credit_lineitems {
   
     my $invnum = $cust_bill_pkg->invnum;
 
-    if ( $setuprecur eq 'setup' ) {
-      $cust_bill_pkg->setup($amount);
-      $cust_bill_pkg->recur(0);
-      $cust_bill_pkg->unitrecur(0);
-      $cust_bill_pkg->type('');
-    } else {
-      $setuprecur = 'recur'; #in case its a usage classnum?
-      $cust_bill_pkg->recur($amount);
-      $cust_bill_pkg->setup(0);
-      $cust_bill_pkg->unitsetup(0);
-    }
-
     push @{$cust_bill_pkg{$invnum}}, $cust_bill_pkg;
 
-    #unapply any payments applied to this line item (other credits too?)
-    foreach my $cust_bill_pay_pkg ( $cust_bill_pkg->cust_bill_pay_pkg($setuprecur) ) {
+    $cust_credit_bill{$invnum} += $amount;
+    push @{ $cust_credit_bill_pkg{$invnum} },
+      new FS::cust_credit_bill_pkg {
+        'billpkgnum' => $billpkgnum,
+        'amount'     => sprintf('%.2f',$amount),
+        'setuprecur' => $setuprecur,
+        'sdate'      => $cust_bill_pkg->sdate,
+        'edate'      => $cust_bill_pkg->edate,
+      };
+    # unapply payments (but not other credits) from this line item
+    foreach my $cust_bill_pay_pkg (
+      $cust_bill_pkg->cust_bill_pay_pkg($setuprecur)
+    ) {
       $error = $cust_bill_pay_pkg->delete;
       if ( $error ) {
         $dbh->rollback if $oldAutoCommit;
@@ -823,24 +924,49 @@ sub credit_lineitems {
       $unapplied_payments{$invnum}{$cust_bill_pay_pkg->billpaynum}
         += $cust_bill_pay_pkg->amount;
     }
+  }
+
+  # do the same for taxes
+  foreach my $tax_credit ( @{ $tax_adjust{taxlines} } ) {
+    my $table = $tax_credit->{table};
+    my $tax_link = "FS::$table"->by_key( $tax_credit->{num} )
+      or die "tried to credit $table #$tax_credit->{num} but it doesn't exist";
+
+    my $billpkgnum = $tax_link->billpkgnum;
+    my $cust_bill_pkg = qsearchs({
+      'table'     => 'cust_bill_pkg',
+      'hashref'   => { 'billpkgnum' => $billpkgnum },
+      'addl_from' => 'LEFT JOIN cust_bill USING (invnum)',
+      'extra_sql' => 'AND custnum = '. $cust_main->custnum,
+    }) or die "unknown billpkgnum $billpkgnum";
+    
+    my $invnum = $cust_bill_pkg->invnum;
+    push @{$cust_bill_pkg{$invnum}}, $cust_bill_pkg;
 
-    #$subtotal += $amount;
+    my $amount = $tax_credit->{credit};
     $cust_credit_bill{$invnum} += $amount;
+
+    # create a credit application record to the tax line item, earmarked
+    # to the specific cust_bill_pkg_Xlocation
     push @{ $cust_credit_bill_pkg{$invnum} },
       new FS::cust_credit_bill_pkg {
-        'billpkgnum' => $cust_bill_pkg->billpkgnum,
-        'amount'     => sprintf('%.2f',$amount),
-        'setuprecur' => $setuprecur,
-        'sdate'      => $cust_bill_pkg->sdate,
-        'edate'      => $cust_bill_pkg->edate,
+        'billpkgnum' => $billpkgnum,
+        'amount'     => sprintf('%.2f', $amount),
+        'setuprecur' => 'setup',
+        $tax_link->primary_key, $tax_credit->{num}
       };
-
-    # recalculate taxes with new amounts
-    $taxlisthash{$invnum} ||= {};
-    if ( $cust_bill_pkg->pkgnum or $cust_bill_pkg->feepart ) {
-      $cust_main->_handle_taxes( $taxlisthash{$invnum}, $cust_bill_pkg );
-    } # otherwise the item itself is a tax, and assume the caller knows
-      # what they're doing
+    # unapply any payments from the tax
+    foreach my $cust_bill_pay_pkg (
+      $cust_bill_pkg->cust_bill_pay_pkg('setup')
+    ) {
+      $error = $cust_bill_pay_pkg->delete;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "Error unapplying payment: $error";
+      }
+      $unapplied_payments{$invnum}{$cust_bill_pay_pkg->billpaynum}
+        += $cust_bill_pay_pkg->amount;
+    }
   }
 
   ###
@@ -852,115 +978,6 @@ sub credit_lineitems {
 
   foreach my $invnum ( sort { $a <=> $b } keys %cust_credit_bill ) {
 
-    local $@;
-    my $arrayref_or_error = eval { $cust_main->calculate_taxes(
-        $cust_bill_pkg{$invnum}, # list of taxable items that we're crediting
-        $taxlisthash{$invnum},   # list of tax-item bindings
-        $cust_bill_pkg{$invnum}->[0]->cust_bill->_date, # invoice time
-      ) };
-
-    if ( $@ ) {
-      $dbh->rollback if $oldAutoCommit;
-      return "Error calculating taxes: $@";
-    }
-    
-    my %tax_links; # {tax billpkgnum}{nontax billpkgnum}
-
-    #taxes
-    foreach my $cust_bill_pkg ( @{ $cust_bill_pkg{$invnum} } ) {
-      my $billpkgnum = $cust_bill_pkg->billpkgnum;
-      my %hash = ( 'taxable_billpkgnum' => $billpkgnum );
-      # gather up existing tax links (we need their billpkgtaxlocationnums)
-      my @tax_links = qsearch('cust_bill_pkg_tax_location', \%hash),
-                      qsearch('cust_bill_pkg_tax_rate_location', \%hash);
-
-      foreach ( @tax_links ) {
-        $tax_links{$_->billpkgnum} ||= {};
-        $tax_links{$_->billpkgnum}{$_->taxable_billpkgnum} = $_;
-      }
-    }
-
-    foreach my $taxline ( @$arrayref_or_error ) {
-
-      my $amount = $taxline->setup;
-
-      # find equivalent tax line item on the existing invoice
-      my $tax_item = qsearchs('cust_bill_pkg', {
-          'invnum'    => $invnum,
-          'pkgnum'    => 0,
-          'itemdesc'  => $taxline->desc,
-      });
-      if (!$tax_item) {
-        # or should we just exit if this happens?
-        $cust_credit->set('amount', 
-          sprintf('%.2f', $cust_credit->get('amount') - $amount)
-        );
-        my $error = $cust_credit->replace;
-        if ( $error ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "error correcting credit for missing tax line: $error";
-        }
-      }
-
-      # but in the new era, we no longer have the problem of uniquely
-      # identifying the tax_Xlocation record.  The billpkgnums of the 
-      # tax and the taxed item are known.
-      foreach my $new_loc
-        ( @{ $taxline->get('cust_bill_pkg_tax_location') },
-          @{ $taxline->get('cust_bill_pkg_tax_rate_location') } )
-      {
-        # the existing tax_Xlocation object
-        my $old_loc =
-          $tax_links{$tax_item->billpkgnum}{$new_loc->taxable_cust_bill_pkg->billpkgnum};
-
-        next if !$old_loc; # apply the leftover amount nonspecifically
-
-        #support partial credits: use $amount if smaller
-        # (so just distribute to the first location?   perhaps should
-        #  do so evenly...)
-        my $loc_amount = min( $amount, $new_loc->amount);
-
-        $amount -= $loc_amount;
-
-        $cust_credit_bill{$invnum} += $loc_amount;
-        push @{ $cust_credit_bill_pkg{$invnum} },
-          new FS::cust_credit_bill_pkg {
-            'billpkgnum'                => $tax_item->billpkgnum,
-            'amount'                    => $loc_amount,
-            'setuprecur'                => 'setup',
-            'billpkgtaxlocationnum'     => $old_loc->billpkgtaxlocationnum,
-            'billpkgtaxratelocationnum' => $old_loc->billpkgtaxratelocationnum,
-          };
-
-      } #foreach my $new_loc
-
-      # we still have to deal with the possibility that the tax links don't
-      # cover the whole amount of tax because of an incomplete upgrade...
-      if ($amount > 0.005) {
-        $cust_credit_bill{$invnum} += $amount;
-        push @{ $cust_credit_bill_pkg{$invnum} },
-          new FS::cust_credit_bill_pkg {
-            'billpkgnum' => $tax_item->billpkgnum,
-            'amount'     => sprintf('%.2f', $amount),
-            'setuprecur' => 'setup',
-          };
-
-      } # if $amount > 0
-
-      #unapply any payments applied to the tax
-      foreach my $cust_bill_pay_pkg
-        ( $tax_item->cust_bill_pay_pkg('setup') )
-      {
-        $error = $cust_bill_pay_pkg->delete;
-        if ( $error ) {
-          $dbh->rollback if $oldAutoCommit;
-          return "Error unapplying payment: $error";
-        }
-        $unapplied_payments{$invnum}{$cust_bill_pay_pkg->billpaynum}
-          += $cust_bill_pay_pkg->amount;
-      }
-    } #foreach $taxline
-
     # if we unapplied any payments from line items, also unapply that 
     # amount from the invoice
     foreach my $billpaynum (keys %{$unapplied_payments{$invnum}}) {
diff --git a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
index 4558682..8f41776 100644
--- a/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
+++ b/httemplate/misc/xmlhttp-cust_bill_pkg-calculate_taxes.html
@@ -4,7 +4,7 @@
 my $curuser = $FS::CurrentUser::CurrentUser;
 die "access denied" unless $curuser->access_right('Credit line items');
 
-my $DEBUG = 0;
+my $DEBUG = 1;
 
 my $conf = new FS::Conf;
 
@@ -12,107 +12,31 @@ my $sub = $cgi->param('sub');
 
 my $return = {};
 
-if ( $sub eq 'calculate_taxes' ) {
+die "unknown sub '$sub'" if $sub ne 'calculate_taxes';
 
-  {
+my %arg = $cgi->param('arg');
+warn join('', map "$_: $arg{$_}\n", keys %arg )
+  if $DEBUG;
 
-    my %arg = $cgi->param('arg');
-    $return = \%arg;
-    warn join('', map "$_: $arg{$_}\n", keys %arg )
-      if $DEBUG;
+#some false laziness w/cust_credit::credit_lineitems
 
-    #some false laziness w/cust_credit::credit_lineitems
+my $cust_main = qsearchs({
+  'table'     => 'cust_main',
+  'hashref'   => { 'custnum' => $arg{custnum} },
+  'extra_sql' => ' AND '. $curuser->agentnums_sql,
+}) or die 'unknown customer';
 
-    my $cust_main = qsearchs({
-      'table'     => 'cust_main',
-      'hashref'   => { 'custnum' => $arg{custnum} },
-      'extra_sql' => ' AND '. $curuser->agentnums_sql,
-    }) or die 'unknown customer';
+$arg{billpkgnums} = [ split(',', $arg{billpkgnums}) ];
+$arg{setuprecurs} = [ split(',', $arg{setuprecurs}) ];
+$arg{amounts} =     [ split(',', $arg{amounts}) ];
 
-    my @billpkgnums = split(',', $arg{billpkgnums});
-    my @setuprecurs = split(',', $arg{setuprecurs});
-    my @amounts =     split(',', $arg{amounts});
+my %results = FS::cust_credit->calculate_tax_adjustment(%arg);
 
-    my @cust_bill_pkg = ();
-    my $taxlisthash = {};
-    while ( @billpkgnums ) {
-      my $billpkgnum = shift @billpkgnums;
-      my $setuprecur = shift @setuprecurs;
-      my $amount     = shift @amounts;
+$return = {
+  %arg,
+  %results
+};
 
-      my $cust_bill_pkg = qsearchs({
-        'table'     => 'cust_bill_pkg',
-        'hashref'   => { 'billpkgnum' => $billpkgnum },
-        'addl_from' => 'LEFT JOIN cust_bill USING (invnum)',
-        'extra_sql' => 'AND custnum = '. $cust_main->custnum,
-      }) or die "unknown billpkgnum $billpkgnum";
-
-      #shouldn't be passed# next if $cust_bill_pkg->pkgnum == 0;
-
-      if ( $setuprecur eq 'setup' ) {
-        $cust_bill_pkg->setup($amount);
-        $cust_bill_pkg->recur(0);
-        $cust_bill_pkg->unitrecur(0);
-        $cust_bill_pkg->type('');
-      } else {
-        $cust_bill_pkg->recur($amount);
-        $cust_bill_pkg->setup(0);
-        $cust_bill_pkg->unitsetup(0);
-      }
-
-      push @cust_bill_pkg, $cust_bill_pkg;
-
-      $cust_main->_handle_taxes( $taxlisthash, $cust_bill_pkg );
-    }
-
-    if ( @cust_bill_pkg ) {
-
-      my $listref_or_error = 
-        $cust_main->calculate_taxes( \@cust_bill_pkg, $taxlisthash, $cust_bill_pkg[0]->cust_bill->_date );
-
-      unless ( ref( $listref_or_error ) ) {
-        $return->{error} = $listref_or_error;
-        last;
-      }
-
-      my @taxlines = ();
-      my $taxtotal = 0;
-      $return->{taxlines} = \@taxlines;
-      foreach my $taxline ( @$listref_or_error ) {
-        my $amount = $taxline->setup;
-        my $desc = $taxline->desc;
-        foreach my $location (
-          @{$taxline->get('cust_bill_pkg_tax_location')},
-          @{$taxline->get('cust_bill_pkg_tax_rate_location')} )
-        {
-          my $taxlocnum = $location->locationnum || '';
-          my $taxratelocnum = $location->taxratelocationnum || '';
-          $location->cust_bill_pkg_desc($taxline->desc); #ugh @ that kludge
-          $taxtotal += $location->amount;
-          push @taxlines,
-            #[ $location->desc, $taxline->setup, $taxlocnum, $taxratelocnum ];
-            [ $location->desc, $location->amount, $taxlocnum, $taxratelocnum ];
-          $amount -= $location->amount;
-        }
-        if ($amount > 0) {
-          $taxtotal += $amount;
-          push @taxlines,
-            [ $taxline->itemdesc. ' (default)', sprintf('%.2f', $amount), '', '' ];
-        }
-      }
-
-      $return->{taxlines} = \@taxlines;
-      $return->{taxtotal} = sprintf('%.2f', $taxtotal);
-
-    } else {
-
-      $return->{taxlines} = [];
-      $return->{taxtotal} = '0.00';
-
-    }
-
-  }
-
-}
+warn Dumper $return if $DEBUG;
 
 </%init>

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

Summary of changes:
 FS/FS/cust_credit.pm                               |  291 +++++++++++---------
 .../xmlhttp-cust_bill_pkg-calculate_taxes.html     |  116 ++------
 2 files changed, 174 insertions(+), 233 deletions(-)




More information about the freeside-commits mailing list