RT# 83402 CLI tool to repair wa state tax tables
[freeside.git] / FS / FS / Cron / tax_rate_update.pm
index ef529c4..4383bc5 100755 (executable)
@@ -294,6 +294,11 @@ sub wa_sales_update_tax_table {
     )
   );
 
+  # The checks themselves will fully log details about the problem,
+  # so simple error message is sufficient here
+  log_error_and_die('abort tax table update, sanity checks failed')
+    unless wa_sales_update_tax_table_sanity_check();
+
   $args->{temp_dir} ||= tempdir();
 
   $args->{filename} ||= wa_sales_fetch_xlsx_file( $args );
@@ -332,17 +337,64 @@ sub wa_sales_update_cust_main_county {
   for my $taxclass ( FS::part_pkg_taxclass->taxclass_names ) {
     $taxclass ||= undef; # trap empty string when taxclasses are disabled
 
-    my %cust_main_county =
-      map { $_->district => $_ }
+    # Dupe detection/remediation:
+    #
+    # Previous code for washington state tax district was creating
+    # duplicate entries for tax districts.  This could lead to customers
+    # being double-taxed
+    #
+    # The following code detects and eliminates duplicates that
+    # were created by wa_sales district code (source=wa_sales)
+    # before updating the tax table with the newly downloaded
+    # data
+
+    my %cust_main_county;
+    my %cust_main_county_dupe;
+
+    for my $row (
       qsearch(
         cust_main_county => {
-          district => { op => '!=', value => undef },
-          state    => 'WA',
-          country  => 'US',
-          source   => 'wa_sales',
-          taxclass => $taxclass,
+          source    => 'wa_sales',
+          district  => { op => '!=', value => undef },
+          tax_class => $taxclass,
         }
-      );
+      )
+    ) {
+      my $district = $row->district;
+
+      # Row belongs to a known dupe group of districts
+      if ( $cust_main_county_dupe{$district} ) {
+        push @{ $cust_main_county_dupe{$district} }, $row;
+        next;
+      }
+
+      # Row is the first seen dupe for the given district
+      if ( $cust_main_county{$district} ) {
+        $cust_main_county_dupe{$district} = [
+          delete $cust_main_county{$district},
+          $row
+        ];
+        next;
+      }
+
+      # Row is the first seen with this district
+      $cust_main_county{$district} = $row;
+    }
+
+    # Merge any dupes, place resulting non-dupe row in %cust_main_county
+    #  Merge, even if one of the dupes has a $0 tax, or some other
+    #  variation on tax row data.  Data for this row will get corrected
+    #  during the following tax import
+    for my $dupe_district_aref ( values %cust_main_county_dupe ) {
+      my $row_to_keep = shift @$dupe_district_aref;
+      while ( my $row_to_merge = shift @$dupe_district_aref ) {
+        $row_to_merge->_merge_into(
+          $row_to_keep,
+          { identical_record_check => 0 },
+        );
+      }
+      $cust_main_county{$row_to_keep->district} = $row_to_keep;
+    }
 
     for my $district ( @{ $args->{tax_districts} } ) {
       if ( my $row = $cust_main_county{ $district->{district} } ) {
@@ -588,6 +640,26 @@ sub wa_sales_fetch_xlsx_file {
 
 }
 
+=head2 wa_sales_update_tax_table_sanity_check
+
+There should be no duplicate tax table entries in the tax table,
+with the same district value, within a tax class, where source=wa_sales.
+
+If there are, custome taxes may have been user-entered in the
+freeside UI, and incorrectly labelled as source=wa_sales.  Or, the
+dupe record may have been created by issues with older wa_sales code.
+
+If these dupes exist, the sysadmin must solve the problem by hand
+with the freeeside-wa-tax-table-resolve script
+
+Returns 1 unless problem sales tax entries are detected
+
+=cut
+
+sub wa_sales_update_tax_table_sanity_check {
+  FS::cust_main_county->find_wa_tax_dupes ? 0 : 1;
+}
+
 sub log {
   state $log = FS::Log->new('tax_rate_update');
   $log;
@@ -608,6 +680,7 @@ sub log_warn_and_warn {
 sub log_error_and_die {
   my $log_message = shift;
   &log()->error( $log_message );
+  warn( "$log_message\n" );
   die( "$log_message\n" );
 }