h_svc_pbx.pm, RT#7322
[freeside.git] / FS / FS / tax_rate.pm
index 2808c6e..30d7f58 100644 (file)
@@ -231,6 +231,8 @@ sub check {
     || $self->ut_enum('passflag', [ '', 'Y', 'N' ])
     || $self->ut_enum('setuptax', [ '', 'Y' ] )
     || $self->ut_enum('recurtax', [ '', 'Y' ] )
+    || $self->ut_enum('inoutcity', [ '', 'I', 'O' ] )
+    || $self->ut_enum('inoutlocal', [ '', 'I', 'O' ] )
     || $self->ut_enum('manual', [ '', 'Y' ] )
     || $self->ut_enum('disabled', [ '', 'Y' ] )
     || $self->SUPER::check
@@ -405,13 +407,14 @@ sub taxline {
     };
   }
 
-  if ($self->maxtype != 0 && $self->maxtype != 9) {
+  my $maxtype = $self->maxtype || 0;
+  if ($maxtype != 0 && $maxtype != 9) {
     return $self->_fatal_or_null( 'tax with "'.
                                     $self->maxtype_name. '" threshold'
                                 );
   }
 
-  if ($self->maxtype == 9) {
+  if ($maxtype == 9) {
     return
       $self->_fatal_or_null( 'tax with "'. $self->maxtype_name. '" threshold' );
                                                                 # "texas" tax
@@ -437,7 +440,7 @@ sub taxline {
 
   my $taxable_units = 0;
   unless ($self->recurtax =~ /^Y$/i) {
-    if ($self->unittype == 0) {
+    if (( $self->unittype || 0 ) == 0) {
       my %seen = ();
       foreach (@cust_bill_pkg) {
         $taxable_units += $_->units
@@ -478,16 +481,16 @@ sub _fatal_or_null {
 
   my $conf = new FS::Conf;
 
-  $error = "fatal: can't yet handle ". $error;
+  $error = "can't yet handle $error";
   my $name = $self->taxname;
   $name = 'Other surcharges'
     if ($self->passtype == 2);
 
   if ($conf->exists('ignore_incalculable_taxes')) {
-    warn $error;
+    warn "WARNING: $error; billing anyway per ignore_incalculable_taxes conf\n";
     return { name => $name, amount => 0 };
   } else {
-    return $error;
+    return "fatal: $error";
   }
 }
 
@@ -641,7 +644,7 @@ sub batch_import {
 
       $hash->{'taxclassnum'} = $tax_class->taxclassnum;
 
-      foreach (qw( inoutcity inoutlocal taxtype taxcat )) {
+      foreach (qw( taxtype taxcat )) {
         delete($hash->{$_});
       }
 
@@ -715,7 +718,10 @@ sub batch_import {
         my $error = $job->update_statustext(
           int( 100 * $imported / $count ). ",Importing tax rates"
         );
-        die $error if $error;
+        if ($error) {
+          $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+          die $error;
+        }
         $last = time;
       }
     }
@@ -759,7 +765,10 @@ sub batch_import {
         my $error = $job->update_statustext(
           int( 100 * $imported / $count ). ",Importing tax rates"
         );
-        die $error if $error;
+        if ($error) {
+          $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+          die $error;
+        }
         $last = time;
       }
     }
@@ -783,7 +792,10 @@ sub batch_import {
         my $error = $job->update_statustext(
           int( 100 * $imported / $count ). ",Importing tax rates"
         );
-        die $error if $error;
+        if ($error) {
+          $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+          die $error;
+        }
         $last = time;
       }
     }
@@ -817,7 +829,10 @@ sub batch_import {
         my $error = $job->update_statustext(
           int( 100 * $imported / $count ). ",Importing tax rates"
         );
-        die $error if $error;
+        if ($error) {
+          $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+          die $error;
+        }
         $last = time;
       }
     }
@@ -917,6 +932,7 @@ sub process_batch_import {
     my $error = '';
     my @insert_list = ();
     my @delete_list = ();
+    my @predelete_list = ();
 
     my @list = ( 'GEOCODE',  'geofile',   \&FS::tax_rate_location::batch_import,
                  'CODE',     'codefile',  \&FS::tax_class::batch_import,
@@ -972,9 +988,26 @@ sub process_batch_import {
       close $dfh;
 
       push @insert_list, $name, $ifh->filename, $import_sub;
-      unshift @delete_list, $name, $dfh->filename, $import_sub;
+      if ( $name eq 'GEOCODE' ) { #handle this whole ordering issue better
+        unshift @predelete_list, $name, $dfh->filename, $import_sub;
+      } else {
+        unshift @delete_list, $name, $dfh->filename, $import_sub;
+      }
 
     }
+
+    while( scalar(@predelete_list) ) {
+      my ($name, $file, $import_sub) =
+        (shift @predelete_list, shift @predelete_list, shift @predelete_list);
+
+      my $fmt = $format. ( $name eq 'ZIP' ? '-zip' : '' );
+      open my $fh, "< $file" or $error ||= "Can't open $name file $file: $!";
+      $error ||=
+        &{$import_sub}({ 'filehandle' => $fh, 'format' => $fmt }, $job);
+      close $fh;
+      unlink $file or warn "Can't delete $file: $!";
+    }
+    
     while( scalar(@insert_list) ) {
       my ($name, $file, $import_sub) =
         (shift @insert_list, shift @insert_list, shift @insert_list);
@@ -1062,15 +1095,26 @@ sub process_download_and_reload {
 
   #remember disabled taxes
   my %disabled_tax_rate = ();
-  foreach my $tax_rate ( qsearch( { table   => 'tax_rate',
-                                    hashref => { disabled => 'Y',
-                                                 data_vendor => $format,
-                                               },
-                                    select  => 'geocode, taxclassnum',
-                                  }
-                                 )
-                       )
-  {
+  my @items = qsearch( { table   => 'tax_rate',
+                         hashref => { disabled => 'Y',
+                                      data_vendor => $format,
+                                    },
+                         select  => 'geocode, taxclassnum',
+                       }
+                     );
+  $count = scalar(@items);
+  foreach my $tax_rate ( @items ) {
+    if ( time - $min_sec > $last ) {
+      my $error = $job->update_statustext(
+        int( 100 * $imported / $count ). ",Remembering disabled taxes"
+      );
+      if ($error) {
+        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+        die $error;
+      }
+      $last = time;
+    }
+    $imported++;
     my $tax_class =
       qsearchs( 'tax_class', { taxclassnum => $tax_rate->taxclassnum } );
     unless ( $tax_class ) {
@@ -1088,14 +1132,26 @@ sub process_download_and_reload {
                   "       part_pkg_option.pkgpart = part_pkg.pkgpart AND ".
                   "       optionname LIKE 'usage_taxproductnum_%' AND ".
                   "       optionvalue != '' )";
-  foreach my $part_pkg ( qsearch( { table => 'part_pkg',
-                                    select  => 'DISTINCT pkgpart,taxproductnum',
-                                    hashref => {},
-                                    extra_sql => $extra_sql,
-                                  }
-                                )
-                       )
-  {
+  @items = qsearch( { table => 'part_pkg',
+                      select  => 'DISTINCT pkgpart,taxproductnum',
+                      hashref => {},
+                      extra_sql => $extra_sql,
+                    }
+                  );
+  $count = scalar(@items);
+  $imported = 0;
+  foreach my $part_pkg ( @items ) {
+    if ( time - $min_sec > $last ) {
+      my $error = $job->update_statustext(
+        int( 100 * $imported / $count ). ",Remembering tax products"
+      );
+      if ($error) {
+        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+        die $error;
+      }
+      $last = time;
+    }
+    $imported++;
     warn "working with package part ". $part_pkg->pkgpart.
       "which has a taxproductnum of ". $part_pkg->taxproductnum. "\n" if $DEBUG;
     my $part_pkg_taxproduct = $part_pkg->taxproduct('');
@@ -1113,6 +1169,11 @@ sub process_download_and_reload {
   }
 
   #wipe out the old data
+  $error = $job->update_statustext( "0,Removing old tax data" );
+  if ($error) {
+    $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+    die $error;
+  }
   foreach my $tax_rate_location ( qsearch( 'tax_rate_location',
                                            { data_vendor => $format,
                                              disabled    => '',
@@ -1133,13 +1194,30 @@ sub process_download_and_reload {
     tax_rate part_pkg_taxrate part_pkg_taxproduct tax_class cust_tax_location
   );
   foreach my $table ( @table ) {
-    foreach my $row ( qsearch( $table, { data_vendor => $format } ) ) {
-      my $error = $row->delete;
-      if ( $error ) {
-        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
-        die $error;
-      }
+    my $dbh = dbh;
+#    my $primary_key = dbdef->table($table)->primary_key;
+#    my $sql = "SELECT $primary_key FROM $table WHERE data_vendor = ".
+    my $sql = "DELETE FROM $table WHERE data_vendor = ".
+      $dbh->quote($format);
+    my $sth = $dbh->prepare($sql);
+    unless ($sth) {
+      $error = $dbh->errstr;
+      $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+      die $error;
     }
+    unless ($sth->execute) {
+      $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+      die "Failed to execute $sql: ". $sth->errstr;
+    }
+#    foreach my $row ( @{ $sth->fetchall_arrayref } ) {
+#      my $record = qsearchs( $table, { $primary_key => $row->[0] } )
+#        or die "Failed to find $table with $primary_key ". $row->[0];
+#      my $error = $record->delete;
+#      if ( $error ) {
+#        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+#        die $error;
+#      }
+#    }
   }
 
   if ( $format eq 'cch' ) {
@@ -1157,11 +1235,29 @@ sub process_download_and_reload {
   }
 
   #import new data
-  process_download_and_update($job, @_);
+  my $statement = ' &process_download_and_update($job, @_); ';
+  eval $statement;
+  if ($@) {
+    $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+    die $@;
+  }
 
   #restore taxproducts
+  $count = scalar(keys %taxproduct);
+  $imported = 0;
   foreach my $pkgpart ( keys %taxproduct ) {
     warn "restoring taxproductnums on pkgpart $pkgpart\n" if $DEBUG;
+    if ( time - $min_sec > $last ) {
+      my $error = $job->update_statustext(
+        int( 100 * $imported / $count ). ",Restoring tax products"
+      );
+      if ( $error ) {
+        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+        die $error;
+      }
+      $last = time;
+    }
+    $imported++;
 
     my $part_pkg = qsearchs('part_pkg', { pkgpart => $pkgpart } );
     unless ( $part_pkg ) {
@@ -1212,7 +1308,20 @@ sub process_download_and_reload {
   }
 
   #disable tax_rates
+  $count = scalar(keys %disabled_tax_rate);
+  $imported = 0;
   foreach my $key (keys %disabled_tax_rate) {
+    if ( time - $min_sec > $last ) {
+      my $error = $job->update_statustext(
+        int( 100 * $imported / $count ). ",Disabling tax rates"
+      );
+      if ( $error ) {
+        $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+        die $error;
+      }
+      $last = time;
+    }
+    $imported++;
     my ($geocode,$taxclass) = split /:/, $key, 2;
     my @tax_class = qsearch( 'tax_class', { data_vendor => $format,
                                             taxclass    => $taxclass,
@@ -1290,11 +1399,14 @@ sub process_download_and_update {
     eval "use XBase;";
     die $@ if $@;
 
-    my $conffile = '%%%FREESIDE_CONF%%%/cchconf';
-    my $conffh = new IO::File "<$conffile" or die "can't open $conffile: $!\n";
-    my ( $urls, $secret, $states ) =
-      map { /^(.*)$/ or die "bad config line in $conffile: $_\n"; $1 }
-          <$conffh>;
+    my $conf = new FS::Conf;
+    die "direct download of tax data not enabled\n" 
+      unless $conf->exists('taxdatadirectdownload');
+    my ( $urls, $username, $secret, $states ) =
+      $conf->config('taxdatadirectdownload');
+    die "No tax download URL provided.  ".
+        "Did you set the taxdatadirectdownload configuration value?\n"
+      unless $urls;
 
     $dir .= '/cch';
 
@@ -1316,7 +1428,7 @@ sub process_download_and_update {
     if (-d $dir) {
 
       if (-d "$dir.4") {
-        opendir(my $dirh, $dir) or die "failed to open $dir.4: $!\n";
+        opendir(my $dirh, "$dir.4") or die "failed to open $dir.4: $!\n";
         foreach my $file (readdir($dirh)) {
           unlink "$dir.4/$file" if (-f "$dir.4/$file");
         }
@@ -1426,6 +1538,7 @@ sub process_download_and_update {
 
     my @insert_list = ();
     my @delete_list = ();
+    my @predelete_list = ();
 
     my @list = (
                  'geocode',  \&FS::tax_rate_location::batch_import, 
@@ -1491,13 +1604,31 @@ sub process_download_and_update {
       %oldlines = ();
 
       push @insert_list, $name, $ifh->filename, $method;
-      unshift @delete_list, $name, $dfh->filename, $method
-        unless $name eq 'detail';
+      if ( $name eq 'geocode' ) {
+        unshift @predelete_list, $name, $dfh->filename, $method
+          unless $name eq 'detail';
+      } else {
+        unshift @delete_list, $name, $dfh->filename, $method
+          unless $name eq 'detail';
+      }
 
       close $dfh;
       close $ifh;
     }
 
+    while( scalar(@predelete_list) ) {
+      my ($name, $file, $method) =
+        (shift @predelete_list, shift @predelete_list, shift @predelete_list);
+
+      my $fmt = "$format-update";
+      $fmt = $fmt. ( $name eq 'zip' ? '-zip' : '' );
+      open my $fh, "< $file" or $error ||= "Can't open $name file $file: $!";
+      $error ||=
+        &{$method}({ 'filehandle' => $fh, 'format' => $fmt }, $job);
+      close $fh;
+      #unlink $file or warn "Can't delete $file: $!";
+    }
+
     while( scalar(@insert_list) ) {
       my ($name, $file, $method) =
         (shift @insert_list, shift @insert_list, shift @insert_list);
@@ -1664,6 +1795,50 @@ sub _upgrade_data {  # class method
         }
       }
 
+    } elsif ( $dbh->{pg_server_version} =~ /^704/ ) {
+
+      # ideally this would be supported in DBIx-DBSchema and friends
+
+      foreach my $column ( @column ) {
+        my $columndef = dbdef->table($self->table)->column($column);
+        unless ($columndef->type eq 'numeric') {
+
+          warn "updating tax_rate column $column to numeric\n" if $DEBUG;
+
+          foreach my $table ( qw( tax_rate h_tax_rate ) ) {
+
+            my $sql = "ALTER TABLE $table RENAME $column TO old_$column";
+            my $sth = $dbh->prepare($sql) or die $dbh->errstr;
+            $sth->execute or die $sth->errstr;
+
+            my $def = dbdef->table($table)->column($column);
+            $def->type('numeric');
+            $def->length('14,8'); 
+            my $null = $def->null;
+            $def->null('NULL');
+
+            $sql = "ALTER TABLE $table ADD COLUMN ". $def->line($dbh);
+            $sth = $dbh->prepare($sql) or die $dbh->errstr;
+            $sth->execute or die $sth->errstr;
+
+            $sql = "UPDATE $table SET $column = CAST( old_$column AS numeric )";
+            $sth = $dbh->prepare($sql) or die $dbh->errstr;
+            $sth->execute or die $sth->errstr;
+
+            unless ( $null eq 'NULL' ) {
+              $sql = "ALTER TABLE $table ALTER $column SET NOT NULL";
+              $sth = $dbh->prepare($sql) or die $dbh->errstr;
+              $sth->execute or die $sth->errstr;
+            }
+
+            $sql = "ALTER TABLE $table DROP old_$column";
+            $sth = $dbh->prepare($sql) or die $dbh->errstr;
+            $sth->execute or die $sth->errstr;
+
+          }
+        }
+      }
+
     } else {
 
       warn "WARNING: tax_rate table upgrade unsupported for this Pg version\n";