6 use FS::Record qw(qsearch qsearchs);
12 FS::TaxEngine - Base class for tax calculation engines.
16 1. At the start of creating an invoice, create an FS::TaxEngine object.
17 2. Each time a sale item is added to the invoice, call L</add_sale> on the
19 3. Set the "pending" flag on the invoice.
20 4. Insert the invoice and its line items.
22 - If the TaxEngine is "batch" style (Billsoft):
23 5. After creating all invoices for the day, call
24 FS::TaxEngine::process_tax_batch. This will create the tax items for
25 all of the pending invoices, clear the "pending" flag, and call
26 L<FS::cust_main::Billing/collect> on each of the billed customers.
28 - If not (the internal tax system, CCH):
29 5. After adding all sale items, call L</calculate_taxes> on the TaxEngine to
30 produce a list of tax line items.
31 6. Append the tax line items to the invoice.
32 7. Update the invoice with the new charged amount and clear the pending flag.
38 =item new 'cust_main' => CUST_MAIN, 'invoice_time' => TIME, OPTIONS...
40 Creates an L<FS::TaxEngine> object. The subclass will be chosen by the
41 'enable_taxproducts' configuration setting.
43 CUST_MAIN and TIME are required. OPTIONS can include:
45 "cancel" => 1 to indicate that the package is being billed on cancellation.
47 "estimate" => 1 to indicate that this calculation is for tax estimation,
48 and isn't an actual sale invoice, in case that matters.
55 my $conf = FS::Conf->new;
56 if ($class eq 'FS::TaxEngine') {
57 my $subclass = $conf->config('enable_taxproducts') || 'internal';
58 $class .= "::$subclass";
61 die "couldn't load $class: $@\n" if $@;
63 my $self = { items => [], taxes => {}, conf => $conf, %opt };
69 Returns a hashref of metadata about this tax method, including:
70 - batch: whether this is a batch-style engine (requires different usage)
71 - override: whether this engine uses tax overrides
72 - manual_tax_location: whether this engine requires the user to select a "tax
73 location" separate from the address/city/state/zip fields
74 - rate_table: the table that stores the tax rates
75 (the 'taxline' method of that class will be used to calculate line-item
77 - link_table: the table that links L<FS::cust_bill_pkg> records for taxes
78 to the C<rate_table> entry that generated them, and to the item they
87 =item add_sale CUST_BILL_PKG
89 Adds the CUST_BILL_PKG object as a taxable sale on this invoice.
91 =item calculate_taxes INVOICE
93 Calculates the taxes on the taxable sales and returns a list of
94 L<FS::cust_bill_pkg> objects to add to the invoice. The base implementation
95 is to call L</make_taxlines> to produce a list of "raw" tax line items,
96 then L</consolidate_taxlines> to combine those with the same itemdesc.
100 sub calculate_taxes {
102 my $cust_bill = shift;
104 my @raw_taxlines = $self->make_taxlines($cust_bill);
106 my @real_taxlines = $self->consolidate_taxlines(@raw_taxlines);
108 if ( $cust_bill and $cust_bill->get('invnum') ) {
109 $_->set('invnum', $cust_bill->get('invnum')) foreach @real_taxlines;
111 return \@real_taxlines;
116 my $conf = $self->{conf};
118 my $cust_bill = shift;
122 # For each distinct tax rate definition, calculate the tax and exemptions.
123 foreach my $taxnum ( keys %{ $self->{taxes} } ) {
125 my $taxables = $self->{taxes}{$taxnum};
126 my $tax_object = shift @$taxables;
127 # $tax_object is a cust_main_county or tax_rate
128 # (with billpkgnum, pkgnum, locationnum set)
129 # the rest of @{ $taxlisthash->{$tax} } is cust_bill_pkg component objects
130 # (setup, recurring, usage classes)
132 my $taxline = $self->taxline('tax' => $tax_object, 'sales' => $taxables);
133 # taxline methods are now required to return real line items
134 # with their link records
135 die $taxline unless ref($taxline);
137 push @taxlines, $taxline;
144 sub consolidate_taxlines {
147 my $conf = $self->{conf};
149 my @raw_taxlines = @_;
152 # keys are tax names (as printed on invoices / itemdesc )
153 # values are arrayrefs of taxlines
155 # collate these by itemdesc
156 foreach my $taxline (@raw_taxlines) {
157 my $taxname = $taxline->itemdesc;
158 $taxname{$taxname} ||= [];
159 push @{ $taxname{$taxname} }, $taxline;
163 # values are (cumulative) amounts
166 my $link_table = $self->info->{link_table};
168 # Preconstruct cust_bill_pkg objects that will become the "final"
169 # taxlines for each name, so that we can reference them.
170 # (keys are taxnames)
171 my %real_taxline_named = map {
172 $_ => FS::cust_bill_pkg->new({
181 # For each distinct tax name (the values set as $taxline->itemdesc),
182 # create a consolidated tax item with the total amount and all the links
183 # of all tax items that share that name.
184 foreach my $taxname ( keys %taxname ) {
186 my $tax_cust_bill_pkg = $real_taxline_named{$taxname};
187 $tax_cust_bill_pkg->set( $link_table => \@tax_links );
190 warn "adding $taxname\n" if $DEBUG > 1;
192 foreach my $taxitem ( @{ $taxname{$taxname} } ) {
193 # then we need to transfer the amount and the links from the
194 # line item to the new one we're creating.
195 $tax_total += $taxitem->setup;
196 foreach my $link ( @{ $taxitem->get($link_table) } ) {
197 $link->set('tax_cust_bill_pkg', $tax_cust_bill_pkg);
199 # if the link represents tax on tax, also fix its taxable pointer
200 # to point to the "final" taxline
201 my $taxable_cust_bill_pkg = $link->get('taxable_cust_bill_pkg');
202 if (my $other_taxname = $taxable_cust_bill_pkg->itemdesc) {
203 $link->set('taxable_cust_bill_pkg',
204 $real_taxline_named{$other_taxname}
208 push @tax_links, $link;
211 next unless $tax_total;
213 # we should really neverround this up...I guess it's okay if taxline
214 # already returns amounts with 2 decimal places
215 $tax_total = sprintf('%.2f', $tax_total );
216 $tax_cust_bill_pkg->set('setup', $tax_total);
218 my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
224 if ( $pkg_category and
225 $conf->config('invoice_latexsummary') ||
226 $conf->config('invoice_htmlsummary')
229 my %hash = ( 'section' => $pkg_category->categoryname );
230 push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
232 $tax_cust_bill_pkg->set('display', \@display);
234 push @tax_line_items, $tax_cust_bill_pkg;
242 =item cust_tax_locations LOCATION
244 Given an L<FS::cust_location> object (or a hash of location fields),
245 returns a list of all tax jurisdiction locations that could possibly
246 match it. This is meant for interactive use: the location editing UI
247 displays the candidate locations to the user so they can choose the
252 sub cust_tax_locations {
254 } # shouldn't even get called unless info->{manual_tax_location} is true
256 =item add_taxproduct DESCRIPTION
258 If the module allows manually adding tax products (categories of taxable
259 items/services), this method will be called to do it. (If not, the UI in
260 browse/part_pkg_taxproduct/* should prevent adding an unlisted tax product.
261 That is the default behavior, so by default this method simply fails.)
263 DESCRIPTION is the contents of the taxproduct_description form input, which
264 will normally be filled in by browse/part_pkg_taxproduct/*.
266 Must return the newly inserted part_pkg_taxproduct object on success, or
273 "$class does not allow manually adding taxproducts";
276 =item transfer_batch (batch-style only)
278 Submits the pending transaction batch for processing, receives the
279 results, and appends the calculated taxes to all invoices that were
280 included in the batch. Then clears their pending flags, and queues
281 a job to run C<FS::cust_main::Billing::collect> on each affected