Merge branch '20150325-cust_main-CurrentUser' of https://github.com/fozzmoo/Freeside...
[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 new 'cust_main' => CUST_MAIN, 'invoice_time' => TIME, OPTIONS...
39
40 Creates an L<FS::TaxEngine> object.  The subclass will be chosen by the 
41 'enable_taxproducts' configuration setting.
42
43 CUST_MAIN and TIME are required.  OPTIONS can include "cancel" => 1 to 
44 indicate that the package is being billed on cancellation.
45
46 =cut
47
48 sub new {
49   my $class = shift;
50   my %opt = @_;
51   my $conf = FS::Conf->new;
52   if ($class eq 'FS::TaxEngine') {
53     my $subclass = $conf->config('enable_taxproducts') || 'internal';
54     $class .= "::$subclass";
55     local $@;
56     eval "use $class";
57     die "couldn't load $class: $@\n" if $@;
58   }
59   my $self = { items => [], taxes => {}, conf => $conf, %opt };
60   bless $self, $class;
61 }
62
63 =item info
64
65 Returns a hashref of metadata about this tax method, including:
66 - batch: whether this is a batch-style engine (requires different usage)
67 - override: whether this engine uses tax overrides
68 - manual_tax_location: whether this engine requires the user to select a "tax
69   location" separate from the address/city/state/zip fields
70 - rate_table: the table that stores the tax rates
71   (the 'taxline' method of that class will be used to calculate line-item
72    taxes)
73 - link_table: the table that links L<FS::cust_bill_pkg> records for taxes
74   to the C<rate_table> entry that generated them, and to the item they 
75   represent tax on.
76
77 =back
78
79 =head1 METHODS
80
81 =over 4
82
83 =item add_sale CUST_BILL_PKG
84
85 Adds the CUST_BILL_PKG object as a taxable sale on this invoice.
86
87 =item calculate_taxes INVOICE
88
89 Calculates the taxes on the taxable sales and returns a list of 
90 L<FS::cust_bill_pkg> objects to add to the invoice.  The base implementation
91 is to call L</make_taxlines> to produce a list of "raw" tax line items, 
92 then L</consolidate_taxlines> to combine those with the same itemdesc.
93
94 =cut
95
96 sub calculate_taxes {
97   my $self = shift;
98   my $cust_bill = shift;
99
100   my @raw_taxlines = $self->make_taxlines($cust_bill);
101
102   my @real_taxlines = $self->consolidate_taxlines(@raw_taxlines);
103
104   if ( $cust_bill and $cust_bill->get('invnum') ) {
105     $_->set('invnum', $cust_bill->get('invnum')) foreach @real_taxlines;
106   }
107   return \@real_taxlines;
108 }
109
110 sub make_taxlines {
111   my $self = shift;
112   my $conf = $self->{conf};
113
114   my $cust_bill = shift;
115
116   my @taxlines;
117
118   # For each distinct tax rate definition, calculate the tax and exemptions.
119   foreach my $taxnum ( keys %{ $self->{taxes} } ) {
120
121     my $taxables = $self->{taxes}{$taxnum};
122     my $tax_object = shift @$taxables;
123     # $tax_object is a cust_main_county or tax_rate 
124     # (with billpkgnum, pkgnum, locationnum set)
125     # the rest of @{ $taxlisthash->{$tax} } is cust_bill_pkg component objects
126     # (setup, recurring, usage classes)
127
128     my $taxline = $self->taxline('tax' => $tax_object, 'sales' => $taxables);
129     # taxline methods are now required to return real line items
130     # with their link records
131     die $taxline unless ref($taxline);
132
133     push @taxlines, $taxline;
134
135   } #foreach $taxnum
136
137   return @taxlines;
138 }
139
140 sub consolidate_taxlines {
141
142   my $self = shift;
143   my $conf = $self->{conf};
144
145   my @raw_taxlines = @_;
146   my @tax_line_items;
147
148   # keys are tax names (as printed on invoices / itemdesc )
149   # values are arrayrefs of taxlines
150   my %taxname;
151   # collate these by itemdesc
152   foreach my $taxline (@raw_taxlines) {
153     my $taxname = $taxline->itemdesc;
154     $taxname{$taxname} ||= [];
155     push @{ $taxname{$taxname} }, $taxline;
156   }
157
158   # keys are taxnums
159   # values are (cumulative) amounts
160   my %tax_amount;
161
162   my $link_table = $self->info->{link_table};
163
164   # Preconstruct cust_bill_pkg objects that will become the "final"
165   # taxlines for each name, so that we can reference them.
166   # (keys are taxnames)
167   my %real_taxline_named = map {
168     $_ => FS::cust_bill_pkg->new({
169         'pkgnum'    => 0,
170         'recur'     => 0,
171         'sdate'     => '',
172         'edate'     => '',
173         'itemdesc'  => $_
174     })
175   } keys %taxname;
176
177   # For each distinct tax name (the values set as $taxline->itemdesc),
178   # create a consolidated tax item with the total amount and all the links
179   # of all tax items that share that name.
180   foreach my $taxname ( keys %taxname ) {
181     my @tax_links;
182     my $tax_cust_bill_pkg = $real_taxline_named{$taxname};
183     $tax_cust_bill_pkg->set( $link_table => \@tax_links );
184
185     my $tax_total = 0;
186     warn "adding $taxname\n" if $DEBUG > 1;
187
188     foreach my $taxitem ( @{ $taxname{$taxname} } ) {
189       # then we need to transfer the amount and the links from the
190       # line item to the new one we're creating.
191       $tax_total += $taxitem->setup;
192       foreach my $link ( @{ $taxitem->get($link_table) } ) {
193         $link->set('tax_cust_bill_pkg', $tax_cust_bill_pkg);
194
195         # if the link represents tax on tax, also fix its taxable pointer
196         # to point to the "final" taxline
197         my $taxable_cust_bill_pkg = $link->get('taxable_cust_bill_pkg');
198         if (my $other_taxname = $taxable_cust_bill_pkg->itemdesc) {
199           $link->set('taxable_cust_bill_pkg',
200             $real_taxline_named{$other_taxname}
201           );
202         }
203
204         push @tax_links, $link;
205       }
206     } # foreach $taxitem
207     next unless $tax_total;
208
209     # we should really neverround this up...I guess it's okay if taxline 
210     # already returns amounts with 2 decimal places
211     $tax_total = sprintf('%.2f', $tax_total );
212     $tax_cust_bill_pkg->set('setup', $tax_total);
213
214     my $pkg_category = qsearchs( 'pkg_category', { 'categoryname' => $taxname,
215                                                    'disabled'     => '',
216                                                  },
217                                );
218
219     my @display = ();
220     if ( $pkg_category and
221          $conf->config('invoice_latexsummary') ||
222          $conf->config('invoice_htmlsummary')
223        )
224     {
225       my %hash = (  'section' => $pkg_category->categoryname );
226       push @display, new FS::cust_bill_pkg_display { type => 'S', %hash };
227     }
228     $tax_cust_bill_pkg->set('display', \@display);
229
230     push @tax_line_items, $tax_cust_bill_pkg;
231   }
232
233   @tax_line_items;
234 }
235
236 =head1 CLASS METHODS
237
238 =item cust_tax_locations LOCATION
239
240 Given an L<FS::cust_location> object (or a hash of location fields), 
241 returns a list of all tax jurisdiction locations that could possibly 
242 match it.  This is meant for interactive use: the location editing UI
243 displays the candidate locations to the user so they can choose the 
244 best match.
245
246 =cut
247
248 sub cust_tax_locations {
249   ();
250 } # shouldn't even get called unless info->{manual_tax_location} is true
251
252 =item add_taxproduct DESCRIPTION
253
254 If the module allows manually adding tax products (categories of taxable
255 items/services), this method will be called to do it. (If not, the UI in
256 browse/part_pkg_taxproduct/* should prevent adding an unlisted tax product.
257 That is the default behavior, so by default this method simply fails.)
258
259 DESCRIPTION is the contents of the taxproduct_description form input, which
260 will normally be filled in by browse/part_pkg_taxproduct/*.
261
262 Must return the newly inserted part_pkg_taxproduct object on success, or
263 a string on failure.
264
265 =cut
266
267 sub add_taxproduct {
268   my $class = shift;
269   "$class does not allow manually adding taxproducts";
270 }
271
272 =item transfer_batch (batch-style only)
273
274 Submits the pending transaction batch for processing, receives the 
275 results, and appends the calculated taxes to all invoices that were 
276 included in the batch.  Then clears their pending flags, and queues
277 a job to run C<FS::cust_main::Billing::collect> on each affected
278 customer.
279
280 =back
281
282 =cut
283
284 1;