fix internal handling of monthly tax exemptions, from #34223
[freeside.git] / FS / FS / TaxEngine / internal.pm
index f45bc08..91b547c 100644 (file)
@@ -3,6 +3,12 @@ package FS::TaxEngine::internal;
 use strict;
 use base 'FS::TaxEngine';
 use FS::Record qw(dbh qsearch qsearchs);
+use FS::Conf;
+use vars qw( $conf );
+
+FS::UID->install_callback(sub {
+    $conf = FS::Conf->new;
+});
 
 =head1 SUMMARY
 
@@ -63,6 +69,8 @@ sub taxline {
   my $taxable_cents = 0;
   my $tax_cents = 0;
 
+  my $round_per_line_item = $conf->exists('tax-round_per_line_item');
+
   my $cust_main = $self->{cust_main};
   my $custnum   = $cust_main->custnum;
   my $invoice_time = $self->{invoice_time};
@@ -235,7 +243,9 @@ sub taxline {
               exempt_monthly  => 'Y',
               year            => $year,
               month           => $mon,
+              taxnum          => $tax_object->taxnum,
             });
+
           $taxable_charged -= $addl;
         }
         # if they're using multiple months of exemption for a multi-month
@@ -249,16 +259,38 @@ sub taxline {
       }
     } # if exempt_amount
 
-    $_->taxnum($tax_object->taxnum) foreach @new_exemptions;
-
     # attach them to the line item
-    push @{ $cust_bill_pkg->cust_tax_exempt_pkg }, @new_exemptions;
+    foreach my $ex (@new_exemptions) {
+
+      if ( $cust_bill_pkg->billpkgnum ) {
+        # the exempted item is already inserted (it should be, these days) so
+        # insert the exemption record now:
+        $ex->set('billpkgnum', $cust_bill_pkg->billpkgnum);
+        my $error = $ex->insert;
+        return "inserting tax exemption record: $error" if $error;
+
+      } else {
+        # defer it until the item is inserted
+        push @{ $cust_bill_pkg->cust_tax_exempt_pkg }, $ex;
+      }
+    }
+
+    # and remember we've used the exemption
     push @existing_exemptions, @new_exemptions;
 
     $taxable_charged = sprintf( "%.2f", $taxable_charged);
     next if $taxable_charged == 0;
 
-    my $this_tax_cents = int($taxable_charged * $tax_object->tax);
+    my $this_tax_cents = $taxable_charged * $tax_object->tax;
+    if ( $round_per_line_item ) {
+      # Round the tax to the nearest cent for each line item, instead of
+      # across the whole invoice.
+      $this_tax_cents = sprintf('%.0f', $this_tax_cents);
+    } else {
+      # Otherwise truncate it so that rounding error is always positive.
+      $this_tax_cents = int($this_tax_cents);
+    }
+
     my $location = FS::cust_bill_pkg_tax_location->new({
         'taxnum'      => $tax_object->taxnum,
         'taxtype'     => ref($tax_object),
@@ -273,11 +305,18 @@ sub taxline {
     $tax_cents += $this_tax_cents;
   } #foreach $cust_bill_pkg
 
-  # now round and distribute
+  # calculate tax and rounding error for the whole group
   my $extra_cents = sprintf('%.2f', $taxable_cents * $tax_object->tax / 100)
                             * 100 - $tax_cents;
   # make sure we have an integer
   $extra_cents = sprintf('%.0f', $extra_cents);
+
+  # if we're rounding per item, then ignore that and don't distribute any
+  # extra cents.
+  if ( $round_per_line_item ) {
+    $extra_cents = 0;
+  }
+
   if ( $extra_cents < 0 ) {
     die "nonsense extra_cents value $extra_cents";
   }