X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2FTaxEngine.pm;h=e92bf768c7a836217070e583bb291bb393bff4bf;hb=e75717bd49c68a49940ebd668368a6542097a234;hp=a146c54d1af6ebcf4761d702aac3d862486a8b14;hpb=7516e3da0f17eeecba27219ef96a8b5f46af2083;p=freeside.git
diff --git a/FS/FS/TaxEngine.pm b/FS/FS/TaxEngine.pm
index a146c54d1..e92bf768c 100644
--- a/FS/FS/TaxEngine.pm
+++ b/FS/FS/TaxEngine.pm
@@ -14,49 +14,67 @@ FS::TaxEngine - Base class for tax calculation engines.
=head1 USAGE
1. At the start of creating an invoice, create an FS::TaxEngine object.
-2. Each time a sale item is added to the invoice, call C on the
+2. Each time a sale item is added to the invoice, call L on the
TaxEngine.
-
-- If the TaxEngine is "batch" style (Billsoft):
3. Set the "pending" flag on the invoice.
4. Insert the invoice and its line items.
+
+- If the TaxEngine is "batch" style (Billsoft):
5. After creating all invoices for the day, call
FS::TaxEngine::process_tax_batch. This will create the tax items for
all of the pending invoices, clear the "pending" flag, and call
- C on each of the billed customers.
+ L on each of the billed customers.
- If not (the internal tax system, CCH):
-3. After adding all sale items, call C on the TaxEngine to
+5. After adding all sale items, call L on the TaxEngine to
produce a list of tax line items.
-4. Append the tax line items to the invoice.
-5. Insert the invoice.
+6. Append the tax line items to the invoice.
+7. Update the invoice with the new charged amount and clear the pending flag.
=head1 CLASS METHODS
=over 4
+=item class
+
+Returns the class name for tax engines, according to the 'tax_data_vendor'
+configuration setting.
+
+=cut
+
+sub class {
+ my $conf = FS::Conf->new;
+ my $subclass = $conf->config('tax_data_vendor') || 'internal';
+ my $class = "FS::TaxEngine::$subclass";
+ local $@;
+ eval "use $class";
+ die "couldn't load $class: $@\n" if $@;
+
+ $class;
+}
+
=item new 'cust_main' => CUST_MAIN, 'invoice_time' => TIME, OPTIONS...
Creates an L object. The subclass will be chosen by the
-'enable_taxproducts' configuration setting.
+'tax_data_vendor' configuration setting.
-CUST_MAIN and TIME are required. OPTIONS can include "cancel" => 1 to
-indicate that the package is being billed on cancellation.
+CUST_MAIN and TIME are required. OPTIONS can include:
+
+"cancel" => 1 to indicate that the package is being billed on cancellation.
+
+"estimate" => 1 to indicate that this calculation is for tax estimation,
+and isn't an actual sale invoice, in case that matters.
=cut
sub new {
my $class = shift;
my %opt = @_;
+ my $conf = FS::Conf->new;
if ($class eq 'FS::TaxEngine') {
- my $conf = FS::Conf->new;
- my $subclass = $conf->config('enable_taxproducts') || 'internal';
- $class .= "::$subclass";
- local $@;
- eval "use $class";
- die "couldn't load $class: $@\n" if $@;
+ $class = $class->class;
}
- my $self = { items => [], taxes => {}, %opt };
+ my $self = { items => [], taxes => {}, conf => $conf, %opt };
bless $self, $class;
}
@@ -84,33 +102,49 @@ Returns a hashref of metadata about this tax method, including:
Adds the CUST_BILL_PKG object as a taxable sale on this invoice.
-=item calculate_taxes CUST_BILL
+=item calculate_taxes INVOICE
Calculates the taxes on the taxable sales and returns a list of
-L objects to add to the invoice. There is a base
-implementation of this, which calls the C method to calculate
-each individual tax.
+L objects to add to the invoice. The base implementation
+is to call L to produce a list of "raw" tax line items,
+then L to combine those with the same itemdesc.
+
+If this fails, it will throw an exception. (Accordingly it should not trap
+exceptions from internal methods that it calls, except to translate error
+messages into a more meaningful form.) If it succeeds, it MUST return an
+arrayref (even if the arrayref is empty).
=cut
sub calculate_taxes {
my $self = shift;
- my $conf = FS::Conf->new;
-
my $cust_bill = shift;
- my @tax_line_items;
- # keys are tax names (as printed on invoices / itemdesc )
- # values are arrayrefs of taxlines
- my %taxname;
+ my @raw_taxlines = $self->make_taxlines($cust_bill);
+ if ( !@raw_taxlines ) {
+ return;
+ } elsif ( !ref $raw_taxlines[0] ) { # error message
+ #this isn't actually handled by our caller... better for make_taxlines to
+ # die, that'll be caught be the eval around us in cust_main/Billing.pm
+ return $raw_taxlines[0];
+ }
- # keys are taxnums
- # values are (cumulative) amounts
- my %tax_amount;
+ my @real_taxlines = $self->consolidate_taxlines(@raw_taxlines);
- # keys are taxnums
- # values are arrayrefs of cust_tax_exempt_pkg objects
- my %tax_exemption;
+ if ( $cust_bill and $cust_bill->get('invnum') ) {
+ $_->set('invnum', $cust_bill->get('invnum')) foreach @real_taxlines;
+ }
+ return \@real_taxlines;
+}
+
+sub make_taxlines {
+ # only used by FS::TaxEngine::internal; should just move there
+ my $self = shift;
+ my $conf = $self->{conf};
+
+ my $cust_bill = shift;
+
+ my @raw_taxlines;
# For each distinct tax rate definition, calculate the tax and exemptions.
foreach my $taxnum ( keys %{ $self->{taxes} } ) {
@@ -122,43 +156,86 @@ sub calculate_taxes {
# the rest of @{ $taxlisthash->{$tax} } is cust_bill_pkg component objects
# (setup, recurring, usage classes)
- my $taxline = $self->taxline('tax' => $tax_object, 'sales' => $taxables);
- # taxline methods are now required to return real line items
- # with their link records
- die $taxline unless ref($taxline);
+ my @taxlines = $self->taxline('tax' => $tax_object, 'sales' => $taxables);
+ # taxline methods are now required to return the link records alone.
+ # Consolidation will take care of the rest.
+ next if !@taxlines;
+ die $taxlines[0] unless ref($taxlines[0]);
- push @{ $taxname{ $taxline->itemdesc } }, $taxline;
+ push @raw_taxlines, @taxlines;
} #foreach $taxnum
- my $link_table = $self->info->{link_table};
- # For each distinct tax name (the values set as $taxline->itemdesc),
- # create a consolidated tax item with the total amount and all the links
- # of all tax items that share that name.
- foreach my $taxname ( keys %taxname ) {
- my @tax_links;
- my $tax_cust_bill_pkg = FS::cust_bill_pkg->new({
- 'invnum' => $cust_bill->invnum,
+ return @raw_taxlines;
+}
+
+sub consolidate_taxlines {
+
+ my $self = shift;
+ my $conf = $self->{conf};
+
+ my @raw_taxlines = @_;
+ return if !@raw_taxlines; # shouldn't even be here
+
+ my @tax_line_items;
+
+ # keys are tax names (as printed on invoices / itemdesc )
+ # values are arrayrefs of tax links ("raw taxlines")
+ my %taxname;
+ # collate these by itemdesc
+ foreach my $taxline (@raw_taxlines) {
+ my $taxname = $taxline->taxname;
+ $taxname{$taxname} ||= [];
+ push @{ $taxname{$taxname} }, $taxline;
+ }
+
+ # keys are taxnums
+ # values are (cumulative) amounts
+ my %tax_amount;
+
+ my $link_table = $raw_taxlines[0]->table;
+
+ # Preconstruct cust_bill_pkg objects that will become the "final"
+ # taxlines for each name, so that we can reference them.
+ # (keys are taxnames)
+ my %real_taxline_named = map {
+ $_ => FS::cust_bill_pkg->new({
'pkgnum' => 0,
'recur' => 0,
'sdate' => '',
'edate' => '',
- 'itemdesc' => $taxname,
- $link_table => \@tax_links,
- });
+ 'itemdesc' => $_
+ })
+ } keys %taxname;
+
+ # For each distinct tax name (the values set as $taxline->itemdesc),
+ # create a consolidated tax item with the total amount and all the links
+ # of all tax items that share that name.
+ foreach my $taxname ( keys %taxname ) {
+ my $tax_links = $taxname{$taxname};
+ my $tax_cust_bill_pkg = $real_taxline_named{$taxname};
+ $tax_cust_bill_pkg->set( $link_table => $tax_links );
my $tax_total = 0;
warn "adding $taxname\n" if $DEBUG > 1;
- foreach my $taxitem ( @{ $taxname{$taxname} } ) {
+ foreach my $link ( @$tax_links ) {
# then we need to transfer the amount and the links from the
# line item to the new one we're creating.
- $tax_total += $taxitem->setup;
- foreach my $link ( @{ $taxitem->get($link_table) } ) {
- $link->set('tax_cust_bill_pkg', $tax_cust_bill_pkg);
- push @tax_links, $link;
+ $tax_total += $link->amount;
+ $link->set('tax_cust_bill_pkg', $tax_cust_bill_pkg);
+
+ # if the link represents tax on tax, also fix its taxable pointer
+ # to point to the "final" taxline
+ my $taxable_cust_bill_pkg = $link->get('taxable_cust_bill_pkg');
+ if ( $taxable_cust_bill_pkg and
+ my $other_taxname = $taxable_cust_bill_pkg->itemdesc) {
+ $link->set('taxable_cust_bill_pkg',
+ $real_taxline_named{$other_taxname}
+ );
}
- } # foreach $taxitem
+
+ } # foreach $link
next unless $tax_total;
# we should really neverround this up...I guess it's okay if taxline
@@ -185,7 +262,7 @@ sub calculate_taxes {
push @tax_line_items, $tax_cust_bill_pkg;
}
- \@tax_line_items;
+ @tax_line_items;
}
=head1 CLASS METHODS
@@ -221,7 +298,10 @@ a string on failure.
sub add_taxproduct {
my $class = shift;
- "$class does not allow manually adding taxproducts";
+ #my $classname = ref($class);
+ #my $vendor = (split('::',$classname))[2];
+ my $vendor = ref($class) || $class;
+ "$vendor does not allow manually adding taxproducts";
}
=item transfer_batch (batch-style only)