Merge branch 'master' of git.freeside.biz:/home/git/freeside
[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 Options:
19
20 -c <taxclass>: operate only on records with the named tax class.  If not 
21 specified, this operates on records with null tax class.
22
23 -t <taxname>: operate only on records with that tax name.  If not specified,
24 it operates on records where the tax name is either null or 'Tax'.
25
26 =cut
27
28 use FS::Record qw(qsearch qsearchs dbh);
29 use FS::cust_main_county;
30 use FS::UID qw(adminsuidsetup);
31 use DateTime;
32 use LWP::UserAgent;
33 use File::Temp 'tempdir';
34 use File::Slurp qw(read_file write_file);
35 use Text::CSV;
36 use Getopt::Std;
37
38 getopts('c:t:');
39 my $user = shift or die usage();
40
41 # download the update file
42 my $now = DateTime->now;
43 my $yr = $now->year;
44 my $qt = $now->quarter;
45 my $file = "Rates${yr}Q${qt}.zip";
46 my $url = 'http://dor.wa.gov/downloads/Add_Data/'.$file;
47 my $dir = tempdir();
48 chdir($dir);
49 my $ua = LWP::UserAgent->new;
50 warn "Downloading $url...\n";
51 my $response = $ua->get($url);
52 if ( ! $response->is_success ) {
53   die $response->status_line;
54 }
55 write_file($file, $response->decoded_content);
56
57 # parse it
58 system('unzip', $file);
59 $file =~ s/\.zip$/.csv/;
60 if (! -f $file) {
61   die "$file not found in zip archive.\n";
62 }
63 open my $fh, '<', $file
64   or die "couldn't open $file: $!\n";
65 my $csv = Text::CSV->new;
66 my $header = $csv->getline($fh);
67 $csv->column_names(@$header);
68 # columns we care about are headed 'Code' and 'Rate'
69
70 # connect to the DB
71 adminsuidsetup($user) or die "bad username '$user'\n";
72 $FS::UID::AutoCommit = 0;
73
74 $opt_c ||= ''; # taxclass
75 $opt_t ||= ''; # taxname
76 my $total_changed = 0;
77 my $total_skipped = 0;
78 while ( !$csv->eof ) {
79   my $line = $csv->getline_hr($fh);
80   my $district = $line->{Code} or next;
81   $district = sprintf('%04d', $district);
82   my $tax = sprintf('%.1f', $line->{Rate} * 100);
83   my $changed = 0;
84   my $skipped = 0;
85   # find all rates in WA
86   my @rates = qsearch('cust_main_county', {
87       country   => 'US',
88       state     => 'WA', # this is specific to WA
89       district  => $district,
90       taxclass  => $opt_c,
91       taxname   => $opt_t,
92   });
93   if ($opt_t eq '') {
94     push @rates, qsearch('cust_main_county', {
95       country   => 'US',
96       state     => 'WA', # this is specific to WA
97       district  => $district,
98       taxclass  => $opt_c,
99       taxname   => 'Tax'
100     });
101   }
102   foreach my $rate (@rates) {
103     if ( $rate->tax == $tax ) {
104       $skipped++;
105     } else {
106       $rate->set('tax', $tax);
107       my $error = $rate->replace;
108       die "error updating district $district: $error\n" if $error;
109       $changed++;
110     }
111   }
112   print "$district: updated $changed, skipped $skipped\n"
113     if $changed or $skipped;
114   $total_changed += $changed;
115   $total_skipped += $skipped;
116 }
117 print "Updated $total_changed tax rates.\nSkipped $total_skipped unchanged rates.\n";
118 dbh->commit;
119
120 sub usage {
121   "usage:
122   wa_tax_rate_update [ -c taxclass ] [ -t taxname ] user
123 ";
124 }