fix error message when an action isn't selected
[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       tax       => { op => '>', value => '0' },
93   });
94   if ($opt_t eq '') {
95     push @rates, qsearch('cust_main_county', {
96       country   => 'US',
97       state     => 'WA', # this is specific to WA
98       district  => $district,
99       taxclass  => $opt_c,
100       taxname   => 'Tax',
101       tax       => { op => '>', value => '0' },
102     });
103   }
104   foreach my $rate (@rates) {
105     if ( $rate->tax == $tax ) {
106       $skipped++;
107     } else {
108       $rate->set('tax', $tax);
109       my $error = $rate->replace;
110       die "error updating district $district: $error\n" if $error;
111       $changed++;
112     }
113   }
114   print "$district: updated $changed, skipped $skipped\n"
115     if $changed or $skipped;
116   $total_changed += $changed;
117   $total_skipped += $skipped;
118 }
119 print "Updated $total_changed tax rates.\nSkipped $total_skipped unchanged rates.\n";
120 dbh->commit;
121
122 sub usage {
123   "usage:
124   wa_tax_rate_update [ -c taxclass ] [ -t taxname ] user
125 ";
126 }