support zip5 tax lookups, correct errors with fixed format cch import, inital import...
authorjeff <jeff>
Wed, 3 Dec 2008 01:42:26 +0000 (01:42 +0000)
committerjeff <jeff>
Wed, 3 Dec 2008 01:42:26 +0000 (01:42 +0000)
14 files changed:
FS/FS/Misc.pm
FS/FS/Schema.pm
FS/FS/cust_main.pm
FS/FS/cust_tax_location.pm
FS/FS/part_pkg_taxrate.pm
FS/FS/tax_class.pm
FS/FS/tax_rate.pm
bin/import-tax-rates [new file with mode: 0755]
httemplate/edit/cust_main.cgi
httemplate/edit/cust_main/choose_tax_location.html [new file with mode: 0644]
httemplate/edit/cust_main/contact.html
httemplate/elements/ajaxcontentmws.js [new file with mode: 0644]
httemplate/misc/tax-import.cgi
httemplate/misc/xmlhttp-cust_main-address_standardize.html

index d79f86e..5231350 100644 (file)
@@ -799,7 +799,7 @@ sub csv_from_fixed {
   my $template = join('', map {$total += $_; "A$_"} @$lengths) if $lengths;
 
   my $dir = "%%%FREESIDE_CACHE%%%/cache.$FS::UID::datasrc";
-  my $fh = new File::Temp( TEMPLATE => "CODE.csv.XXXXXXXX",
+  my $fh = new File::Temp( TEMPLATE => "FILE.csv.XXXXXXXX",
                            DIR      => $dir,
                            UNLINK   => 0,
                          ) or return "can't open temp file: $!\n"
index 73f4f26..719f3a8 100644 (file)
@@ -648,6 +648,7 @@ sub tables_hashref {
         'paystate', 'varchar', 'NULL', $char_d, '', '', 
         'paytype',  'varchar', 'NULL', $char_d, '', '', 
         'payip',    'varchar', 'NULL', 15, '', '', 
+        'geocode',  'varchar', 'NULL', 20,  '', '',
         'tax',      'char', 'NULL', 1, '', '', 
         'otaker',   'varchar', '',    32, '', '', 
         'refnum',   'int',  '',     '', '', '', 
@@ -754,11 +755,15 @@ sub tables_hashref {
       'columns' => [
         'custlocationnum', 'serial',  '',     '', '', '', 
         'data_vendor',     'varchar', 'NULL', $char_d, '', '', # update source
+        'city',            'varchar', 'NULL', $char_d, '', '',
+        'postalcity',      'varchar', 'NULL', $char_d, '', '',
+        'county',          'varchar', 'NULL', $char_d, '', '',
         'zip',             'char',    '',     5,  '', '', 
         'state',           'char',    '',     2,  '', '', 
-        'plus4hi',         'char',    '',     4,  '', '', 
-        'plus4lo',         'char',    '',     4,  '', '', 
+        'plus4hi',         'char',    'NULL', 4,  '', '', 
+        'plus4lo',         'char',    'NULL', 4,  '', '', 
         'default_location','char',    'NULL', 1,  '', '', # Y = default for zip
+        'cityflag',        'char',    'NULL', 1,  '', '', # I(n)/O(out)/B(oth)/NULL
         'geocode',         'varchar', '',    20,  '', '', 
       ],
       'primary_key' => 'custlocationnum',
index 8b57b93..c572bea 100644 (file)
@@ -1262,6 +1262,7 @@ sub check {
     || $self->ut_textn('stateid')
     || $self->ut_textn('stateid_state')
     || $self->ut_textn('invoice_terms')
+    || $self->ut_alphan('geocode')
   ;
 
   #barf.  need message catalogs.  i18n.  etc.
@@ -5227,6 +5228,9 @@ Currently this only makes sense for "CCH" as DATA_VENDOR.
 sub geocode {
   my ($self, $data_vendor) = (shift, shift);  #always cch for now
 
+  my $geocode = $self->get('geocode');  #XXX only one data_vendor for geocode
+  return $geocode if $geocode;
+
   my $prefix = ( $conf->exists('tax-ship_address') && length($self->ship_last) )
                ? 'ship_'
                : '';
@@ -5237,7 +5241,6 @@ sub geocode {
   #CCH specific location stuff
   my $extra_sql = "AND plus4lo <= '$plus4' AND plus4hi >= '$plus4'";
 
-  my $geocode = '';
   my @cust_tax_location =
     qsearch( {
                'table'     => 'cust_tax_location', 
index cd24cc8..f77ad37 100644 (file)
@@ -113,15 +113,33 @@ sub check {
   my $error = 
     $self->ut_numbern('custlocationnum')
     || $self->ut_text('data_vendor')
-    || $self->ut_number('zip')
+    || $self->ut_textn('city')
+    || $self->ut_textn('postalcity')
+    || $self->ut_textn('county')
     || $self->ut_text('state')
-    || $self->ut_number('plus4hi')
-    || $self->ut_number('plus4lo')
-    || $self->ut_enum('default', [ '', ' ', 'Y' ] )
-    || $self->ut_number('geocode')
+    || $self->ut_numbern('plus4hi')
+    || $self->ut_numbern('plus4lo')
+    || $self->ut_enum('default', [ '', ' ', 'Y' ] ) # wtf?
+    || $self->ut_enum('cityflag', [ '', 'I', 'O', 'B' ] )
+    || $self->ut_alpha('geocode')
   ;
   return $error if $error;
 
+  #ugh!  cch canada weirdness
+  if ($self->state eq 'CN') {
+    $error = "Illegal cch canadian zip"
+     unless $self->zip =~ /^[A-Z]$/;
+  } else {
+    $error = $self->ut_number('zip', $self->state eq 'CN' ? 'CA' : 'US');
+  }
+  return $error if $error;
+
+  #ugh!  cch canada weirdness
+  return "must specify either city/county or plus4lo/plus4hi"
+    unless ( $self->plus4lo && $self->plus4hi || 
+             ($self->city || $self->state eq 'CN') && $self->county
+           );
+
   $self->SUPER::check;
 }
 
@@ -138,15 +156,24 @@ sub batch_import {
 
   my @column_lengths = ();
   my @column_callbacks = ();
-  if ( $format eq 'cch-fixed' || $format eq 'cch-fixed-update' ) {
+  if ( $format =~ /^cch-fixed/ ) {
     $format =~ s/-fixed//;
-    push @column_lengths, qw( 5 2 4 4 10 1 );
-    push @column_lengths, 1 if $format eq 'cch-update';
+    my $f = $format;
+    my $update = 0;
+    $f =~ s/-update// && ($update = 1);
+    if ($f eq 'cch') {
+      push @column_lengths, qw( 5 2 4 4 10 1 );
+    } elsif ( $f eq 'cch-zip' ) {
+      push @column_lengths, qw( 5 28 25 2 28 5 1 1 10 1 2 );
+    } else {
+      return "Unknown format: $format";
+    }
+    push @column_lengths, 1 if $update;
   }
 
   my $line;
   my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
-  if ( $job || scalar(@column_callbacks) ) {
+  if ( $job || scalar(@column_lengths) ) {
     my $error = csv_from_fixed(\$fh, \$count, \@column_lengths);
     return $error if $error;
   }
@@ -182,6 +209,38 @@ sub batch_import {
       
     };
 
+  } elsif ( $format eq 'cch-zip' || $format eq 'cch-update-zip' ) {
+    @fields = qw( zip city county state postalcity countyfips countydef default geocode cityflag unique );
+    push @fields, 'actionflag' if $format eq 'cch-update';
+
+    $imported++ if $format eq 'cch-update'; #empty file ok
+    
+    $hook = sub {
+      my $hash = shift;
+
+      $hash->{'data_vendor'} = 'cch-zip';
+      delete($hash->{$_}) foreach qw( countyfips countydef unique );
+
+      if (exists($hash->{actionflag}) && $hash->{actionflag} eq 'D') {
+        delete($hash->{actionflag});
+
+        my $cust_tax_location = qsearchs('cust_tax_location', $hash);
+        return "Can't find cust_tax_location to delete: ".
+               join(" ", map { "$_ => ". $hash->{$_} } @fields)
+          unless $cust_tax_location;
+
+        my $error = $cust_tax_location->delete;
+        return $error if $error;
+
+        delete($hash->{$_}) foreach (keys %$hash);
+      }
+
+      delete($hash->{'actionflag'});
+
+      '';
+      
+    };
+
   } elsif ( $format eq 'extended' ) {
     die "unimplemented\n";
     @fields = qw( );
index 1563621..9e1c723 100644 (file)
@@ -244,7 +244,7 @@ sub batch_import {
       unless ($part_pkg_taxproduct) {
         return "Can't find part_pkg_taxproduct for txmatrix deletion: ".
                join(" ", map { "$_ => ". $hash->{$_} } @fields)
-          if $hash->{'actionflag'} eq 'D';
+          if ($hash->{'actionfield'} && $hash->{'actionflag'} eq 'D');
 
         $part_pkg_taxproduct{'description'} = 
           join(' : ', (map{ $hash->{$_} } qw(groupdesc itemdesc)),
@@ -279,7 +279,9 @@ sub batch_import {
 
         return "Can't find tax class for txmatrix deletion: ".
                join(" ", map { "$_ => ". $hash->{$_} } @fields)
-          if ($hash->{'actionflag'} eq 'D' && !$tax_class && $class ne ':');
+          if ( $hash->{'actionflag'} && $hash->{'actionflag'} eq 'D' &&
+               !$tax_class && $class ne ':'
+             );
 
         delete($hash->{$_}) foreach @{$map{$item}};
       }
index 8ce70f5..51d87ab 100644 (file)
@@ -158,7 +158,7 @@ sub batch_import {
 
   my $line;
   my ( $count, $last, $min_sec ) = (0, time, 5); #progressbar
-  if ( $job || scalar(@column_callbacks) ) {
+  if ( $job || scalar(@column_lengths) ) {
     my $error = csv_from_fixed(\$fh, \$count, \@column_lengths);
     return $error if $error;
   }
index f45d014..2837f9c 100644 (file)
@@ -570,14 +570,22 @@ sub batch_import {
       }
 
       my $actionflag = delete($hash->{'actionflag'});
+
+      $hash->{'taxname'} =~ s/`/'/g; 
+      $hash->{'taxname'} =~ s|\\|/|g;
+
+      return '' if $format eq 'cch';  # but not cch-update
+
       if ($actionflag eq 'I') {
-        $insert{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = $hash;
+        $insert{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = { %$hash };
       }elsif ($actionflag eq 'D') {
-        $delete{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = $hash;
+        $delete{ $hash->{'geocode'}. ':'. $hash->{'taxclassnum'} } = { %$hash };
       }else{
         return "Unexpected action flag: ". $hash->{'actionflag'};
       }
 
+      delete($hash->{$_}) for keys %$hash;
+
       '';
 
     };
@@ -641,6 +649,18 @@ sub batch_import {
       return $error;
     }
 
+    if (scalar(keys %tax_rate)) { #inserts only, not updates for cch
+
+      my $tax_rate = new FS::tax_rate( \%tax_rate );
+      $error = $tax_rate->insert;
+
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return "can't insert tax_rate for $line: $error";
+      }
+
+    }
+
     $imported++;
 
   }
@@ -765,23 +785,30 @@ sub process_batch {
     local $FS::UID::AutoCommit = 0;
     my $dbh = dbh;
     my $error = '';
+    my $have_location = 0;
 
     my @list = ( 'CODE',     'codefile',  \&FS::tax_class::batch_import,
                  'PLUS4',    'plus4file', \&FS::cust_tax_location::batch_import,
+                 'ZIP',      'zipfile',   \&FS::cust_tax_location::batch_import,
                  'TXMATRIX', 'txmatrix',  \&FS::part_pkg_taxrate::batch_import,
                  'DETAIL',   'detail',    \&FS::tax_rate::batch_import,
                );
     while( scalar(@list) ) {
       my ($name, $file, $import_sub) = (shift @list, shift @list, shift @list);
       unless ($files{$file}) {
+        next if $name eq 'PLUS4';
         $error = "No $name supplied";
+        $error = "Neither PLUS4 nor ZIP supplied"
+          if ($name eq 'ZIP' && !$have_location);
         next;
       }
+      $have_location = 1 if $name eq 'PLUS4';
+      my $fmt = $format. ( $name eq 'ZIP' ? '-zip' : '' );
       my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc;
       my $filename = "$dir/".  $files{$file};
       open my $fh, "< $filename" or $error ||= "Can't open $name file: $!";
 
-      $error ||= &{$import_sub}({ 'filehandle' => $fh, 'format' => $format }, $job);
+      $error ||= &{$import_sub}({ 'filehandle' => $fh, 'format' => $fmt }, $job);
       close $fh;
       unlink $filename or warn "Can't delete $filename: $!";
     }
@@ -804,12 +831,23 @@ sub process_batch {
 
     my @list = ( 'CODE',     'codefile',  \&FS::tax_class::batch_import,
                  'PLUS4',    'plus4file', \&FS::cust_tax_location::batch_import,
+                 'ZIP',      'zipfile',   \&FS::cust_tax_location::batch_import,
                  'TXMATRIX', 'txmatrix',  \&FS::part_pkg_taxrate::batch_import,
                );
     my $dir = '%%%FREESIDE_CACHE%%%/cache.'. $FS::UID::datasrc;
     while( scalar(@list) ) {
       my ($name, $file, $import_sub) = (shift @list, shift @list, shift @list);
       unless ($files{$file}) {
+        my $vendor = $name eq 'ZIP' ? 'cch' : 'cch-zip';
+        next     # update expected only for previously installed location data
+          if (   ($name eq 'PLUS4' || $name eq 'ZIP')
+               && !scalar( qsearch( { table => 'cust_tax_location',
+                                      hashref => { data_vendor => $vendor },
+                                      select => 'DISTINCT data_vendor',
+                                  } )
+                         )
+             );
+
         $error = "No $name supplied";
         next;
       }
@@ -849,9 +887,10 @@ sub process_batch {
       my ($name, $file, $import_sub) =
         (shift @insert_list, shift @insert_list, shift @insert_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' => $format }, $job);
+        &{$import_sub}({ 'filehandle' => $fh, 'format' => $fmt }, $job);
       close $fh;
       unlink $file or warn "Can't delete $file: $!";
     }
@@ -871,9 +910,10 @@ sub process_batch {
       my ($name, $file, $import_sub) =
         (shift @delete_list, shift @delete_list, shift @delete_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' => $format }, $job);
+        &{$import_sub}({ 'filehandle' => $fh, 'format' => $fmt }, $job);
       close $fh;
       unlink $file or warn "Can't delete $file: $!";
     }
diff --git a/bin/import-tax-rates b/bin/import-tax-rates
new file mode 100755 (executable)
index 0000000..1cb76e0
--- /dev/null
@@ -0,0 +1,56 @@
+#!/usr/bin/perl -Tw
+
+use strict;
+use vars qw($opt_c $opt_p $opt_t $opt_d $opt_z $opt_f);
+use vars qw($DEBUG);
+use Getopt::Std;
+use FS::UID qw(adminsuidsetup);
+use FS::Conf;
+use FS::tax_rate;
+use FS::cust_tax_location;
+
+getopts('c:p:t:d:z:f:');
+
+my $user = shift or die &usage;
+my $dbh = adminsuidsetup $user;
+
+my ($format) = $opt_f =~ /^([-\w]+)$/;
+
+my @list = (
+  'CODE',     $opt_c, \&FS::tax_class::batch_import,
+  'PLUS4',    $opt_p, \&FS::cust_tax_location::batch_import,
+  'ZIP',      $opt_z, \&FS::cust_tax_location::batch_import,
+  'TXMATRIX', $opt_t, \&FS::part_pkg_taxrate::batch_import,
+  'DETAIL',   $opt_d, \&FS::tax_rate::batch_import,
+);
+
+my $oldAutoCommit = $FS::UID::AutoCommit;
+local $FS::UID::AutoCommit = 0;
+
+my $error = '';
+
+while(@list) {
+  my ($name, $file, $method) = splice(@list, 0, 3);
+
+  my $fh;
+
+  $file =~ /^([\s\d\w.]+)$/ or die "Illegal filename: $file\n";
+  $file = $1;
+
+  my $f = $format;
+  $f .= '-zip' if $name eq 'ZIP';
+
+  open $fh, '<', $file or die "can't open $name file: $!\n";
+  $error ||= &{$method}( { filehandle => $fh, 'format' => $f, } );
+
+  die "error while processing $file: $error" if $error;
+  close $fh;
+}
+
+if ($error) {
+  $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
+}else{
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+}
+
+sub usage { die "Usage:\nimport-tax-rates f FORMAT -c CODEFILE -p PLUS4FILE -z ZIPFILE -t TXMATRIXFILE -d DETAILFILE  user\n\n"; }
index 8de73c5..8336183 100755 (executable)
@@ -5,6 +5,7 @@
 ) %>
 
 <% include('/elements/init_overlib.html') %>
+<SCRIPT SRC="<% $fsurl %>elements/ajaxcontentmws.js" TYPE="text/javascript"></SCRIPT>
 
 <% include('/elements/error.html') %>
 
@@ -249,6 +250,8 @@ function bottomfixup(what) {
     'ship_county', 'ship_state', 'ship_zip', 'ship_country',
     'ship_daytime','ship_night', 'ship_fax',
 
+    'geocode',
+
     'select' // XXX key
   );
 
@@ -329,6 +332,8 @@ function update_address(arg) {
 
   var changed  = argsHash['address_standardized'];
   var ship_changed = argsHash['ship_address_standardized'];
+  var error = argsHash['error'];
+  var ship_error = argsHash['ship_error'];
 
   //yay closures
   standardize_address = function () {
@@ -355,7 +360,14 @@ function update_address(arg) {
 
   }
 
-  if ( changed || ship_changed ) {
+  if ( error || ship_error ) {
+
+    var url = "cust_main/choose_tax_location.html?data_vendor=cch-zip;city="+document.bottomform.elements['city'].value+";state="+document.bottomform.elements['state'].value+";zip="+document.bottomform.elements['zip'].value+";";
+    // popup a chooser
+    OLgetAJAX( url, update_geocode, 300 );
+
+
+  } else if ( changed || ship_changed ) {
 
 %   if ( $conf->exists('cust_main-auto_standardize_address') ) {
 
@@ -448,6 +460,26 @@ function update_address(arg) {
 
 }
 
+function update_geocode() {
+
+  //yay closures
+  set_geocode = function (what) {
+
+    //alert(what.options[what.selectedIndex].value);
+    var argsHash = eval('(' + what.options[what.selectedIndex].value + ')');
+    document.bottomform.elements['city'].value = argsHash['city'];
+    document.bottomform.elements['state'].value = argsHash['state'];
+    document.bottomform.elements['zip'].value = argsHash['zip'];
+    document.bottomform.elements['geocode'].value = argsHash['geocode'];
+
+  }
+
+  // popup a chooser
+
+  overlib( OLresponseAJAX, CAPTION, 'Select tax location', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+
+}
+
 function copyelement(from, to) {
   if ( from == undefined ) {
     to.value = '';
@@ -490,6 +522,8 @@ function copyelement(from, to) {
 %     'ship_county', 'ship_state', 'ship_zip', 'ship_country',
 %     'ship_daytime','ship_night', 'ship_fax',
 %     
+%     'geocode',
+%     
 %     'select', #XXX key
 %
 %     'payauto',
diff --git a/httemplate/edit/cust_main/choose_tax_location.html b/httemplate/edit/cust_main/choose_tax_location.html
new file mode 100644 (file)
index 0000000..23fdbf2
--- /dev/null
@@ -0,0 +1,74 @@
+<FORM NAME="choosegeocodeform">
+<CENTER><BR><B>Choose tax location</B><BR><BR>
+<P STYLE="<% $style %>"><% $header %></P>
+
+<SELECT NAME='geocodes' ID='geocodes' STYLE="<% $style %>">
+% foreach my $location (@cust_tax_location) {
+%   my $value = encode_entities(objToJson({ zip => $zip5,
+%                                           map { $_ => $location->$_ }
+%                                             qw ( city state geocode )
+%                                        })
+%                              );
+%   my $content = '';
+%   $content .= $location->$_. '&nbsp;' x ( $max{$_} - length($location->$_) )
+%     foreach qw( city county state );
+%   $content .=   $location->cityflag eq 'I' ? 'Y' : 'N' ;
+%   my $selected = '' ;
+%   if (!$have_selected && lc($location->city) eq lc($city)) {
+%     $selected = 'SELECTED';
+%   }
+  <OPTION VALUE="<% $value %>" STYLE="<% $style %>" <% $selected %>><% $content %>
+% }
+</SELECT><BR><BR>
+
+<TABLE><TR>
+  <TD> <BUTTON TYPE="button" onClick="set_geocode(document.getElementById('geocodes')); document.bottomform.submit();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Set location </BUTTON></TD>
+  <TD><BUTTON TYPE="button" onClick="document.bottomform.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission </BUTTON></TD>
+</TR>
+</TABLE>
+
+</CENTER>
+</FORM>
+<%init>
+
+my $conf = new FS::Conf;
+my $have_selected = 0;
+
+my ($data_vendor) = $cgi->param('data_vendor') =~ /^([-\w]+)$/;
+my ($city)  = $cgi->param('city')  =~ /^([\w ]+)$/;
+my ($state) = $cgi->param('state') =~ /^(\w+)$/;
+my ($zip)   = $cgi->param('zip')   =~ /^([-\w]+)$/;
+
+my($zip5, $zip4) = split('-', $zip);
+
+my $hashref = { data_vendor => $data_vendor,
+                #city        => $city,
+                #state       => $state,
+                zip         => $zip5,
+              };
+#my @keys = qw ( city state zip );
+my @keys = qw ( zip );
+my @cust_tax_location = ();
+until ( @cust_tax_location ) {
+  @cust_tax_location = qsearch( 'cust_tax_location', $hashref );
+  last unless scalar(@keys);
+  delete $hashref->{ shift @keys };
+} 
+
+my %max = ( city => 4, county => 6, state => 5);
+foreach my $location (@cust_tax_location) {
+  foreach ( qw( city county state ) ) {
+    my $length = length($location->$_);
+    $max{$_} = ($length > $max{$_}) ? $length : $max{$_};
+  }
+}
+$max{$_}++ foreach qw( city county state );
+
+my $header = '&nbsp;&nbsp;';
+$header .= $_. '&nbsp;' x ( $max{lc($_)} - length($_) )
+  foreach qw( City County State );
+$header .=   "In city?";
+
+my $style = "font-family:monospace;";
+
+</%init>
index 21c6b29..2d37dc8 100644 (file)
 <TR>
   <TH ALIGN="right"><%$r%>Country</TH>
   <TD COLSPAN=5><% include('select-country.html', %select_hash ) %></TD>
+% if ( !$pre ) { 
+  <TD><INPUT TYPE="hidden" NAME="geocode" VALUE="<% $opt{geocode} %>"></TD>
+% } 
+
 </TR>
 
 <TR>
diff --git a/httemplate/elements/ajaxcontentmws.js b/httemplate/elements/ajaxcontentmws.js
new file mode 100644 (file)
index 0000000..9177049
--- /dev/null
@@ -0,0 +1,185 @@
+/*\r
+ ajaxcontentmws.js - Foteos Macrides (author and Copyright holder)\r
+    Initial: June 22, 2006 - Last Revised: March 24, 2008\r
+ Wrapper function set for getting and using the responseText and / or\r
+ responseXML from a GET or POST XMLHttpRequest, which can be used to\r
+ generate dynamic content for overlib or overlib2 calls, or to modify\r
+ the content of a displayed STICKY popup dynamically.\r
+\r
+ For GET Use:\r
+       onmouseover="return OLgetAJAX(url, command, delay, css);"\r
+       onmouseout="OLclearAJAX();"  (if delay > 0)\r
+ or:\r
+       onclick="OLgetAJAX(url, command, 0, css); return false;"\r
+ or:\r
+       onload="OLgetAJAX(url, command, 0, css);\r
\r
+ Where:\r
+       url (required)\r
+ is a quoted string, or unquoted string variable name or array entry, with\r
+ the full, relative, or partial URL for a file or a server-side script (php,\r
+ asp, or cgi, e.g. perl), and may have a query string appended (e.g.,\r
+ 'http://my.domain.com/scripts/myScript.php?foo=bar&life=grand').\r
+ And:\r
+       command (required)\r
+ is the function reference (unquoted name without parens) of a function to\r
+ be called when the server's response has been received (it could instead be\r
+ an inline function, i.e., defined within the 2nd argument, or a quoted string\r
+ for a function with parens and any args)\r
+ And:\r
+       delay (may be omitted unless css is included)\r
+ is an unquoted number indicating the number of millisecs to wait before\r
+ initiating an XMLHttpRequest GET request. It should be 0 when using onclick\r
+ or onload, but may be a modest value such as 300 for onmouseover to avoid\r
+ any chatter of requests. When used with onmouseover, include:\r
+       onmouseout="OLclearAJAX();"\r
+ to clear the request if the user does not hover for at least that long.  If\r
+ the popup is not STICKY, include an nd or nd2 call, e.g.,\r
+       onmouseout="OLclearAJAX(); nd();"\r
+ And:\r
+       css (may be omitted)\r
+ is a quoted string with the CSS class (e.g. 'ovfl510' for\r
+ .ovfl510 {width:510px; height:145px; overflow:auto; ...} ) for a div to\r
+ encase the responseText and set the width, height and scrollbars in the\r
+ main text area of the popup, or the unquoted number 0 if no encasing div\r
+ is to be used.\r
+\r
+ For POST substitute OLpostAJAX(url, qry, command, delay, css);\r
+ Where\r
+       qry (required)\r
+ is the string to be posted, typically a query string (without a lead ?)\r
+ and the other arguments are as above.\r
+\r
+ See http://www.macridesweb.com/oltest/AJAX.html for more information.\r
+*/\r
+\r
+// Initialize our global variables for this function set.\r
+var OLhttp=false,OLcommandAJAX=null,OLdelayidAJAX=0,OLclassAJAX='',\r
+OLresponseAJAX='',OLabortAJAX=0,OLdebugAJAX=0;\r
+\r
+// Create a series of wrapper functions (e.g. OLcmdT#() for  ones which\r
+// use OLhttp.responseText via the OLresponseAJAX global, and OLcmdX#()\r
+// for ones which use OLhttp.responseXML) whose reference (unquoted name\r
+// without parens) is the 2nd argument in OLgetAJAX(url,command,delay,css)\r
+// calls.  This one is for the first example in the AJAX.html support\r
+// document, to use the OLresponseAJAX global as the lead argument for an\r
+// overlib popup. Put your functions in the head, or in another imported\r
+// .js file, so that they will not be affected by updates of this .js file.\r
+//\r
+function OLcmdExT1() {\r
+ return overlib(OLresponseAJAX, TEXTPADDING,0, CAPTIONPADDING,4,\r
+  CAPTION,'Example with AJAX content via <span '\r
+  +'class="yellow">responseText</span>.&nbsp; Popup scrolls with the window.',\r
+  WRAP, BORDER,2, STICKY, CLOSECLICK, SCROLL,\r
+  MIDX,0, RELY,100,\r
+  STATUS,'Example with AJAX content via responseText of XMLHttpResponse');\r
+}\r
+\r
+// Alert for old browsers which lack XMLHttpRequest support. \r
+function OLsorryAJAX() {\r
+ alert('Sorry, AJAX is not supported by your browser.');\r
+ return false;\r
+}\r
+\r
+// Check 2nd arg for function\r
+function OLchkFuncAJAX(ar){\r
+ var t=typeof ar;return (((t=='function'))||((t=='string')&&(/.+\(.*\)/.test(ar))));\r
+}\r
+\r
+// Alert for bad 2nd argument\r
+function OLnotFuncAJAX(m) {\r
+  if(over)cClick();\r
+  alert('The 2nd arg of OL'+m+'AJAX is not a function reference, nor an inline function, '\r
+  +'nor a quoted string with a function indicated.');\r
+  return OLclearAJAX();\r
+}\r
+\r
+// Alert for indicating an XMLHttpRequest network error.\r
+function OLerrorAJAX() {\r
+ if(OLhttp.status&&OLhttp.status!=2147746065)alert('Network error '+OLhttp.status+'. Try again later.');\r
+ return false;\r
+}\r
+\r
+// Returns a new XMLHttpRequest object, or false for older browsers\r
+// which did not yet support it.  Called as OLhttp=OLnewXMLHttp() via\r
+// the OLgetAJAX(url,command,delay,css) wrapper function.\r
+//\r
+function OLnewXMLHttp() {\r
+ var f=false,req=f;\r
+ if(window.XMLHttpRequest)eval(new Array('try{',\r
+ 'req=new XMLHttpRequest();','}catch(e){','req=f;','}').join('\n'));\r
+ /*@cc_on @if(@_jscript_version>=5)if(!req)\r
+ eval(new Array('try{','req=new ActiveXObject("Msxml2.XMLHTTP");',\r
+ '}catch(e){','try{','req=new ActiveXObject("Microsoft.XMLHTTP");',\r
+ '}catch(e){','req=f;','}}').join('\n')); @end @*/\r
+ return req;\r
+}\r
+\r
+// Handle the OLhttp.responseText string from the XMLHttpRequest object.\r
+function OLdoAJAX() {\r
+ if(OLhttp.readyState==4){\r
+  if(OLdebugAJAX)alert(\r
+    'OLhttp.status = '+OLhttp.status+'\n'\r
+   +'OLhttp.statusText = '+OLhttp.statusText+'\n'\r
+   +'OLhttp.getAllResponseHeaders() = \n'\r
+   +OLhttp.getAllResponseHeaders()+'\n'\r
+   +'OLhttp.getResponseHeader("Content-Type") = '\r
+   +OLhttp.getResponseHeader("Content-Type")+'\n');\r
+  if(OLhttp.status==200||(OLhttp.status==0&&!OLabortAJAX&&!OLie55)){\r
+   OLresponseAJAX=OLclassAJAX?'<div class="'+OLclassAJAX+'">':'';\r
+   OLresponseAJAX += OLhttp.responseText;\r
+   OLresponseAJAX += OLclassAJAX?'</div>':'';\r
+   if(OLdebugAJAX)alert('OLresponseAJAX = \n'+OLresponseAJAX);\r
+   OLclassAJAX=0;\r
+   return (typeof OLcommandAJAX=='string')?eval(OLcommandAJAX):OLcommandAJAX();\r
+  }else{\r
+   OLclassAJAX=0;\r
+   OLabortAJAX=0;\r
+   return OLerrorAJAX();\r
+  }\r
+ }\r
+}\r
+\r
+// Actually make the request initiated via OLgetAJAX or OLpostAJAX, or\r
+// invoke a "permission denied" alert if a cross-domain URL was used.\r
+function OLsetAJAX(url,qry) {\r
+ if(window.location.protocol.indexOf('http')==0&&\r
+ (url.indexOf('file:')==0||url.indexOf('ftp:')==0)){\r
+ alert('[object Error]\n(Cross-domain access not permitted)');return false;}\r
+ qry=(qry||null);var s='',m=(qry)?'POST':'GET';OLabortAJAX=0;\r
+ OLdelayidAJAX=0;eval(new Array('try{','OLhttp.open(m,url,true);',\r
+ '}catch(e){','s=e','OLhttp=false;','}').join('\n'));if(!OLhttp){\r
+ alert(s+'\n(Cross-domain access not permitted)');return false;}if(qry)\r
+ OLhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');\r
+ OLhttp.onreadystatechange=OLdoAJAX;\r
+ OLhttp.send(qry);\r
+}\r
+\r
+// Clear or abort any delayed OLsetAJAX call or pending request. \r
+function OLclearAJAX() {\r
+ if(OLdelayidAJAX){clearTimeout(OLdelayidAJAX);OLdelayidAJAX=0;}\r
+ if(OLhttp&&!OLdebugAJAX){OLabortAJAX=1;OLhttp.abort();}\r
+ return false;\r
+}\r
+\r
+// Load a new XMLHttpRequest object into the OLhttp global, load the\r
+// OLcommandAJAX and OLclassAJAX globals, and initiate a GET request\r
+// via OLsetAJAX(url) to populate OLhttp.\r
+function OLgetAJAX(url,command,delay,css) {\r
+ if(!OLchkFuncAJAX(command))return OLnotFuncAJAX('get');\r
+ OLclearAJAX();OLhttp=OLnewXMLHttp();if(!OLhttp)return OLsorryAJAX();\r
+ OLcommandAJAX=command;delay=(delay||0);css=(css||0);OLclassAJAX=css;\r
+ if(delay)OLdelayidAJAX=setTimeout("OLsetAJAX('"+url+"')",delay);\r
+ else OLsetAJAX(url);\r
+}\r
+\r
+// Load a new XMLHttpRequest object into the OLhttp global, load the\r
+// OLcommandAJAX and OLclassAJAX globals, and initiate a POST request\r
+// via OLsetAJAX(url,qry) to populate OLhttp.\r
+function OLpostAJAX(url,qry,command,delay,css) {\r
+ if(!OLchkFuncAJAX(command))return OLnotFuncAJAX('post');\r
+ OLclearAJAX();OLhttp=OLnewXMLHttp();if(!OLhttp)return OLsorryAJAX();\r
+ qry=(qry||0);OLcommandAJAX=command;delay=(delay||0);css=(css||0);OLclassAJAX=css;\r
+ if(delay)OLdelayidAJAX=setTimeout("OLsetAJAX('"+url+"','"+qry+"')",delay);\r
+ else OLsetAJAX(url,qry);\r
+}\r
index 2bae6f1..bca623f 100644 (file)
@@ -29,11 +29,13 @@ Import a CSV file set containing tax rate records.
   <% include( '/elements/file-upload.html',
                 'field'    => [ 'codefile',
                                 'plus4file',
+                                'zipfile',
                                 'txmatrix',
                                 'detail',
                               ],
                 'label'    => [ 'code filename',
                                 'plus4 filename',
+                                'zip filename',
                                 'txmatrix filename',
                                 'detail filename',
                               ],
index 8532bb2..72fa4a4 100644 (file)
@@ -72,7 +72,9 @@ if ( $sub eq 'address_standardize' ) {
 
       } else {
 
-        warn "USPS WebTools error: ". $verifier->response. "\n";
+        $return->{$pre.'error'} = "USPS WebTools error: ".
+                                  $verifier->{error}{description};
+
 
       }