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.
40 Returns the class name for tax engines, according to the 'tax_data_vendor'
41 configuration setting.
46 my $conf = FS::Conf->new;
47 my $subclass = $conf->config('tax_data_vendor') || 'internal';
48 my $class = "FS::TaxEngine::$subclass";
51 die "couldn't load $class: $@\n" if $@;
56 =item new 'cust_main' => CUST_MAIN, 'invoice_time' => TIME, OPTIONS...
58 Creates an L<FS::TaxEngine> object. The subclass will be chosen by the
59 'tax_data_vendor' configuration setting.
61 CUST_MAIN and TIME are required. OPTIONS can include:
63 "cancel" => 1 to indicate that the package is being billed on cancellation.
65 "estimate" => 1 to indicate that this calculation is for tax estimation,
66 and isn't an actual sale invoice, in case that matters.
73 my $conf = FS::Conf->new;
74 if ($class eq 'FS::TaxEngine') {
75 $class = $class->class;
77 my $self = { items => [], taxes => {}, conf => $conf, %opt };
83 Returns a hashref of metadata about this tax method, including:
84 - batch: whether this is a batch-style engine (requires different usage)
85 - override: whether this engine uses tax overrides
86 - manual_tax_location: whether this engine requires the user to select a "tax
87 location" separate from the address/city/state/zip fields
88 - rate_table: the table that stores the tax rates
89 (the 'taxline' method of that class will be used to calculate line-item
91 - link_table: the table that links L<FS::cust_bill_pkg> records for taxes
92 to the C<rate_table> entry that generated them, and to the item they
101 =item add_sale CUST_BILL_PKG
103 Adds the CUST_BILL_PKG object as a taxable sale on this invoice.
105 =item calculate_taxes INVOICE
107 Calculates the taxes on the taxable sales and returns a list of
108 L<FS::cust_bill_pkg> objects to add to the invoice. The base implementation
109 is to call L</make_taxlines> to produce a list of "raw" tax line items,
110 then L</consolidate_taxlines> to combine those with the same itemdesc.
112 If this fails, it will throw an exception. (Accordingly it should not trap
113 exceptions from internal methods that it calls, except to translate error
114 messages into a more meaningful form.) If it succeeds, it MUST return an
115 arrayref (even if the arrayref is empty).
119 sub calculate_taxes {
121 my $cust_bill = shift;
123 my @raw_taxlines = $self->make_taxlines($cust_bill);
124 if ( !@raw_taxlines ) {
126 } elsif ( !ref $raw_taxlines[0] ) { # error message
127 #this isn't actually handled by our caller... better for make_taxlines to
128 # die, that'll be caught be the eval around us in cust_main/Billing.pm
129 return $raw_taxlines[0];
132 my @real_taxlines = $self->consolidate_taxlines(@raw_taxlines);
134 if ( $cust_bill and $cust_bill->get('invnum') ) {
135 $_->set('invnum', $cust_bill->get('invnum')) foreach @real_taxlines;
137 return \@real_taxlines;
141 # only used by FS::TaxEngine::internal; should just move there
143 my $conf = $self->{conf};
145 my $cust_bill = shift;
149 # For each distinct tax rate definition, calculate the tax and exemptions.
150 foreach my $taxnum ( keys %{ $self->{taxes} } ) {
152 my $taxables = $self->{taxes}{$taxnum};
153 my $tax_object = shift @$taxables;
154 # $tax_object is a cust_main_county or tax_rate
155 # (with billpkgnum, pkgnum, locationnum set)
156 # the rest of @{ $taxlisthash->{$tax} } is cust_bill_pkg component objects
157 # (setup, recurring, usage classes)
159 my @taxlines = $self->taxline('tax' => $tax_object, 'sales' => $taxables);
160 # taxline methods are now required to return the link records alone.
161 # Consolidation will take care of the rest.
163 die $taxlines[0] unless ref($taxlines[0]);
165 push @raw_taxlines, @taxlines;
169 return @raw_taxlines;
172 sub consolidate_taxlines {
175 my $conf = $self->{conf};
177 my @raw_taxlines = @_;
178 return if !@raw_taxlines; # shouldn't even be here
182 # keys are tax names (as printed on invoices / itemdesc )
183 # values are arrayrefs of tax links ("raw taxlines")
185 # collate these by itemdesc
186 foreach my $taxline (@raw_taxlines) {
187 my $taxname = $taxline->taxname;
188 $taxname{$taxname} ||= [];
189 push @{ $taxname{$taxname} }, $taxline;
193 # values are (cumulative) amounts
196 my $link_table = $raw_taxlines[0]->table;
198 # Preconstruct cust_bill_pkg objects that will become the "final"
199 # taxlines for each name, so that we can reference them.
200 # (keys are taxnames)
201 my %real_taxline_named = map {
202 $_ => FS::cust_bill_pkg->new({
211 # For each distinct tax name (the values set as $taxline->itemdesc),
212 # create a consolidated tax item with the total amount and all the links
213 # of all tax items that share that name.
214 foreach my $taxname ( keys %taxname ) {
215 my $tax_links = $taxname{$taxname};
216 my $tax_cust_bill_pkg = $real_taxline_named{$taxname};
217 $tax_cust_bill_pkg->set( $link_table => $tax_links );
220 warn "adding $taxname\n" if $DEBUG > 1;
222 foreach my $link ( @$tax_links ) {
223 # then we need to transfer the amount and the links from the
224 # line item to the new one we're creating.
225 $tax_total += $link->amount;
226 $link->set('tax_cust_bill_pkg', $tax_cust_bill_pkg);
228 # if the link represents tax on tax, also fix its taxable pointer
229 # to point to the "final" taxline
230 my $taxable_cust_bill_pkg = $link->get('taxable_cust_bill_pkg');
231 if ( $taxable_cust_bill_pkg and
232 my $other_taxname = $taxable_cust_bill_pkg->itemdesc) {
233 $link->set('taxable_cust_bill_pkg',
234 $real_taxline_named{$other_taxname}
239 next unless $tax_total;
241 # we should really neverround this up...I guess it's okay if taxline
242 # already returns amounts with 2 decimal places
243 $tax_total = sprintf('%.2f', $tax_total );
244 $tax_cust_bill_pkg->set('setup', $tax_total);
246 my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
252 if ( $pkg_category and
253 $conf->config('invoice_latexsummary') ||
254 $conf->config('invoice_htmlsummary')
257 my %hash = ( 'section' => $pkg_category->categoryname );
258 push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
260 $tax_cust_bill_pkg->set('display', \@display);
262 push @tax_line_items, $tax_cust_bill_pkg;
270 =item cust_tax_locations LOCATION
272 Given an L<FS::cust_location> object (or a hash of location fields),
273 returns a list of all tax jurisdiction locations that could possibly
274 match it. This is meant for interactive use: the location editing UI
275 displays the candidate locations to the user so they can choose the
280 sub cust_tax_locations {
282 } # shouldn't even get called unless info->{manual_tax_location} is true
284 =item add_taxproduct DESCRIPTION
286 If the module allows manually adding tax products (categories of taxable
287 items/services), this method will be called to do it. (If not, the UI in
288 browse/part_pkg_taxproduct/* should prevent adding an unlisted tax product.
289 That is the default behavior, so by default this method simply fails.)
291 DESCRIPTION is the contents of the taxproduct_description form input, which
292 will normally be filled in by browse/part_pkg_taxproduct/*.
294 Must return the newly inserted part_pkg_taxproduct object on success, or
301 #my $classname = ref($class);
302 #my $vendor = (split('::',$classname))[2];
303 my $vendor = ref($class) || $class;
304 "$vendor does not allow manually adding taxproducts";
307 =item transfer_batch (batch-style only)
309 Submits the pending transaction batch for processing, receives the
310 results, and appends the calculated taxes to all invoices that were
311 included in the batch. Then clears their pending flags, and queues
312 a job to run C<FS::cust_main::Billing::collect> on each affected