revise process for updating WA sales taxes, #73185 and #73226
[freeside.git] / FS / t / suite / 12-wa_sales_tax.t
1 #!/usr/bin/perl
2
3 =head2 DESCRIPTION
4
5 Tests automatic lookup of Washington sales tax districts and rates.
6
7 This will set up two tax classes. One of them (class A) has only the sales
8 tax. The other (class B) will have an additional, manually created tax.
9
10 This will test the following sequence of actions (running
11 process_district_update() after each one):
12
13 1. Enter a customer in Washington for which there is not yet a district tax
14    entry.
15 2. Add a manual tax in class B.
16 3. Rename the sales taxes.
17 4. Delete the sales taxes.
18 5. Change the sales tax rates (to simulate a change in the actual rate).
19 6. Set the sales tax rate to zero.
20
21 The correct result is always for there to be exactly one tax entry for this
22 district in each class, with the correct rate, except after step 6, when
23 the rate should remain at zero (because setting the rate to zero is a way
24 of manually disabling the tax).
25
26 =cut
27
28 use strict;
29 use Test::More tests => 6;
30 use FS::Test;
31 use FS::cust_main;
32 use FS::cust_location;
33 use FS::cust_main_county;
34 use FS::Misc;
35 use FS::Conf;
36 my $FS= FS::Test->new;
37
38 my $error;
39
40 # start clean
41 my @taxes = $FS->qsearch('cust_main_county', { city => 'SEATTLE' });
42 my @classes = $FS->qsearch('part_pkg_taxclass');
43 foreach (@taxes, @classes) {
44   $error = $_->delete;
45   BAIL_OUT("can't flush existing taxes: $error") if $error;
46   # we won't charge any of the taxes in this script so FK errors shouldn't
47   # happen.
48 }
49
50 # configure stuff
51 @classes = map { FS::part_pkg_taxclass->new({ taxclass => $_ }) }
52   qw( ClassA ClassB );
53 foreach (@classes) {
54   $error = $_->insert;
55   BAIL_OUT("can't create tax class: $error") if $error;
56 }
57
58 # should be an FS::Test method to temporarily set this up
59 my $conf = FS::Conf->new;
60 $conf->set('tax_district_method', 'wa_sales');
61 $conf->set('tax_district_taxname', 'Sales Tax');
62 $conf->set('enable_taxclasses', 1);
63
64 # create the customer
65 my $cust = $FS->new_customer('WA Taxes');
66 # Sea-Tac International Airport
67 $cust->bill_location->address1('17801 International Blvd');
68 $cust->bill_location->city('Seattle');
69 $cust->bill_location->zip('98158');
70 $cust->bill_location->state('WA');
71 $cust->bill_location->country('US');
72
73 $error = $cust->insert;
74 BAIL_OUT("can't create test customer: $error") if $error;
75
76 my $location = $cust->bill_location;
77 my @prev_taxes;
78
79 # after each action, refresh the tax district (as if we'd added/edited a
80 # customer in that district) and then get the new list of defined taxes
81 sub reprocess {
82   # remember all the taxes from the last test
83   @prev_taxes = map { $_ && FS::cust_main_county->new({$_->hash}) } @taxes;
84   local $@;
85   eval { FS::geocode_Mixin::process_district_update( 'FS::cust_location',
86          $location->locationnum )};
87   $error = $@;
88   BAIL_OUT("can't update tax district: $error") if $error;
89
90   $location = $location->replace_old;
91   @taxes = $FS->qsearch({
92     table => 'cust_main_county',
93     hashref => { city => 'SEATTLE' },
94     order_by => 'ORDER BY taxclass ASC, taxname ASC', # make them easily findable
95   });
96 }
97
98 # and then we'll want to check that the total number of taxes is what we
99 # expect.
100 sub ok_num_taxes {
101   my $num = shift;
102   is( scalar(@taxes), $num, "Number of taxes" )
103     or BAIL_OUT('Wrong number of tax records, can\'t continue.');
104 }
105
106 subtest 'Step 1: Initial tax lookup' => sub {
107   plan 'tests' => 4;
108   reprocess();
109   ok( $location->district, 'Found district '.$location->district);
110   ok_num_taxes(2);
111   ok( (   $taxes[0]
112       and $taxes[0]->taxname eq 'Sales Tax'
113       and $taxes[0]->taxclass eq 'ClassA'
114       and $taxes[0]->district eq $location->district
115       and $taxes[0]->source eq 'wa_sales'
116       and $taxes[0]->tax > 0
117       ),
118     'ClassA tax = '.$taxes[0]->tax )
119     or diag explain($taxes[0]);
120   ok( (   $taxes[1] 
121       and $taxes[1]->taxname eq 'Sales Tax'
122       and $taxes[1]->taxclass eq 'ClassB'
123       and $taxes[1]->district eq $location->district
124       and $taxes[1]->source eq 'wa_sales'
125       and $taxes[1]->tax > 0
126       ),
127     'ClassB tax = '.$taxes[1]->tax )
128     or diag explain($taxes[1]);
129 };
130
131 # "Sales Tax" sorts before "USF"; this is intentional.
132 subtest 'Step 2: Add manual tax ("USF") to ClassB' => sub {
133   plan tests => 4;
134   if ($taxes[1]) {
135     my $manual_tax = $taxes[1]->new({
136       $taxes[1]->hash,
137       'taxnum'  => '',
138       'taxname' => 'USF',
139       'source'  => '',
140       'tax'     => '17',
141     });
142     $error = $manual_tax->insert;
143     BAIL_OUT("can't create manual tax: $error") if $error;
144   }
145   reprocess();
146   ok_num_taxes(3);
147   is_deeply( $taxes[0], $prev_taxes[0], 'ClassA sales tax was not changed' );
148   is_deeply( $taxes[1], $prev_taxes[1], 'ClassB sales tax was not changed' );
149   ok( (   $taxes[2]
150       and $taxes[2]->taxname eq 'USF'
151       and $taxes[2]->taxclass eq 'ClassB'
152       and $taxes[2]->tax == 17
153       and $taxes[2]->source eq ''
154     ), 'Manual tax was accepted')
155     or diag explain($taxes[2]);
156 };
157
158 subtest 'Step 3: Rename ClassB sales tax. Does it stay renamed?' => sub {
159   plan tests => 4;
160   if ($taxes[1]) {
161     $taxes[1]->set('taxname', 'Renamed Sales Tax');
162     $error = $taxes[1]->replace;
163     BAIL_OUT("can't rename tax: $error") if $error;
164   }
165
166   reprocess();
167   ok_num_taxes(3);
168   is_deeply( $taxes[0], $prev_taxes[0], 'ClassA sales tax was not changed' );
169   ok( (   $taxes[1]
170       and $taxes[1]->taxname eq 'Renamed Sales Tax'
171       and $taxes[1]->source eq 'wa_sales'
172       and $taxes[1]->tax == $prev_taxes[1]->tax
173     ), $taxes[1]->taxclass .' sales tax was renamed')
174     or diag explain($taxes[1]);
175   is_deeply( $taxes[2], $prev_taxes[2], 'ClassB manual tax was not changed' );
176 };
177
178 subtest 'Step 4: Remove ClassB sales tax. Is it recreated?' => sub {
179   plan tests => 4;
180   if ($taxes[1]) {
181     $error = $taxes[1]->delete;
182     BAIL_OUT("can't delete tax: $error") if $error;
183   }
184   reprocess();
185   ok_num_taxes(3);
186   is_deeply( $taxes[0], $prev_taxes[0], 'ClassA sales tax was not changed' );
187   ok( (   $taxes[1]
188       and $taxes[1]->taxname eq 'Sales Tax'
189       and $taxes[1]->source eq 'wa_sales'
190       and $taxes[1]->tax == $prev_taxes[1]->tax
191     ), $taxes[1]->taxclass .' sales tax was deleted and recreated')
192     or diag explain($taxes[1]);
193   is_deeply( $taxes[2], $prev_taxes[2], 'ClassB manual tax was not changed' );
194 };
195
196 subtest 'Step 5: Simulate a change in tax rate. Do the taxes update?' => sub {
197   plan tests => 3;
198   my $correct_rate = $taxes[0]->tax;
199   foreach (@taxes[0,1]) {
200     if ($_ and $_->source eq 'wa_sales') {
201       $_->tax( $correct_rate + 1 );
202       $error = $_->replace;
203       BAIL_OUT("can't change tax rate: $error") if $error;
204     }
205   }
206   reprocess();
207   ok_num_taxes(3);
208   ok( (   $taxes[0] and $taxes[0]->tax == $correct_rate
209       and $taxes[1] and $taxes[1]->tax == $correct_rate
210     ), 'Tax was reset to correct rate' )
211     or diag("Tax rates: ".$taxes[0]->tax.', '.$taxes[1]->tax);
212   is_deeply( $taxes[2], $prev_taxes[2], 'ClassB manual tax was not changed' );
213 };
214
215 subtest 'Step 6: Manually disable Class A sales tax. Does it stay disabled?' => sub {
216   plan tests => 4;
217   if ($taxes[0]) {
218     $taxes[0]->set('tax', 0);
219     $error = $taxes[0]->replace;
220     BAIL_OUT("can't change tax rate to zero: $error") if $error;
221   }
222   reprocess();
223   ok_num_taxes(3);
224   ok( $taxes[0]->tax == 0, 'ClassA sales tax remains at zero')
225     or diag("Tax rate: ".$taxes[1]->tax);
226   is_deeply( $taxes[1], $prev_taxes[1], 'ClassB sales tax was not changed' );
227   is_deeply( $taxes[2], $prev_taxes[2], 'ClassB manual tax was not changed' );
228 };
229
230 $conf->delete('tax_district_method');
231 $conf->delete('tax_district_taxname');
232 $conf->delete('enable_taxclasses');