backup the schema for tables we don't need the data from. RT#85959
[freeside.git] / FS / FS / TaxEngine.pm
1 package FS::TaxEngine;
2
3 use strict;
4 use vars qw( $DEBUG );
5 use FS::Conf;
6 use FS::Record qw(qsearch qsearchs);
7
8 $DEBUG = 0;
9
10 =head1 NAME
11
12 FS::TaxEngine - Base class for tax calculation engines.
13
14 =head1 USAGE
15
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 
18    TaxEngine.
19 3. Set the "pending" flag on the invoice.
20 4. Insert the invoice and its line items.
21
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.
27
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.
33
34 =head1 CLASS METHODS
35
36 =over 4
37
38 =item class
39
40 Returns the class name for tax engines, according to the 'tax_data_vendor'
41 configuration setting.
42
43 =cut
44
45 sub class {
46   my $conf = FS::Conf->new;
47   my $subclass = $conf->config('tax_data_vendor') || 'internal';
48   my $class = "FS::TaxEngine::$subclass";
49   local $@;
50   eval "use $class";
51   die "couldn't load $class: $@\n" if $@;
52
53   $class;
54 }
55
56 =item new 'cust_main' => CUST_MAIN, 'invoice_time' => TIME, OPTIONS...
57
58 Creates an L<FS::TaxEngine> object.  The subclass will be chosen by the 
59 'tax_data_vendor' configuration setting.
60
61 CUST_MAIN and TIME are required.  OPTIONS can include:
62
63 "cancel" => 1 to indicate that the package is being billed on cancellation.
64
65 "estimate" => 1 to indicate that this calculation is for tax estimation,
66 and isn't an actual sale invoice, in case that matters.
67
68 =cut
69
70 sub new {
71   my $class = shift;
72   my %opt = @_;
73   my $conf = FS::Conf->new;
74   if ($class eq 'FS::TaxEngine') {
75     $class = $class->class;
76   }
77   my $self = { items => [], taxes => {}, conf => $conf, %opt };
78   bless $self, $class;
79 }
80
81 =item info
82
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
90    taxes)
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 
93   represent tax on.
94
95 =back
96
97 =head1 METHODS
98
99 =over 4
100
101 =item add_sale CUST_BILL_PKG
102
103 Adds the CUST_BILL_PKG object as a taxable sale on this invoice.
104
105 =item calculate_taxes INVOICE
106
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.
111
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).
116
117 =cut
118
119 sub calculate_taxes {
120   my $self = shift;
121   my $cust_bill = shift;
122
123   my @raw_taxlines = $self->make_taxlines($cust_bill);
124   if ( !@raw_taxlines ) {
125     return;
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];
130   }
131
132   my @real_taxlines = $self->consolidate_taxlines(@raw_taxlines);
133
134   if ( $cust_bill and $cust_bill->get('invnum') ) {
135     $_->set('invnum', $cust_bill->get('invnum')) foreach @real_taxlines;
136   }
137   return \@real_taxlines;
138 }
139
140 sub make_taxlines {
141   # only used by FS::TaxEngine::internal; should just move there
142   my $self = shift;
143   my $conf = $self->{conf};
144
145   my $cust_bill = shift;
146
147   my @raw_taxlines;
148
149   # For each distinct tax rate definition, calculate the tax and exemptions.
150   foreach my $taxnum ( keys %{ $self->{taxes} } ) {
151
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)
158
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.
162     next if !@taxlines;
163     die $taxlines[0] unless ref($taxlines[0]);
164
165     push @raw_taxlines, @taxlines;
166
167   } #foreach $taxnum
168
169   return @raw_taxlines;
170 }
171
172 sub consolidate_taxlines {
173
174   my $self = shift;
175   my $conf = $self->{conf};
176
177   my @raw_taxlines = @_;
178   return if !@raw_taxlines; # shouldn't even be here
179
180   my @tax_line_items;
181
182   # keys are tax names (as printed on invoices / itemdesc )
183   # values are arrayrefs of tax links ("raw taxlines")
184   my %taxname;
185   # collate these by itemdesc
186   foreach my $taxline (@raw_taxlines) {
187     my $taxname = $taxline->taxname;
188     $taxname{$taxname} ||= [];
189     push @{ $taxname{$taxname} }, $taxline;
190   }
191
192   # keys are taxnums
193   # values are (cumulative) amounts
194   my %tax_amount;
195
196   my $link_table = $raw_taxlines[0]->table;
197
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({
203         'pkgnum'    => 0,
204         'recur'     => 0,
205         'sdate'     => '',
206         'edate'     => '',
207         'itemdesc'  => $_
208     })
209   } keys %taxname;
210
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 );
218
219     my $tax_total = 0;
220     warn "adding $taxname\n" if $DEBUG > 1;
221
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);
227
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}
235         );
236       }
237
238     } # foreach $link
239     next unless $tax_total;
240
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);
245
246     my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
247                                                    'disabled'     => '',
248                                                  },
249                                );
250
251     my @display = ();
252     if ( $pkg_category and
253          $conf->config('invoice_latexsummary') ||
254          $conf->config('invoice_htmlsummary')
255        )
256     {
257       my %hash = (  'section' => $pkg_category->categoryname );
258       push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
259     }
260     $tax_cust_bill_pkg->set('display', \@display);
261
262     push @tax_line_items, $tax_cust_bill_pkg;
263   }
264
265   @tax_line_items;
266 }
267
268 =head1 CLASS METHODS
269
270 =item cust_tax_locations LOCATION
271
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 
276 best match.
277
278 =cut
279
280 sub cust_tax_locations {
281   ();
282 } # shouldn't even get called unless info->{manual_tax_location} is true
283
284 =item add_taxproduct DESCRIPTION
285
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.)
290
291 DESCRIPTION is the contents of the taxproduct_description form input, which
292 will normally be filled in by browse/part_pkg_taxproduct/*.
293
294 Must return the newly inserted part_pkg_taxproduct object on success, or
295 a string on failure.
296
297 =cut
298
299 sub add_taxproduct {
300   my $class = shift;
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";
305 }
306
307 =item transfer_batch (batch-style only)
308
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
313 customer.
314
315 =back
316
317 =cut
318
319 1;