RT# 83402 Fix typo
[freeside.git] / FS / bin / freeside-wa-tax-table-resolve
1 #!/usr/bin/env perl
2 use v5.10;
3 use strict;
4 use warnings;
5
6 our $VERSION = '1.0';
7
8 use Data::Dumper;
9 use FS::cust_main_county;
10 use FS::Log;
11 use FS::Record qw( qsearch qsearchs );
12 use FS::UID qw( adminsuidsetup );
13 use Getopt::Long;
14 use Pod::Usage;
15
16 # Begin transaction
17 local $FS::UID::AutoCommit = 0;
18
19 my(
20   $dbh,
21   $freeside_user,
22   $opt_check,
23   @opt_merge,
24   @opt_set_source_null,
25 );
26
27 GetOptions(
28   'check'             => \$opt_check,
29   'merge=s'           => \@opt_merge,
30   'set-source-null=s' => \@opt_set_source_null,
31 );
32 @opt_merge = split(',',join(',',@opt_merge));
33 @opt_set_source_null = split(',',join(',',@opt_set_source_null));
34
35
36 # say Dumper({
37 #   check => $opt_check,
38 #   merge => \@opt_merge,
39 #   set_source_numm => \@opt_set_source_null,
40 # });
41
42 validate_opts();
43
44 $dbh = adminsuidsetup( $freeside_user )
45   or die "Bad  username: $freeside_user\n";
46
47 my $log = FS::Log->new('freeside-wa-tax-table-resolve');
48
49 if ( $opt_check ) {
50   check();
51 } elsif ( @opt_merge ) {
52   merge();
53 } elsif ( @opt_set_source_null ) {
54   set_source_null();
55 } else {
56   error_and_help('No options selected');
57 }
58
59 # Commit transaction
60 $dbh->commit;
61 local $FS::UID::AutoCommit = 1;
62
63 exit;
64
65
66 sub set_source_null {
67   my @cust_main_county;
68   for my $taxnum ( @opt_set_source_null ) {
69     my $row = qsearchs( cust_main_county => { taxnum => $taxnum } );
70     if ( $row ) {
71       push @cust_main_county, $row;
72     } else {
73       error_and_help("Invalid taxnum specified: $taxnum");
74     }
75   }
76
77   say "=== Specified tax rows ===";
78   print_taxnum($_) for @cust_main_county;
79
80   confirm_to_continue("
81
82     The source column will be set to NULL for each of the
83     tax rows listed.  The tax row will no longer be managed
84     by the washington state sales tax table update utilities.
85
86     The listed taxes should be manually created taxes, that
87     were never intended to be managed by the auto updater.
88
89   ");
90
91   for my $row ( @cust_main_county ) {
92
93     $row->setfield( source => undef );
94     my $error = $row->replace;
95
96     if ( $error ) {
97       $dbh->rollback;
98
99       my $message = sprintf 'Error setting source=null taxnum %s: %s',
100           $row->taxnum, $error;
101
102       $log->error( $message );
103       say $message;
104
105       return;
106     }
107
108     my $message = sprintf 'Source column set to null for taxnum %s',
109       $row->taxnum;
110
111     $log->warn( $message );
112     say $message;
113   }
114 }
115
116 sub merge {
117   my $source = qsearchs( cust_main_county => { taxnum => $opt_merge[0] });
118   my $target = qsearchs( cust_main_county => { taxnum => $opt_merge[1] });
119
120   error_and_help("Invalid source taxnum: $opt_merge[0]")
121     unless $source;
122   error_and_help("Invalid target taxnum: $opt_merge[1]")
123     unless $target;
124
125   local $| = 1; # disable output buffering
126
127   say '==== source row ====';
128   print_taxnum( $source );
129
130   say '==== target row ====';
131   print_taxnum( $target );
132
133   confirm_to_continue("
134   
135     The source tax will be merged into the target tax.
136     All references to the source tax on customer invoices
137     will be replaced with references to the target tax.
138     The source tax will be removed from the tax tables.
139
140   ");
141
142   local $@;
143   eval { $source->_merge_into( $target, { identical_record_check => 0 } ) };
144   if ( $@ ) {
145     $dbh->rollback;
146   
147     my $message = sprintf 'Failed to merge wa sales tax %s into %s: %s',
148         $source->taxnum, $target->taxnum, $@;
149
150     say $message;
151     $log->error( $message );
152
153   } else {
154     my $message = sprintf 'Merged wa sales tax %s into %s - success',
155         $source->taxnum, $target->taxnum;
156
157     say $message;
158     $log->warn( $message );
159   }
160 }
161
162 sub validate_opts {
163
164   $freeside_user = shift @ARGV
165     or error_and_help('freeside_user parameter required');
166
167   if ( @opt_merge ) {
168     error_and_help(( '--merge requires a comma separated list of two taxnums'))
169       unless scalar(@opt_merge) == 2
170           && $opt_merge[0] =~ /^\d+$/
171           && $opt_merge[1] =~ /^\d+$/;
172   }
173
174   for my $taxnum ( @opt_set_source_null ) {
175     if ( $taxnum =~ /\D/ ) {
176       error_and_help( "Invalid taxnum ($taxnum)" );
177     }
178   }
179 }
180
181 sub check {
182   my @dupes = FS::cust_main_county->find_wa_tax_dupes;
183
184   unless ( @dupes ) {
185     say 'No duplicate tax rows detected for WA sales tax districts';
186     return;
187   }
188
189   say sprintf '=== Detected %s duplicate tax rows ===', scalar @dupes;
190
191   print_taxnum($_) for @dupes;
192
193   $log->error(
194     sprintf 'Detected %s duplicate wa sales tax rows: %s',
195       scalar( @dupes ),
196       join( ',', map{ $_->taxnum } @dupes )
197   );
198
199 }
200
201 sub print_taxnum {
202   my $taxnum = shift;
203   die unless ref $taxnum;
204
205   say 'taxnum: '.$taxnum->taxnum;
206   say join "\n" => (
207     map { sprintf('  %s:%s', $_, $taxnum->$_ ) }
208     qw/district city county state tax taxname taxclass source/
209   );
210   print "\n";
211 }
212
213 sub confirm_to_continue {
214   say shift;
215   print "Confirm: [y/N]: ";
216   my $yn = <STDIN>;
217   chomp $yn;
218   if ( lc $yn ne 'y' ) {
219     say "\nAborted\n";
220     exit;
221   }
222 }
223
224 sub error_and_help {
225   pod2usage({
226     -message => sprintf( "\n\nError:\n\t%s\n\n", shift ),
227     -exitval => 2,
228     verbose => 1,
229   });
230   exit;
231 }
232
233 __END__
234
235 =head1 name
236
237 freeside-wa-tax-table-resolve
238
239 =head1 SYNOPSIS
240
241 freeside-wa-tax-table-resolve --help
242 freeside-wa-tax-table-resolve --check [freeside_user]
243 freeside-wa-tax-table-resolve --merge 123,234 [freeside_user]
244 freeside-wa-tax-table-resolve --set-source-null 1337,6553 [freeside_user]
245
246 =head1 OPTIONS
247
248 =over 4
249
250 =item B<--help>
251
252 Display help and exit
253
254 =item B<--check>
255
256 Display info on any taxnums considered blocking duplicates
257
258 =item B<--merge> [source-taxnum],[target-taxnum]
259
260 Update all records referring to [source-taxnum], so they now
261 refer to [target-taxnum].  [source-taxnum] is deleted.
262
263 Used to merge duplicate taxnums
264
265 =item B<--set-source-null> [taxnum],[taxnum],...
266
267 Update all records for the given taxnums, by setting the
268 I<source> column to NULL.
269
270 Used for manually entered tax entries, incorrectly labelled
271 as created and managed for Washington State Sales Taxes
272
273 =back
274
275 =head1 DESCRIPTION
276
277 Tool to resolve tax table issues for customer using Washington state
278 sales tax districts.
279
280 If Freeside detects duplicate rows within the wa sales tax tables,
281 tax table updates are blocked, and a log message directs the
282 sysadmin to this tool.
283
284 Duplicate rows may be manually entered taxes, not related
285 to WA sales tax.  Or duplicate rows may have been manually entered
286 into freeside for other tax purposes.
287
288 Use --check to display which tax entries were detected as dupes.
289
290 For each tax entry, decide if it is a duplicate wa sales tax entry,
291 or some other manually entered tax.
292
293 if the row is a duplicate, merge the duplicates with the --merge
294 option of this script
295
296 If the row is a manually entered tax, not for WA state sales taxes,
297 keep the tax but remove the flag incorrectly labeling it as WA state
298 sales taxes with the --set-source-null option of this script
299
300 Once --check no longer returns problematic tax entries, the
301 wa state tax tables will be able to complete their automatic
302 tax rate updates
303
304 =cut