summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjeff <jeff>2008-12-03 01:42:26 +0000
committerjeff <jeff>2008-12-03 01:42:26 +0000
commit4e77f6927631e226e13da84082be66867b71330f (patch)
tree79f72113426a69e7ee1383d72ab47a7c59375fe1
parent43bd4c723d9da8dbf2ed0428620aade17e44bac9 (diff)
support zip5 tax lookups, correct errors with fixed format cch import, inital import performance improvements, noise reduction on imports, tool for inital import
-rw-r--r--FS/FS/Misc.pm2
-rw-r--r--FS/FS/Schema.pm9
-rw-r--r--FS/FS/cust_main.pm5
-rw-r--r--FS/FS/cust_tax_location.pm77
-rw-r--r--FS/FS/part_pkg_taxrate.pm6
-rw-r--r--FS/FS/tax_class.pm2
-rw-r--r--FS/FS/tax_rate.pm50
-rwxr-xr-xbin/import-tax-rates56
-rwxr-xr-xhttemplate/edit/cust_main.cgi36
-rw-r--r--httemplate/edit/cust_main/choose_tax_location.html74
-rw-r--r--httemplate/edit/cust_main/contact.html4
-rw-r--r--httemplate/elements/ajaxcontentmws.js185
-rw-r--r--httemplate/misc/tax-import.cgi2
-rw-r--r--httemplate/misc/xmlhttp-cust_main-address_standardize.html4
14 files changed, 489 insertions, 23 deletions
diff --git a/FS/FS/Misc.pm b/FS/FS/Misc.pm
index d79f86e..5231350 100644
--- a/FS/FS/Misc.pm
+++ b/FS/FS/Misc.pm
@@ -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"
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 73f4f26..719f3a8 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -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',
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 8b57b93..c572bea 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -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',
diff --git a/FS/FS/cust_tax_location.pm b/FS/FS/cust_tax_location.pm
index cd24cc8..f77ad37 100644
--- a/FS/FS/cust_tax_location.pm
+++ b/FS/FS/cust_tax_location.pm
@@ -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( );
diff --git a/FS/FS/part_pkg_taxrate.pm b/FS/FS/part_pkg_taxrate.pm
index 1563621..9e1c723 100644
--- a/FS/FS/part_pkg_taxrate.pm
+++ b/FS/FS/part_pkg_taxrate.pm
@@ -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}};
}
diff --git a/FS/FS/tax_class.pm b/FS/FS/tax_class.pm
index 8ce70f5..51d87ab 100644
--- a/FS/FS/tax_class.pm
+++ b/FS/FS/tax_class.pm
@@ -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;
}
diff --git a/FS/FS/tax_rate.pm b/FS/FS/tax_rate.pm
index f45d014..2837f9c 100644
--- a/FS/FS/tax_rate.pm
+++ b/FS/FS/tax_rate.pm
@@ -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
index 0000000..1cb76e0
--- /dev/null
+++ b/bin/import-tax-rates
@@ -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"; }
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index 8de73c5..8336183 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -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
index 0000000..23fdbf2
--- /dev/null
+++ b/httemplate/edit/cust_main/choose_tax_location.html
@@ -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>
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
index 21c6b29..2d37dc8 100644
--- a/httemplate/edit/cust_main/contact.html
+++ b/httemplate/edit/cust_main/contact.html
@@ -69,6 +69,10 @@
<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
index 0000000..9177049
--- /dev/null
+++ b/httemplate/elements/ajaxcontentmws.js
@@ -0,0 +1,185 @@
+/*
+ ajaxcontentmws.js - Foteos Macrides (author and Copyright holder)
+ Initial: June 22, 2006 - Last Revised: March 24, 2008
+ Wrapper function set for getting and using the responseText and / or
+ responseXML from a GET or POST XMLHttpRequest, which can be used to
+ generate dynamic content for overlib or overlib2 calls, or to modify
+ the content of a displayed STICKY popup dynamically.
+
+ For GET Use:
+ onmouseover="return OLgetAJAX(url, command, delay, css);"
+ onmouseout="OLclearAJAX();" (if delay > 0)
+ or:
+ onclick="OLgetAJAX(url, command, 0, css); return false;"
+ or:
+ onload="OLgetAJAX(url, command, 0, css);
+
+ Where:
+ url (required)
+ is a quoted string, or unquoted string variable name or array entry, with
+ the full, relative, or partial URL for a file or a server-side script (php,
+ asp, or cgi, e.g. perl), and may have a query string appended (e.g.,
+ 'http://my.domain.com/scripts/myScript.php?foo=bar&life=grand').
+ And:
+ command (required)
+ is the function reference (unquoted name without parens) of a function to
+ be called when the server's response has been received (it could instead be
+ an inline function, i.e., defined within the 2nd argument, or a quoted string
+ for a function with parens and any args)
+ And:
+ delay (may be omitted unless css is included)
+ is an unquoted number indicating the number of millisecs to wait before
+ initiating an XMLHttpRequest GET request. It should be 0 when using onclick
+ or onload, but may be a modest value such as 300 for onmouseover to avoid
+ any chatter of requests. When used with onmouseover, include:
+ onmouseout="OLclearAJAX();"
+ to clear the request if the user does not hover for at least that long. If
+ the popup is not STICKY, include an nd or nd2 call, e.g.,
+ onmouseout="OLclearAJAX(); nd();"
+ And:
+ css (may be omitted)
+ is a quoted string with the CSS class (e.g. 'ovfl510' for
+ .ovfl510 {width:510px; height:145px; overflow:auto; ...} ) for a div to
+ encase the responseText and set the width, height and scrollbars in the
+ main text area of the popup, or the unquoted number 0 if no encasing div
+ is to be used.
+
+ For POST substitute OLpostAJAX(url, qry, command, delay, css);
+ Where
+ qry (required)
+ is the string to be posted, typically a query string (without a lead ?)
+ and the other arguments are as above.
+
+ See http://www.macridesweb.com/oltest/AJAX.html for more information.
+*/
+
+// Initialize our global variables for this function set.
+var OLhttp=false,OLcommandAJAX=null,OLdelayidAJAX=0,OLclassAJAX='',
+OLresponseAJAX='',OLabortAJAX=0,OLdebugAJAX=0;
+
+// Create a series of wrapper functions (e.g. OLcmdT#() for ones which
+// use OLhttp.responseText via the OLresponseAJAX global, and OLcmdX#()
+// for ones which use OLhttp.responseXML) whose reference (unquoted name
+// without parens) is the 2nd argument in OLgetAJAX(url,command,delay,css)
+// calls. This one is for the first example in the AJAX.html support
+// document, to use the OLresponseAJAX global as the lead argument for an
+// overlib popup. Put your functions in the head, or in another imported
+// .js file, so that they will not be affected by updates of this .js file.
+//
+function OLcmdExT1() {
+ return overlib(OLresponseAJAX, TEXTPADDING,0, CAPTIONPADDING,4,
+ CAPTION,'Example with AJAX content via <span '
+ +'class="yellow">responseText</span>.&nbsp; Popup scrolls with the window.',
+ WRAP, BORDER,2, STICKY, CLOSECLICK, SCROLL,
+ MIDX,0, RELY,100,
+ STATUS,'Example with AJAX content via responseText of XMLHttpResponse');
+}
+
+// Alert for old browsers which lack XMLHttpRequest support.
+function OLsorryAJAX() {
+ alert('Sorry, AJAX is not supported by your browser.');
+ return false;
+}
+
+// Check 2nd arg for function
+function OLchkFuncAJAX(ar){
+ var t=typeof ar;return (((t=='function'))||((t=='string')&&(/.+\(.*\)/.test(ar))));
+}
+
+// Alert for bad 2nd argument
+function OLnotFuncAJAX(m) {
+ if(over)cClick();
+ alert('The 2nd arg of OL'+m+'AJAX is not a function reference, nor an inline function, '
+ +'nor a quoted string with a function indicated.');
+ return OLclearAJAX();
+}
+
+// Alert for indicating an XMLHttpRequest network error.
+function OLerrorAJAX() {
+ if(OLhttp.status&&OLhttp.status!=2147746065)alert('Network error '+OLhttp.status+'. Try again later.');
+ return false;
+}
+
+// Returns a new XMLHttpRequest object, or false for older browsers
+// which did not yet support it. Called as OLhttp=OLnewXMLHttp() via
+// the OLgetAJAX(url,command,delay,css) wrapper function.
+//
+function OLnewXMLHttp() {
+ var f=false,req=f;
+ if(window.XMLHttpRequest)eval(new Array('try{',
+ 'req=new XMLHttpRequest();','}catch(e){','req=f;','}').join('\n'));
+ /*@cc_on @if(@_jscript_version>=5)if(!req)
+ eval(new Array('try{','req=new ActiveXObject("Msxml2.XMLHTTP");',
+ '}catch(e){','try{','req=new ActiveXObject("Microsoft.XMLHTTP");',
+ '}catch(e){','req=f;','}}').join('\n')); @end @*/
+ return req;
+}
+
+// Handle the OLhttp.responseText string from the XMLHttpRequest object.
+function OLdoAJAX() {
+ if(OLhttp.readyState==4){
+ if(OLdebugAJAX)alert(
+ 'OLhttp.status = '+OLhttp.status+'\n'
+ +'OLhttp.statusText = '+OLhttp.statusText+'\n'
+ +'OLhttp.getAllResponseHeaders() = \n'
+ +OLhttp.getAllResponseHeaders()+'\n'
+ +'OLhttp.getResponseHeader("Content-Type") = '
+ +OLhttp.getResponseHeader("Content-Type")+'\n');
+ if(OLhttp.status==200||(OLhttp.status==0&&!OLabortAJAX&&!OLie55)){
+ OLresponseAJAX=OLclassAJAX?'<div class="'+OLclassAJAX+'">':'';
+ OLresponseAJAX += OLhttp.responseText;
+ OLresponseAJAX += OLclassAJAX?'</div>':'';
+ if(OLdebugAJAX)alert('OLresponseAJAX = \n'+OLresponseAJAX);
+ OLclassAJAX=0;
+ return (typeof OLcommandAJAX=='string')?eval(OLcommandAJAX):OLcommandAJAX();
+ }else{
+ OLclassAJAX=0;
+ OLabortAJAX=0;
+ return OLerrorAJAX();
+ }
+ }
+}
+
+// Actually make the request initiated via OLgetAJAX or OLpostAJAX, or
+// invoke a "permission denied" alert if a cross-domain URL was used.
+function OLsetAJAX(url,qry) {
+ if(window.location.protocol.indexOf('http')==0&&
+ (url.indexOf('file:')==0||url.indexOf('ftp:')==0)){
+ alert('[object Error]\n(Cross-domain access not permitted)');return false;}
+ qry=(qry||null);var s='',m=(qry)?'POST':'GET';OLabortAJAX=0;
+ OLdelayidAJAX=0;eval(new Array('try{','OLhttp.open(m,url,true);',
+ '}catch(e){','s=e','OLhttp=false;','}').join('\n'));if(!OLhttp){
+ alert(s+'\n(Cross-domain access not permitted)');return false;}if(qry)
+ OLhttp.setRequestHeader('Content-type','application/x-www-form-urlencoded');
+ OLhttp.onreadystatechange=OLdoAJAX;
+ OLhttp.send(qry);
+}
+
+// Clear or abort any delayed OLsetAJAX call or pending request.
+function OLclearAJAX() {
+ if(OLdelayidAJAX){clearTimeout(OLdelayidAJAX);OLdelayidAJAX=0;}
+ if(OLhttp&&!OLdebugAJAX){OLabortAJAX=1;OLhttp.abort();}
+ return false;
+}
+
+// Load a new XMLHttpRequest object into the OLhttp global, load the
+// OLcommandAJAX and OLclassAJAX globals, and initiate a GET request
+// via OLsetAJAX(url) to populate OLhttp.
+function OLgetAJAX(url,command,delay,css) {
+ if(!OLchkFuncAJAX(command))return OLnotFuncAJAX('get');
+ OLclearAJAX();OLhttp=OLnewXMLHttp();if(!OLhttp)return OLsorryAJAX();
+ OLcommandAJAX=command;delay=(delay||0);css=(css||0);OLclassAJAX=css;
+ if(delay)OLdelayidAJAX=setTimeout("OLsetAJAX('"+url+"')",delay);
+ else OLsetAJAX(url);
+}
+
+// Load a new XMLHttpRequest object into the OLhttp global, load the
+// OLcommandAJAX and OLclassAJAX globals, and initiate a POST request
+// via OLsetAJAX(url,qry) to populate OLhttp.
+function OLpostAJAX(url,qry,command,delay,css) {
+ if(!OLchkFuncAJAX(command))return OLnotFuncAJAX('post');
+ OLclearAJAX();OLhttp=OLnewXMLHttp();if(!OLhttp)return OLsorryAJAX();
+ qry=(qry||0);OLcommandAJAX=command;delay=(delay||0);css=(css||0);OLclassAJAX=css;
+ if(delay)OLdelayidAJAX=setTimeout("OLsetAJAX('"+url+"','"+qry+"')",delay);
+ else OLsetAJAX(url,qry);
+}
diff --git a/httemplate/misc/tax-import.cgi b/httemplate/misc/tax-import.cgi
index 2bae6f1..bca623f 100644
--- a/httemplate/misc/tax-import.cgi
+++ b/httemplate/misc/tax-import.cgi
@@ -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',
],
diff --git a/httemplate/misc/xmlhttp-cust_main-address_standardize.html b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
index 8532bb2..72fa4a4 100644
--- a/httemplate/misc/xmlhttp-cust_main-address_standardize.html
+++ b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
@@ -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};
+
}