script to update Washington sales tax rates, #26265
[freeside.git] / bin / wa_tax_rate_update
1 #!/usr/bin/perl
2
3 =head1 NAME
4
5 wa_tax_rate_update
6
7 =head1 DESCRIPTION
8
9 Tool to update city/district sales tax rates in I<cust_main_county> from 
10 the Washington State Department of Revenue website.
11
12 This does not handle address standardization or geocoding addresses to 
13 Washington tax district codes.  That logic is still in FS::Misc::Geo,
14 and relies on a heinous screen-scraping of the interactive search tool.
15 This script just updates the cust_main_county records that already exist
16 with the latest quarterly tax rates.
17
18 The only option it accepts is "-c" to operate on a specific tax class 
19 (named after the -c).  If this isn't included it will operate on records
20 with null tax class.
21
22 =cut
23
24 use FS::Record qw(qsearch qsearchs dbh);
25 use FS::cust_main_county;
26 use FS::UID qw(adminsuidsetup);
27 use DateTime;
28 use LWP::UserAgent;
29 use File::Temp 'tempdir';
30 use File::Slurp qw(read_file write_file);
31 use Text::CSV;
32 use Getopt::Std;
33
34 getopts('c:');
35 my $user = shift or die usage();
36
37 # download the update file
38 my $now = DateTime->now;
39 my $yr = $now->year;
40 my $qt = $now->quarter;
41 my $file = "Rates${yr}Q${qt}.zip";
42 my $url = 'http://dor.wa.gov/downloads/Add_Data/'.$file;
43 my $dir = tempdir();
44 chdir($dir);
45 my $ua = LWP::UserAgent->new;
46 warn "Downloading $url...\n";
47 my $response = $ua->get($url);
48 if ( ! $response->is_success ) {
49   die $response->status_line;
50 }
51 write_file($file, $response->decoded_content);
52
53 # parse it
54 system('unzip', $file);
55 $file =~ s/\.zip$/.csv/;
56 if (! -f $file) {
57   die "$file not found in zip archive.\n";
58 }
59 open my $fh, '<', $file
60   or die "couldn't open $file: $!\n";
61 my $csv = Text::CSV->new;
62 my $header = $csv->getline($fh);
63 $csv->column_names(@$header);
64 # columns we care about are headed 'Code' and 'Rate'
65
66 # connect to the DB
67 adminsuidsetup($user) or die "bad username '$user'\n";
68 $FS::UID::AutoCommit = 0;
69
70 $opt_c ||= ''; # taxclass
71 my $total_changed = 0;
72 my $total_skipped = 0;
73 while ( !$csv->eof ) {
74   my $line = $csv->getline_hr($fh);
75   my $district = $line->{Code} or next;
76   my $tax = sprintf('%.1f', $line->{Rate} * 100);
77   my $changed = 0;
78   my $skipped = 0;
79   # find all rates in WA
80   my @rates = qsearch('cust_main_county', {
81       country   => 'US',
82       state     => 'WA', # this is specific to WA
83       district  => $district,
84       taxclass  => $opt_c,
85   });
86   foreach my $rate (@rates) {
87     if ( $rate->tax == $tax ) {
88       $skipped++;
89     } else {
90       $rate->set('tax', $tax);
91       my $error = $rate->replace;
92       die "error updating district $district: $error\n" if $error;
93       $changed++;
94     }
95   }
96   print "$district: updated $changed, skipped $skipped\n"
97     if $changed or $skipped;
98   $total_changed += $changed;
99   $total_skipped += $skipped;
100 }
101 print "Updated $total_changed tax rates.\nSkipped $total_skipped unchanged rates.\n";
102 dbh->commit;
103
104 sub usage {
105   "usage:
106   wa_tax_rate_update [ -c taxclass ] user
107 ";
108 }