X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FTaxEngine%2Fcch.pm;h=ccfb846fea5d6d5e0af3defa8d71ba591c71ca6d;hb=68546df9b125f73764eda31f1dcb4e2c0555f859;hp=4e6dbaf7e6e542b3d4978965abf71e546353ed3e;hpb=0fda4498e5b48587090b03d40ea97fec1e024385;p=freeside.git diff --git a/FS/FS/TaxEngine/cch.pm b/FS/FS/TaxEngine/cch.pm index 4e6dbaf7e..ccfb846fe 100644 --- a/FS/FS/TaxEngine/cch.pm +++ b/FS/FS/TaxEngine/cch.pm @@ -5,6 +5,7 @@ use vars qw( $DEBUG ); use base 'FS::TaxEngine'; use FS::Record qw(dbh qsearch qsearchs); use FS::Conf; +use List::Util qw(sum); =head1 SUMMARY @@ -122,7 +123,7 @@ sub make_taxlines { my @raw_taxlines; my %taxable_location; # taxable billpkgnum => cust_location - my %item_has_tax; # taxable billpkgnum => taxnum + my %item_has_tax; # taxable billpkgnum => charge class => taxnum foreach my $taxnum ( keys %{ $self->{taxes} } ) { my $tax_rate = FS::tax_rate->by_key($taxnum); my $taxables = $self->{taxes}{$taxnum}; @@ -131,32 +132,21 @@ sub make_taxlines { $taxable_location{ $_->billpkgnum } ||= $_->tax_location; } - my @taxlines = $tax_rate->taxline_cch( $taxables, $charge_classes ); - - next if !@taxlines; - if (!ref $taxlines[0]) { + foreach my $link ( $tax_rate->taxline_cch( $taxables, $charge_classes ) ) { + if (!ref $link) { # it's an error string - warn "error evaluating tax#$taxnum\n"; - return $taxlines[0]; - } - - my $billpkgnum = -1; # the current one - my $fragments; # $item_has_tax{$billpkgnum}{taxnum} - - foreach my $taxline (@taxlines) { - next if $taxline->setup == 0; + die "error evaluating tax#$taxnum: $link\n"; + } + next if $link->amount == 0; - my $link = $taxline->get('cust_bill_pkg_tax_rate_location')->[0]; # store this tax fragment, indexed by taxable item, then by taxnum - if ( $billpkgnum != $link->taxable_billpkgnum ) { - $billpkgnum = $link->taxable_billpkgnum; - $item_has_tax{$billpkgnum} ||= {}; - $fragments = $item_has_tax{$billpkgnum}{$taxnum} ||= []; - } + my $billpkgnum = $link->taxable_billpkgnum; + my $fragments = $item_has_tax{$billpkgnum}{$link->taxclass}{$taxnum} + ||= []; - $taxline->set('invnum', $cust_bill->invnum); - push @$fragments, $taxline; # so we can ToT it - push @raw_taxlines, $taxline; # so we actually bill it + push @raw_taxlines, $link; # this will go into final consolidation + push @$fragments, $link; # this will go into a temporary cust_bill_pkg + # for ToT calculation } } # foreach $taxnum @@ -166,38 +156,58 @@ sub make_taxlines { # taxes that apply to this item my $this_has_tax = $item_has_tax{$billpkgnum}; my $location = $taxable_location{$billpkgnum}; - foreach my $taxnum (keys %$this_has_tax) { - my $tax_rate = FS::tax_rate->by_key($taxnum); - # find all taxes that apply to it in this location - my @tot = $tax_rate->tax_on_tax( $location ); - next if !@tot; - - warn "found possible taxed taxnum $taxnum\n" - if $DEBUG > 2; - # Calculate ToT separately for each taxable item, and only if _that - # item_ is already taxed under the ToT. This is counterintuitive. - # See RT#5243. - foreach my $tot (@tot) { - my $totnum = $tot->taxnum; - warn "checking taxnum ".$tot->taxnum. - " which we call ". $tot->taxname ."\n" + + foreach my $charge_class (keys %$this_has_tax) { + # taxes that apply to this item and charge class + my $this_class_has_tax = $this_has_tax->{$charge_class}; + foreach my $taxnum (keys %$this_class_has_tax) { + + my $tax_rate = FS::tax_rate->by_key($taxnum); + # find all taxes that apply to it in this location + my @tot = $tax_rate->tax_on_tax( $location ); + next if !@tot; + + warn "found possible taxed taxnum $taxnum\n" if $DEBUG > 2; - if ( exists $this_has_tax->{ $totnum } ) { - warn "calculating tax on tax: taxnum ".$tot->taxnum." on $taxnum\n" - if $DEBUG; - my @taxlines = $tot->taxline_cch( - $this_has_tax->{ $taxnum }, # the first-stage tax (in an arrayref) - ); - next if (!@taxlines); # it didn't apply after all - if (!ref($taxlines[0])) { - warn "error evaluating TOT ($totnum on $taxnum)\n"; - return $taxlines[0]; - } - # add these to the taxline queue - push @raw_taxlines, @taxlines; - } # if $this_has_tax->{$totnum} - } # foreach my $tot (tax-on-tax rate definition) - } # foreach $taxnum (first-tier rate definition) + # Calculate ToT separately for each taxable item and class, and only + # if _that class on the item_ is already taxed under the ToT. This is + # counterintuitive. + # See RT#5243 and RT#36380. + my $temp_lineitem; + foreach my $tot (@tot) { + my $totnum = $tot->taxnum; + warn "checking taxnum ".$tot->taxnum. + " which we call ". $tot->taxname ."\n" + if $DEBUG > 2; + # note: if the _null class_ on this item is taxed under the ToT, + # then this specific class is taxed also (because null class + # includes all classes) and so ToT is applicable. + if ( + exists $this_class_has_tax->{ $totnum } + or exists $this_has_tax->{''}{ $totnum } + ) { + warn "calculating tax on tax: taxnum ".$tot->taxnum." on $taxnum\n" + if $DEBUG; + # construct a line item to calculate tax on + $temp_lineitem ||= FS::cust_bill_pkg->new({ + 'pkgnum' => 0, + 'invnum' => $cust_bill->invnum, + 'setup' => sum(map $_->amount, @{ $this_class_has_tax->{$taxnum} }), + 'recur' => 0, + 'itemdesc' => $tax_rate->taxname, + 'cust_bill_pkg_tax_rate_location' => $this_class_has_tax->{$taxnum}, + }); + my @new_taxlines = $tot->taxline_cch( [ $temp_lineitem ] ); + next if (!@new_taxlines); # it didn't apply after all + if (!ref($new_taxlines[0])) { + die "error evaluating TOT ($totnum on $taxnum): $new_taxlines[0]\n"; + } + # add these to the taxline queue + push @raw_taxlines, @new_taxlines; + } # if $this_has_tax->{$totnum} + } # foreach my $tot (tax-on-tax rate definition) + } # foreach $taxnum (first-tier rate definition) + } # foreach $charge_class } # foreach $taxable_item return @raw_taxlines;