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   my $tax = sprintf('%.1f', $line->{Rate} * 100);
82   my $changed = 0;
83   my $skipped = 0;
84   # find all rates in WA
85   my @rates = qsearch('cust_main_county', {
86       country   => 'US',
87       state     => 'WA', # this is specific to WA
88       district  => $district,
89       taxclass  => $opt_c,
90       taxname   => $opt_t,
91   });
92   if ($opt_t eq '') {
93     push @rates, qsearch('cust_main_county', {
94       country   => 'US',
95       state     => 'WA', # this is specific to WA
96       district  => $district,
97       taxclass  => $opt_c,
98       taxname   => 'Tax'
99     });
100   }
101   foreach my $rate (@rates) {
102     if ( $rate->tax == $tax ) {
103       $skipped++;
104     } else {
105       $rate->set('tax', $tax);
106       my $error = $rate->replace;
107       die "error updating district $district: $error\n" if $error;
108       $changed++;
109     }
110   }
111   print "$district: updated $changed, skipped $skipped\n"
112     if $changed or $skipped;
113   $total_changed += $changed;
114   $total_skipped += $skipped;
115 }
116 print "Updated $total_changed tax rates.\nSkipped $total_skipped unchanged rates.\n";
117 dbh->commit;
118
119 sub usage {
120   "usage:
121   wa_tax_rate_update [ -c taxclass ] [ -t taxname ] user
122 ";
123 }