summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wells <mark@freeside.biz>2012-11-06 12:48:41 -0800
committerMark Wells <mark@freeside.biz>2012-11-06 12:49:11 -0800
commit468c9e660eb0edb2033f0f8dbb4458f20280082c (patch)
tree9a87abfbfad9c820598c3a8d773f35c421c2bb38
parent2b2aa5664742a134da11862a7cedb37d25524423 (diff)
improved address standardization, #13763
-rw-r--r--FS/FS/Conf.pm25
-rw-r--r--FS/FS/GeocodeCache.pm209
-rw-r--r--FS/FS/Mason.pm3
-rw-r--r--FS/FS/Misc/Geo.pm154
-rw-r--r--FS/FS/Schema.pm3
-rw-r--r--FS/FS/cust_location.pm4
-rw-r--r--FS/FS/cust_main.pm16
-rw-r--r--FS/MANIFEST2
-rw-r--r--FS/t/GeocodeCache.t5
-rwxr-xr-xbin/generate-table-module2
-rwxr-xr-xbin/usps-webtools-test-script38
-rwxr-xr-xhttemplate/edit/cust_main.cgi2
-rw-r--r--httemplate/edit/cust_main/bottomfixup.html10
-rw-r--r--httemplate/edit/cust_main/bottomfixup.js139
-rw-r--r--httemplate/edit/cust_main/contact.html4
-rw-r--r--httemplate/elements/location.html27
-rw-r--r--httemplate/elements/order_pkg.js7
-rw-r--r--httemplate/elements/standardize_locations.html2
-rw-r--r--httemplate/elements/standardize_locations.js288
-rw-r--r--httemplate/elements/tr-select-cust_location.html7
-rwxr-xr-xhttemplate/misc/change_pkg.cgi1
-rw-r--r--httemplate/misc/confirm-address_standardize.html123
-rw-r--r--httemplate/misc/confirm-censustract.html78
-rw-r--r--httemplate/misc/order_pkg.html1
-rw-r--r--httemplate/misc/xmlhttp-address_standardize.html38
-rw-r--r--httemplate/misc/xmlhttp-cust_main-address_standardize.html93
26 files changed, 896 insertions, 385 deletions
diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index e74c19faa..02869b16d 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -4065,6 +4065,17 @@ and customer address. Include units.',
},
{
+ 'key' => 'address_standardize_method',
+ 'section' => 'UI', #???
+ 'description' => 'Method for standardizing customer addresses.',
+ 'type' => 'select',
+ 'select_hash' => [ '' => '',
+ 'usps' => 'U.S. Postal Service',
+ 'ezlocate' => 'EZLocate',
+ ],
+ },
+
+ {
'key' => 'usps_webtools-userid',
'section' => 'UI',
'description' => 'Production UserID for USPS web tools. Enables USPS address standardization. See the <a href="http://www.usps.com/webtools/">USPS website</a>, register and agree not to use the tools for batch purposes.',
@@ -4079,6 +4090,20 @@ and customer address. Include units.',
},
{
+ 'key' => 'ezlocate-userid',
+ 'section' => 'UI',
+ 'description' => 'User ID for EZ-Locate service. See <a href="http://www.geocode.com/">the TomTom website</a> for access and pricing information.',
+ 'type' => 'text',
+ },
+
+ {
+ 'key' => 'ezlocate-password',
+ 'section' => 'UI',
+ 'description' => 'Password for EZ-Locate service.',
+ 'type' => 'text'
+ },
+
+ {
'key' => 'cust_main-auto_standardize_address',
'section' => 'UI',
'description' => 'When using USPS web tools, automatically standardize the address without asking.',
diff --git a/FS/FS/GeocodeCache.pm b/FS/FS/GeocodeCache.pm
new file mode 100644
index 000000000..7829c4df2
--- /dev/null
+++ b/FS/FS/GeocodeCache.pm
@@ -0,0 +1,209 @@
+package FS::GeocodeCache;
+
+use strict;
+use vars qw($conf $DEBUG);
+use base qw( FS::geocode_Mixin );
+use FS::Record qw( qsearch qsearchs );
+use FS::Conf;
+use FS::Misc::Geo;
+
+use Data::Dumper;
+
+FS::UID->install_callback( sub { $conf = new FS::Conf; } );
+
+$DEBUG = 0;
+
+=head1 NAME
+
+FS::GeocodeCache - An address undergoing the geocode process.
+
+=head1 SYNOPSIS
+
+ use FS::GeocodeCache;
+
+ $record = FS::GeocodeCache->standardize(%location_hash);
+
+=head1 DESCRIPTION
+
+An FS::GeocodeCache object represents a street address in the process of
+being geocoded. FS::GeocodeCache inherits from FS::geocode_Mixin.
+
+Most methods on this object throw an exception on error.
+
+FS::GeocodeCache has the following fields, with the same meaning as in
+L<FS::cust_location>:
+
+=over 4
+
+=item address1
+
+=item address2
+
+=item city
+
+=item county
+
+=item state
+
+=item zip
+
+=item latitude
+
+=item longitude
+
+=item addr_clean
+
+=item country
+
+=item censustract
+
+=item geocode
+
+=item district
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new cache object. For internal use. See C<standardize>.
+
+=cut
+
+# minimalist constructor
+sub new {
+ my $class = shift;
+ my $self = {
+ company => '',
+ address1 => '',
+ address2 => '',
+ city => '',
+ state => '',
+ zip => '',
+ country => '',
+ latitude => '',
+ longitude => '',
+ addr_clean => '',
+ censustract => '',
+ @_
+ };
+ bless $self, $class;
+}
+
+# minimalist accessor, for compatibility with geocode_Mixin
+sub get {
+ $_[0]->{$_[1]}
+}
+
+sub set {
+ $_[0]->{$_[1]} = $_[2];
+}
+
+sub location_hash { %{$_[0]} };
+
+=item set_censustract
+
+Look up the censustract, if it's not already filled in, and return it.
+On error, sets 'error' and returns nothing.
+
+This uses the "get_censustract_*" methods in L<FS::Misc::Geo>; currently
+the only one is 'ffiec'.
+
+=cut
+
+sub set_censustract {
+ my $self = shift;
+
+ if ( $self->get('censustract') =~ /^\d{9}\.\d{2}$/ ) {
+ return $self->get('censustract');
+ }
+ my $censusyear = $conf->config('census_year');
+ return if !$censusyear;
+
+ my $method = 'ffiec';
+ # configurable censustract-only lookup goes here if it's ever needed.
+ $method = "get_censustract_$method";
+ my $censustract = eval { FS::Misc::Geo->$method($self, $censusyear) };
+ $self->set("censustract_error", $@);
+ $self->set("censustract", $censustract);
+}
+
+=item set_coord
+
+Set the latitude and longitude fields if they're not already set. Returns
+those values, in order.
+
+=cut
+
+sub set_coord { # the one in geocode_Mixin will suffice
+ my $self = shift;
+ if ( !$self->get('latitude') || !$self->get('longitude') ) {
+ $self->SUPER::set_coord;
+ $self->set('coord_error', $@);
+ }
+ return $self->get('latitude'), $self->get('longitude');
+}
+
+=head1 CLASS METHODS
+
+=over 4
+
+=item standardize LOCATION
+
+Given a location hash or L<FS::geocode_Mixin> object, standardize the
+address using the configured method and return an L<FS::GeocodeCache>
+object.
+
+The methods are the "standardize_*" functions in L<FS::Geo::Misc>.
+
+=cut
+
+sub standardize {
+ my $class = shift;
+ my $location = shift;
+ $location = { $location->location_hash }
+ if UNIVERSAL::can($location, 'location_hash');
+
+ local $Data::Dumper::Terse = 1;
+ warn "standardizing location:\n".Dumper($location) if $DEBUG;
+
+ my $method = $conf->config('address_standardize_method');
+
+ if ( $method ) {
+ $method = "standardize_$method";
+ my $new_location = eval { FS::Misc::Geo->$method( $location ) };
+ if ( $new_location ) {
+ $location = {
+ addr_clean => 'Y',
+ %$new_location
+ # standardize_* can return an address with addr_clean => '' if
+ # the address is somehow questionable
+ }
+ }
+ else {
+ # XXX need an option to decide what to do on error
+ $location->{'addr_clean'} = '';
+ $location->{'error'} = $@;
+ }
+ warn "result:\n".Dumper($location) if $DEBUG;
+ }
+ # else $location = $location
+ my $cache = $class->new(%$location);
+ return $cache;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>, schema.html from the base documentation.
+
+=cut
+
+1;
+
diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm
index f7d98a156..944a4836c 100644
--- a/FS/FS/Mason.pm
+++ b/FS/FS/Mason.pm
@@ -64,7 +64,7 @@ if ( -e $addl_handler_use_file ) {
use DateTime;
use DateTime::Format::Strptime;
use FS::Misc::DateTime qw( parse_datetime );
- use FS::Misc::Geo qw( get_censustract get_district );
+ use FS::Misc::Geo qw( get_district );
use Lingua::EN::Inflect qw(PL);
Lingua::EN::Inflect::classical names=>0; #Categorys
use Tie::IxHash;
@@ -326,6 +326,7 @@ if ( -e $addl_handler_use_file ) {
use FS::cust_bill_pkg_discount_void;
use FS::agent_pkg_class;
use FS::svc_export_machine;
+ use FS::GeocodeCache;
# Sammath Naur
if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm
index 5d6f33cb7..6bc71fc84 100644
--- a/FS/FS/Misc/Geo.pm
+++ b/FS/FS/Misc/Geo.pm
@@ -2,7 +2,7 @@ package FS::Misc::Geo;
use strict;
use base qw( Exporter );
-use vars qw( $DEBUG @EXPORT_OK );
+use vars qw( $DEBUG @EXPORT_OK $conf );
use LWP::UserAgent;
use HTTP::Request;
use HTTP::Request::Common qw( GET POST );
@@ -10,15 +10,19 @@ use HTML::TokeParser;
use URI::Escape 3.31;
use Data::Dumper;
+FS::UID->install_callback( sub {
+ $conf = new FS::Conf;
+} );
+
$DEBUG = 0;
-@EXPORT_OK = qw( get_censustract get_district );
+@EXPORT_OK = qw( get_district );
=head1 NAME
FS::Misc::Geo - routines to fetch geographic information
-=head1 FUNCTIONS
+=head1 CLASS METHODS
=over 4
@@ -30,7 +34,8 @@ codes) or an error message.
=cut
-sub get_censustract {
+sub get_censustract_ffiec {
+ my $class = shift;
my $location = shift;
my $year = shift;
@@ -45,7 +50,7 @@ sub get_censustract {
my $res = $ua->request( GET( $url ) );
warn $res->as_string
- if $DEBUG > 1;
+ if $DEBUG > 2;
unless ($res->code eq '200') {
@@ -87,12 +92,12 @@ sub get_censustract {
btnSearch => 'Search',
);
warn join("\n", @ffiec_args )
- if $DEBUG;
+ if $DEBUG > 1;
push @{ $ua->requests_redirectable }, 'POST';
$res = $ua->request( POST( $url, \@ffiec_args ) );
warn $res->as_string
- if $DEBUG > 1;
+ if $DEBUG > 2;
unless ($res->code eq '200') {
@@ -102,7 +107,7 @@ sub get_censustract {
my @id = qw( MSACode StateCode CountyCode TractCode );
$content = $res->content;
- warn $res->content if $DEBUG > 1;
+ warn $res->content if $DEBUG > 2;
$p = new HTML::TokeParser \$content;
my $prefix = 'UcGeoResult11_lb';
my $compare =
@@ -127,7 +132,7 @@ sub get_censustract {
} #unless ($res->code eq '200')
- return "FFIEC Geocoding error: $error" if $error;
+ die "FFIEC Geocoding error: $error\n" if $error;
$return->{'statecode'} . $return->{'countycode'} . $return->{'tractcode'};
}
@@ -201,12 +206,12 @@ sub wa_sales {
my $query_string = join($delim, @args );
$url .= "?$query_string";
- warn "\nrequest: $url\n\n" if $DEBUG;
+ warn "\nrequest: $url\n\n" if $DEBUG > 1;
my $res = $ua->request( GET( "$url?$query_string" ) );
warn $res->as_string
- if $DEBUG > 1;
+ if $DEBUG > 2;
if ($res->code ne '200') {
$error = $res->message;
@@ -253,7 +258,7 @@ sub wa_sales {
# just to make sure
if ( $return->{'district'} =~ /^\d+$/ and $return->{'tax'} =~ /^.\d+$/ ) {
$return->{'tax'} *= 100; #percentage
- warn Dumper($return) if $DEBUG;
+ warn Dumper($return) if $DEBUG > 1;
return $return;
}
else {
@@ -267,6 +272,131 @@ sub wa_sales {
die "WA tax district lookup error: $error";
}
+sub standardize_usps {
+ my $class = shift;
+
+ eval "use Business::US::USPS::WebTools::AddressStandardization";
+ die $@ if $@;
+
+ my $location = shift;
+ if ( $location->{country} ne 'US' ) {
+ # soft failure
+ warn "standardize_usps not for use in country ".$location->{country}."\n";
+ $location->{addr_clean} = '';
+ return $location;
+ }
+ my $userid = $conf->config('usps_webtools-userid');
+ my $password = $conf->config('usps_webtools-password');
+ my $verifier = Business::US::USPS::WebTools::AddressStandardization->new( {
+ UserID => $userid,
+ Password => $password,
+ Testing => 0,
+ } ) or die "error starting USPS WebTools\n";
+
+ my($zip5, $zip4) = split('-',$location->{'zip'});
+
+ my %usps_args = (
+ FirmName => $location->{company},
+ Address2 => $location->{address1},
+ Address1 => $location->{address2},
+ City => $location->{city},
+ State => $location->{state},
+ Zip5 => $zip5,
+ Zip4 => $zip4,
+ );
+ warn join('', map "$_: $usps_args{$_}\n", keys %usps_args )
+ if $DEBUG > 1;
+
+ my $hash = $verifier->verify_address( %usps_args );
+
+ warn $verifier->response
+ if $DEBUG > 1;
+
+ die "USPS WebTools error: ".$verifier->{error}{description} ."\n"
+ if $verifier->is_error;
+
+ my $zip = $hash->{Zip5};
+ $zip .= '-' . $hash->{Zip4} if $hash->{Zip4} =~ /\d/;
+
+ { company => $hash->{FirmName},
+ address1 => $hash->{Address2},
+ address2 => $hash->{Address1},
+ city => $hash->{City},
+ state => $hash->{State},
+ zip => $zip,
+ country => 'US',
+ addr_clean=> 'Y' }
+}
+
+my %ezlocate_error = ( # USA_Geo_002 documentation
+ 10 => 'State not found',
+ 11 => 'City not found',
+ 12 => 'Invalid street address',
+ 14 => 'Street name not found',
+ 15 => 'Address range does not exist',
+ 16 => 'Ambiguous address',
+ 17 => 'Intersection not found', #unused?
+);
+
+sub standardize_ezlocate {
+ my $self = shift;
+ my $location = shift;
+ my $class;
+ #if ( $location->{country} eq 'US' ) {
+ # $class = 'USA_Geo_004Tool';
+ #}
+ #elsif ( $location->{country} eq 'CA' ) {
+ # $class = 'CAN_Geo_001Tool';
+ #}
+ #else { # shouldn't be a fatal error, just pass through unverified address
+ # warn "standardize_teleatlas: address lookup in '".$location->{country}.
+ # "' not available\n";
+ # return $location;
+ #}
+ #my $path = $conf->config('teleatlas-path') || '';
+ #local @INC = (@INC, $path);
+ #eval "use $class;";
+ #if ( $@ ) {
+ # die "Loading $class failed:\n$@".
+ # "\nMake sure the TeleAtlas Perl SDK is installed correctly.\n";
+ #}
+
+ $class = 'Geo::EZLocate'; # use our own library
+ eval "use $class";
+ die $@ if $@;
+
+ my $userid = $conf->config('ezlocate-userid')
+ or die "no ezlocate-userid configured\n";
+ my $password = $conf->config('ezlocate-password')
+ or die "no ezlocate-password configured\n";
+
+ my $tool = $class->new($userid, $password);
+ my $match = $tool->findAddress(
+ $location->{address1},
+ $location->{city},
+ $location->{state},
+ $location->{zip}, #12345-6789 format is allowed
+ );
+ warn "ezlocate returned match:\n".Dumper($match) if $DEBUG > 1;
+ # error handling - B codes indicate success
+ die $ezlocate_error{$match->{MAT_STAT}}."\n"
+ unless $match->{MAT_STAT} =~ /^B\d$/;
+
+ {
+ address1 => $match->{STD_ADDR},
+ address2 => $location->{address2},
+ city => $match->{STD_CITY},
+ state => $match->{STD_ST},
+ country => $location->{country},
+ zip => $match->{STD_ZIP}.'-'.$match->{STD_P4},
+ latitude => $match->{MAT_LAT},
+ longitude => $match->{MAT_LON},
+ censustract => $match->{FIPS_ST}.$match->{FIPS_CTY}.
+ sprintf('%04.2f',$match->{CEN_TRCT}),
+ addr_clean => 'Y',
+ };
+}
+
=back
=cut
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 912f3e269..9eb59a09a 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -1010,6 +1010,7 @@ sub tables_hashref {
'latitude', 'decimal', 'NULL', '10,7', '', '',
'longitude','decimal', 'NULL', '10,7', '', '',
'coord_auto', 'char', 'NULL', 1, '', '',
+ 'addr_clean', 'char', 'NULL', 1, '', '',
'daytime', 'varchar', 'NULL', 20, '', '',
'night', 'varchar', 'NULL', 20, '', '',
'fax', 'varchar', 'NULL', 12, '', '',
@@ -1028,6 +1029,7 @@ sub tables_hashref {
'ship_latitude', 'decimal', 'NULL', '10,7', '', '',
'ship_longitude','decimal', 'NULL', '10,7', '', '',
'ship_coord_auto', 'char', 'NULL', 1, '', '',
+ 'ship_addr_clean', 'char', 'NULL', 1, '', '',
'ship_daytime', 'varchar', 'NULL', 20, '', '',
'ship_night', 'varchar', 'NULL', 20, '', '',
'ship_fax', 'varchar', 'NULL', 12, '', '',
@@ -1252,6 +1254,7 @@ sub tables_hashref {
'latitude', 'decimal', 'NULL', '10,7', '', '',
'longitude', 'decimal', 'NULL', '10,7', '', '',
'coord_auto', 'char', 'NULL', 1, '', '',
+ 'addr_clean', 'char', 'NULL', 1, '', '',
'country', 'char', '', 2, '', '',
'geocode', 'varchar', 'NULL', 20, '', '',
'district', 'varchar', 'NULL', 20, '', '',
diff --git a/FS/FS/cust_location.pm b/FS/FS/cust_location.pm
index 2810dc957..1521960d4 100644
--- a/FS/FS/cust_location.pm
+++ b/FS/FS/cust_location.pm
@@ -188,6 +188,7 @@ sub check {
|| $self->ut_coordn('latitude')
|| $self->ut_coordn('longitude')
|| $self->ut_enum('coord_auto', [ '', 'Y' ])
+ || $self->ut_enum('addr_clean', [ '', 'Y' ])
|| $self->ut_alphan('location_type')
|| $self->ut_textn('location_number')
|| $self->ut_enum('location_kind', [ '', 'R', 'B' ] )
@@ -208,9 +209,6 @@ sub check {
return "Unit # is required";
}
- $self->set_coord
- unless $import || ($self->latitude && $self->longitude);
-
# tricky...we have to allow for the customer to not be inserted yet
return "No prospect or customer!" unless $self->prospectnum
|| $self->custnum
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 9e39b3006..4ea4a6b9d 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -1488,20 +1488,6 @@ sub replace {
return "You are not permitted to create complimentary accounts.";
}
- # should be unnecessary--geocode will default to null on new locations
- #if ( $old->get('geocode') && $old->get('geocode') eq $self->get('geocode')
- # && $conf->exists('enable_taxproducts')
- # )
- #{
- # my $pre = ($conf->exists('tax-ship_address') && $self->ship_zip)
- # ? 'ship_' : '';
- # $self->set('geocode', '')
- # if $old->get($pre.'zip') ne $self->get($pre.'zip')
- # && length($self->get($pre.'zip')) >= 10;
- #}
-
- # set_coord/coord_auto stuff is now handled by cust_location
-
local($ignore_expired_card) = 1
if $old->payby =~ /^(CARD|DCRD)$/
&& $self->payby =~ /^(CARD|DCRD)$/
@@ -1862,8 +1848,6 @@ sub check {
}
- #ship_ fields are gone
-
#$self->payby =~ /^(CARD|DCRD|CHEK|DCHK|LECB|BILL|COMP|PREPAY|CASH|WEST|MCRD)$/
# or return "Illegal payby: ". $self->payby;
#$self->payby($1);
diff --git a/FS/MANIFEST b/FS/MANIFEST
index f530610e7..9c444be58 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -673,3 +673,5 @@ FS/part_export_machine.pm
t/part_export_machine.t
FS/svc_export_machine.pm
t/svc_export_machine.t
+FS/GeocodeCache.pm
+t/GeocodeCache.t
diff --git a/FS/t/GeocodeCache.t b/FS/t/GeocodeCache.t
new file mode 100644
index 000000000..eae6f0d01
--- /dev/null
+++ b/FS/t/GeocodeCache.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::GeocodeCache;
+$loaded=1;
+print "ok 1\n";
diff --git a/bin/generate-table-module b/bin/generate-table-module
index e7fc99258..b536360c5 100755
--- a/bin/generate-table-module
+++ b/bin/generate-table-module
@@ -95,7 +95,7 @@ close TEST;
# add them to MANIFEST
###
-system('cvs edit FS/MANIFEST');
+#system('cvs edit FS/MANIFEST');
open(MANIFEST,">>FS/MANIFEST") or die $!;
print MANIFEST "FS/$table.pm\n",
diff --git a/bin/usps-webtools-test-script b/bin/usps-webtools-test-script
new file mode 100755
index 000000000..414ae4cad
--- /dev/null
+++ b/bin/usps-webtools-test-script
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+
+use FS::Misc::Geo 'standardize';
+use Data::Dumper; $Data::Dumper::Terse = 1;
+my @tests = (
+ {
+ address1 => '6406 Ivy Lane',
+ address2 => '',
+ city => 'Greenbelt',
+ state => 'MD',
+ zip => '',
+ },
+ {
+ address1 => '8 Wildwood Drive',
+ address2 => '',
+ city => 'Old Lyme',
+ state => 'CT',
+ zip => '06371',
+ },
+);
+
+my ($userid, $password) = @ARGV;
+
+my %opt = (
+ userid => $userid,
+ password=> $password,
+ test => 1,
+);
+my $i = 1;
+foreach (@tests) {
+ print "Test $i\n";
+ my $result = eval { standardize($_, %opt) };
+ print "ERROR: $@\n\n" if $@;
+ print Dumper($result);
+ $i++;
+}
+
+1;
diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index 2628b4e01..0aded597f 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -316,6 +316,8 @@ if ( $cgi->param('error') ) {
$payinfo = '';
$cgi->param('tagnum', FS::part_tag->default_tags);
+ $cust_main->coord_auto('Y');
+ $cust_main->ship_coord_auto('Y');
if ( $cgi->param('qualnum') =~ /^(\d+)$/ ) {
my $qualnum = $1;
diff --git a/httemplate/edit/cust_main/bottomfixup.html b/httemplate/edit/cust_main/bottomfixup.html
index 60edcc111..b5d10c467 100644
--- a/httemplate/edit/cust_main/bottomfixup.html
+++ b/httemplate/edit/cust_main/bottomfixup.html
@@ -1,15 +1,9 @@
<& /elements/init_overlib.html &>
<& /elements/xmlhttp.html,
- url => $p.'misc/xmlhttp-cust_main-address_standardize.html',
+ url => $p.'misc/xmlhttp-address_standardize.html',
subs => [ 'address_standardize' ],
- #'method' => 'POST', #could get too long?
-&>
-
-<& /elements/xmlhttp.html,
- url => $p.'misc/xmlhttp-cust_main-censustract.html',
- subs => [ 'censustract' ],
- #'method' => 'POST', #could get too long?
+ method => 'POST', #could get too long?
&>
<INPUT TYPE="hidden" NAME="duplicate_of_custnum" VALUE="">
diff --git a/httemplate/edit/cust_main/bottomfixup.js b/httemplate/edit/cust_main/bottomfixup.js
index 77d4294a6..4f3b7da42 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -7,8 +7,7 @@ my $company_longitude = $conf->config('company_longitude');
my @fixups = ('copy_payby_fields', 'standardize_locations');
-push @fixups, 'fetch_censustract'
- if $conf->exists('cust_main-require_censustract');
+push @fixups, 'confirm_censustract';
push @fixups, 'check_unique'
if $conf->exists('cust_main-check_unique') and !$opt{'custnum'};
@@ -18,15 +17,19 @@ push @fixups, 'do_submit'; # always last
var fixups = <% encode_json(\@fixups) %>;
var fixup_position;
+var running = false;
%# state machine to deal with all the asynchronous stuff we're doing
%# call this after each fixup on success:
function submit_continue() {
- window[ fixups[fixup_position++] ].call();
+ if ( running ) {
+ window[ fixups[fixup_position++] ].call();
+ }
}
%# or on failure:
function submit_abort() {
+ running = false;
fixup_position = 0;
document.CustomerForm.submitButton.disabled = false;
cClick();
@@ -35,6 +38,7 @@ function submit_abort() {
function bottomfixup(what) {
fixup_position = 0;
document.CustomerForm.submitButton.disabled = true;
+ running = true;
submit_continue();
}
@@ -63,8 +67,6 @@ function copy_payby_fields() {
submit_continue();
}
-%# call submit_continue() on completion...
-%# otherwise not touching standardize_locations for now
<% include( '/elements/standardize_locations.js',
'callback' => 'submit_continue();',
'main_prefix' => 'bill_',
@@ -72,104 +74,6 @@ function copy_payby_fields() {
)
%>
-var prefix;
-function fetch_censustract() {
-
- //alert('fetch census tract data');
- prefix = document.getElementById('same').checked ? 'bill_' : 'ship_';
- var cf = document.CustomerForm;
- var state_el = cf.elements[prefix + 'state'];
- var census_data = new Array(
- 'year', <% $conf->config('census_year') || '2012' %>,
- 'address1', cf.elements[prefix + 'address1'].value,
- 'city', cf.elements[prefix + 'city'].value,
- 'state', state_el.options[ state_el.selectedIndex ].value,
- 'zip', cf.elements[prefix + 'zip'].value
- );
-
- censustract( census_data, update_censustract );
-
-}
-
-var set_censustract;
-
-function update_censustract(arg) {
-
- var argsHash = eval('(' + arg + ')');
-
- var cf = document.CustomerForm;
-
-/* var msacode = argsHash['msacode'];
- var statecode = argsHash['statecode'];
- var countycode = argsHash['countycode'];
- var tractcode = argsHash['tractcode'];
-
- var newcensus =
- new String(statecode) +
- new String(countycode) +
- new String(tractcode).replace(/\s$/, ''); // JSON 1 workaround */
- var error = argsHash['error'];
- var newcensus = argsHash['censustract'];
-
- set_censustract = function () {
-
- cf.elements[prefix + 'censustract'].value = newcensus;
- submit_continue();
-
- }
-
- if (error || cf.elements[prefix + 'censustract'].value != newcensus) {
- // popup an entry dialog
-
- if (error) { newcensus = error; }
- newcensus.replace(/.*ndefined.*/, 'Not found');
-
- var latitude = cf.elements[prefix + 'latitude'].value
- || '<% $company_latitude %>';
- var longitude= cf.elements[prefix + 'longitude'].value
- || '<% $company_longitude %>';
-
- var choose_censustract =
- '<CENTER><BR><B>Confirm censustract</B><BR>' +
- '<A href="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?' +
- 'census_year=<% $conf->config('census_year') || '2012' %>' +
- '&latitude=' + latitude +
- '&longitude=' + longitude +
- '" target="_blank">Map service module location</A><BR>' +
- '<A href="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?' +
- 'census_year=<% $conf->config('census_year') || '2012' %>' +
- '&zip_code=' + cf.elements[prefix + 'zip'].value +
- '" target="_blank">Map zip code center</A><BR><BR>' +
- '<TABLE>';
-
- choose_censustract = choose_censustract +
- '<TR><TH style="width:50%">Entered census tract</TH>' +
- '<TH style="width:50%">Calculated census tract</TH></TR>' +
- '<TR><TD>' + cf.elements[prefix + 'censustract'].value +
- '</TD><TD>' + newcensus + '</TD></TR>' +
- '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- choose_censustract = choose_censustract +
- '<TR><TD ALIGN="center">' +
- '<BUTTON TYPE="button" onClick="submit_continue();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered census tract </BUTTON>' +
- '</TD><TD ALIGN="center">' +
- '<BUTTON TYPE="button" onClick="set_censustract();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use calculated census tract </BUTTON>' +
- '</TD></TR>' +
- '<TR><TD COLSPAN=2 ALIGN="center">' +
- '<BUTTON TYPE="button" onClick="submit_abort();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
-
- '</TABLE></CENTER>';
-
- overlib( choose_censustract, CAPTION, 'Confirm censustract', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
-
- } else {
-
- submit_continue();
-
- }
-
-}
-
function copyelement(from, to) {
if ( from == undefined ) {
to.value = '';
@@ -192,6 +96,35 @@ function copyelement(from, to) {
//alert(from + " (" + from.type + "): " + to.name + " => " + to.value);
}
+% # the value in 'censustract' is the confirmed censustract; if it's set,
+% # do nothing here
+function confirm_censustract() {
+ var cf = document.CustomerForm;
+ if ( cf.elements['censustract'].value == '' ) {
+ var address_info = form_address_info();
+ address_info['ship_latitude'] = cf.elements['ship_latitude'].value;
+ address_info['ship_longitude'] = cf.elements['ship_longitude'].value;
+ OLpostAJAX(
+ '<%$p%>/misc/confirm-censustract.html',
+ 'q=' + encodeURIComponent(JSON.stringify(address_info)),
+ function() {
+ overlib( OLresponseAJAX, CAPTION, 'Confirm censustract', STICKY,
+ AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
+ 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ TEXTSIZE, 3 );
+ },
+ 0);
+ } else submit_continue();
+}
+
+%# called from confirm-censustract.html
+function set_censustract(tract, year) {
+ var cf = document.CustomerForm;
+ cf.elements['censustract'].value = tract;
+ cf.elements['censusyear'].value = year;
+ submit_continue();
+}
+
function check_unique() {
var search_hash = new Object;
% foreach ($conf->config('cust_main-check_unique')) {
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
index 57490b962..4140ec1ea 100644
--- a/httemplate/edit/cust_main/contact.html
+++ b/httemplate/edit/cust_main/contact.html
@@ -174,9 +174,7 @@ $cust_main->set('stateid_state', $cust_main->state )
$opt{geocode} ||= $cust_main->get('geocode');
-if ( $conf->exists('cust_main-require_censustract') ) {
- $opt{censustract} ||= $cust_main->censustract;
-}
+$opt{censustract} ||= $cust_main->censustract;
$daytime_label = FS::Msgcat::_gettext('daytime') =~ /^(daytime)?$/
? 'Day'
diff --git a/httemplate/elements/location.html b/httemplate/elements/location.html
index 5c7c888de..de844e465 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -33,7 +33,7 @@ Example:
'options' => \@location_kind_options,
'labels' => $location_kind_labels,
'curr_value' => scalar($cgi->param('location_kind'))
- || $object->get($pre.'location_kind'),
+ || $object->get('location_kind'),
)
%>
</TD>
@@ -164,7 +164,7 @@ Example:
NAME = "<%$pre%>zip"
ID = "<%$pre%>zip"
VALUE = "<% $object->get('zip') |h %>"
- SIZE = 10
+ SIZE = 11
onChange = "<% $onchange %>"
<% $disabled %>
<% $style %>
@@ -206,23 +206,22 @@ Example:
<INPUT TYPE="hidden" NAME="<%$pre%>coord_auto" VALUE="<% $object->coord_auto %>">
<INPUT TYPE="hidden" NAME="<%$pre%>geocode" VALUE="<% $object->geocode %>">
+<INPUT TYPE="hidden" NAME="<%$pre%>censustract" VALUE="<% $object->censustract %>">
<INPUT TYPE="hidden" NAME="<%$pre%>censusyear" VALUE="<% $object->censusyear %>">
-<TR>
% if ( $opt{enable_censustract} ) {
+<TR>
<TD ALIGN="right">Census&nbsp;tract</TD>
<TD COLSPAN=8>
<INPUT TYPE="text" SIZE=15
- NAME="<%$pre%>censustract"
+ NAME="enter_censustract"
VALUE="<% $object->censustract %>">
<% '(automatic)' %>
</TD>
-% } else {
- <INPUT TYPE="hidden" NAME="<%$pre%>censustract" VALUE="<% $object->censustract %>">
-% }
</TR>
+% }
% if ( $conf->config('tax_district_method') ) {
- <TR>
% if ( $opt{enable_district} ) {
+ <TR>
<TD ALIGN="right">Tax&nbsp;district</TD>
<TD COLSPAN=8>
<INPUT TYPE="text" SIZE=15
@@ -230,12 +229,22 @@ Example:
VALUE="<% $object->district %>">
<% '(automatic)' %>
</TD>
+ </TR>
% } else {
<INPUT TYPE="hidden" NAME="<%$pre%>district" VALUE="<% $object->district %>">
% }
- </TR>
% }
+%# For address standardization:
+%# keep a clean copy of the address so we know if we need
+%# to re-standardize
+% foreach (qw(address1 city state country zip latitude
+% longitude censustract addr_clean) ) {
+<INPUT TYPE="hidden" NAME="old_<%$pre.$_%>" ID="old_<%$pre.$_%>" VALUE="<% $object->get($_) |h%>">
+% }
+%# Placeholders
+<INPUT TYPE="hidden" NAME="<%$pre%>cachenum" VALUE="">
+<INPUT TYPE="hidden" NAME="<%$pre%>addr_clean" VALUE="">
<%init>
my %opt = @_;
diff --git a/httemplate/elements/order_pkg.js b/httemplate/elements/order_pkg.js
index 48073593a..8c1efd93a 100644
--- a/httemplate/elements/order_pkg.js
+++ b/httemplate/elements/order_pkg.js
@@ -19,13 +19,10 @@ function pkg_changed () {
form.start_date_text.disabled = false;
form.start_date.style.backgroundColor = '#ffffff';
form.start_date_button.style.display = '';
- form.start_date_button_disabled.style.display = 'none';
- form.invoice_terms.disabled = true;
} else {
form.start_date_text.disabled = true;
form.start_date.style.backgroundColor = '#dddddd';
form.start_date_button.style.display = 'none';
- form.start_date_button_disabled.style.display = '';
}
} else {
@@ -44,3 +41,7 @@ function standardize_new_location() {
form.submit();
}
}
+
+function submit_abort() {
+ document.OrderPkgForm.submitButton.disabled = false;
+}
diff --git a/httemplate/elements/standardize_locations.html b/httemplate/elements/standardize_locations.html
index 9f8b71c62..5a4ee0f80 100644
--- a/httemplate/elements/standardize_locations.html
+++ b/httemplate/elements/standardize_locations.html
@@ -1,7 +1,7 @@
<% include('/elements/init_overlib.html') %>
<% include( '/elements/xmlhttp.html',
- 'url' => $p.'misc/xmlhttp-cust_main-address_standardize.html',
+ 'url' => $p.'misc/xmlhttp-address_standardize.html',
'subs' => [ 'address_standardize' ],
#'method' => 'POST', #could get too long?
)
diff --git a/httemplate/elements/standardize_locations.js b/httemplate/elements/standardize_locations.js
index 86f8d2be8..f6564a55e 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -1,179 +1,205 @@
-function standardize_locations() {
-
+function form_address_info() {
var cf = document.<% $formname %>;
-
var state_el = cf.elements['<% $main_prefix %>state'];
var ship_state_el = cf.elements['<% $ship_prefix %>state'];
-
- var address_info = new Array(
+ return {
% if ( $onlyship ) {
- 'onlyship', 1,
+ 'onlyship': 1,
% } else {
% if ( $withfirm ) {
'company', cf.elements['company'].value,
% }
- 'address1', cf.elements['<% $main_prefix %>address1'].value,
- 'address2', cf.elements['<% $main_prefix %>address2'].value,
- 'city', cf.elements['<% $main_prefix %>city'].value,
- 'state', state_el.options[ state_el.selectedIndex ].value,
- 'zip', cf.elements['<% $main_prefix %>zip'].value,
+ 'address1': cf.elements['<% $main_prefix %>address1'].value,
+ 'address2': cf.elements['<% $main_prefix %>address2'].value,
+ 'city': cf.elements['<% $main_prefix %>city'].value,
+ 'state': state_el.options[ state_el.selectedIndex ].value,
+ 'zip': cf.elements['<% $main_prefix %>zip'].value,
+ 'country': cf.elements['<% $main_prefix %>country'].value,
% }
- 'ship_address1', cf.elements['<% $ship_prefix %>address1'].value,
- 'ship_address2', cf.elements['<% $ship_prefix %>address2'].value,
- 'ship_city', cf.elements['<% $ship_prefix %>city'].value,
- 'ship_state', ship_state_el.options[ ship_state_el.selectedIndex ].value,
- 'ship_zip', cf.elements['<% $ship_prefix %>zip'].value
- );
-
- address_standardize( address_info, update_address );
-
+% if ( $withcensus ) {
+ 'ship_censustract': cf.elements['enter_censustract'].value,
+% }
+ 'ship_address1': cf.elements['<% $ship_prefix %>address1'].value,
+ 'ship_address2': cf.elements['<% $ship_prefix %>address2'].value,
+ 'ship_city': cf.elements['<% $ship_prefix %>city'].value,
+ 'ship_state': ship_state_el.options[ ship_state_el.selectedIndex ].value,
+ 'ship_zip': cf.elements['<% $ship_prefix %>zip'].value,
+ 'ship_country': cf.elements['<% $ship_prefix %>country'].value,
+ };
}
-var standardize_address;
-
-function update_address(arg) {
+function standardize_locations() {
- var argsHash = eval('(' + arg + ')');
+ var startup_msg = '<P STYLE="position:absolute; top:50%; margin-top:-1em; width:100%; text-align:center"><B><FONT SIZE="+1">Verifying address...</FONT></B></P>';
+ overlib(startup_msg, WIDTH, 444, HEIGHT, 168, CAPTION, 'Please wait...', STICKY, AUTOSTATUSCAP, CLOSECLICK, MIDX, 0, MIDY, 0);
+ var cf = document.<% $formname %>;
+ var address_info = form_address_info();
- var changed = argsHash['address_standardized'];
- var ship_changed = argsHash['ship_address_standardized'];
- var error = argsHash['error'];
- var ship_error = argsHash['ship_error'];
-
+ var changed = false; // have any of the address fields been changed?
- //yay closures
- standardize_address = function () {
+// clear coord_auto fields if the user has changed the coordinates
+% for my $pre ($ship_prefix, $onlyship ? () : $main_prefix) {
+% for my $field ($pre.'latitude', $pre.'longitude') {
- var cf = document.<% $formname %>;
- var state_el = cf.elements['<% $main_prefix %>state'];
- var ship_state_el = cf.elements['<% $ship_prefix %>state'];
+ if ( cf.elements['<% $field %>'].value != cf.elements['old_<% $field %>'].value ) {
+ cf.elements['<% $pre %>coord_auto'].value = '';
+ }
-% if ( !$onlyship ) {
- if ( changed ) {
-% if ( $withfirm ) {
- cf.elements['<% $main_prefix %>company'].value = argsHash['new_company'];
% }
- cf.elements['<% $main_prefix %>address1'].value = argsHash['new_address1'];
- cf.elements['<% $main_prefix %>address2'].value = argsHash['new_address2'];
- cf.elements['<% $main_prefix %>city'].value = argsHash['new_city'];
- setselect(cf.elements['<% $main_prefix %>state'], argsHash['new_state']);
- cf.elements['<% $main_prefix %>zip'].value = argsHash['new_zip'];
- }
-% }
+ // but if the coordinates have been set to null, turn coord_auto on
+ // and standardize
+ if ( cf.elements['<% $pre %>latitude'].value == '' &&
+ cf.elements['<% $pre %>longitude'].value == '' ) {
+ cf.elements['<% $pre %>coord_auto'].value = 'Y';
+ changed = true;
+ }
- if ( ship_changed ) {
-% if ( $withfirm ) {
- cf.elements['<% $ship_prefix %>company'].value = argsHash['new_ship_company'];
% }
- cf.elements['<% $ship_prefix %>address1'].value = argsHash['new_ship_address1'];
- cf.elements['<% $ship_prefix %>address2'].value = argsHash['new_ship_address2'];
- cf.elements['<% $ship_prefix %>city'].value = argsHash['new_ship_city'];
- setselect(cf.elements['<% $ship_prefix %>state'], argsHash['new_ship_state']);
- cf.elements['<% $ship_prefix %>zip'].value = argsHash['new_ship_zip'];
- }
- post_standardization();
+ // standardize if the old address wasn't clean
+ if ( cf.elements['old_<% $ship_prefix %>addr_clean'].value == '' ||
+ ( <% !$onlyship || 0 %> &&
+ cf.elements['old_<% $main_prefix %>addr_clean'].value == '' ) ) {
+ changed = true;
+
+ }
+ // or if it was clean but has been changed
+ for (var key in address_info) {
+ var old_el = cf.elements['old_'+key];
+ if ( old_el && address_info[key] != old_el.value ) {
+ changed = true;
+ break;
+ }
}
+% # If address hasn't been changed, auto-confirm the existing value of
+% # censustract so that we don't ask the user to confirm it again.
+ if ( !changed ) {
+ cf.elements['<% $main_prefix %>censustract'].value =
+ address_info['ship_censustract'];
+ }
- if ( changed || ship_changed ) {
+% if ( $conf->config('address_standardize_method') ) {
+ if ( changed ) {
+ address_standardize(JSON.stringify(address_info), confirm_standardize);
+ }
+ else {
+ cf.elements['ship_addr_clean'].value = 'Y';
+% if ( !$onlyship ) {
+ cf.elements['addr_clean'].value = 'Y';
+% }
+ post_standardization();
+ }
-% if ( $conf->exists('cust_main-auto_standardize_address') ) {
+% } else {
- standardize_address();
+ post_standardization();
-% } else {
+% } # if address_standardize_method
+}
- // popup a confirmation popup
+var returned;
- var confirm_change =
- '<CENTER><BR><B>Confirm address standardization</B><BR><BR>' +
- '<TABLE>';
-
- if ( changed ) {
+function confirm_standardize(arg) {
+ // contains 'old', which was what we sent, and 'new', which is what came
+ // back, including any errors
+ returned = JSON.parse(arg);
- confirm_change = confirm_change +
- '<TR><TH>Entered billing address</TH>' +
- '<TH>Standardized billing address</TH></TR>';
- // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- if ( argsHash['company'] || argsHash['new_company'] ) {
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['company'] +
- '</TD><TD>' + argsHash['new_company'] + '</TD></TR>';
- }
-
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['address1'] +
- '</TD><TD>' + argsHash['new_address1'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['address2'] +
- '</TD><TD>' + argsHash['new_address2'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['city'] + ', ' + argsHash['state'] + ' ' + argsHash['zip'] +
- '</TD><TD>' + argsHash['new_city'] + ', ' + argsHash['new_state'] + ' ' + argsHash['new_zip'] + '</TD></TR>' +
- '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+ if ( <% $conf->exists('cust_main-auto_standardize_address') || 0 %> ) {
- }
+ replace_address(); // with the contents of returned['new']
+
+ }
+ else {
+
+ var querystring = encodeURIComponent( JSON.stringify(returned) );
+ // confirmation popup: knows to call replace_address(),
+ // post_standardization(), or submit_abort() depending on the
+ // user's choice.
+ OLpostAJAX(
+ '<%$p%>/misc/confirm-address_standardize.html',
+ 'q='+querystring,
+ function() {
+ overlib( OLresponseAJAX, CAPTION, 'Address standardization', STICKY,
+ AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH,
+ 576, HEIGHT, 268, BGCOLOR, '#333399', CGCOLOR, '#333399',
+ TEXTSIZE, 3 );
+ }, 0);
- if ( ship_changed ) {
-
- confirm_change = confirm_change +
- '<TR><TH>Entered service address</TH>' +
- '<TH>Standardized service address</TH></TR>';
- // + '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
-
- if ( argsHash['ship_company'] || argsHash['new_ship_company'] ) {
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['ship_company'] +
- '</TD><TD>' + argsHash['new_ship_company'] + '</TD></TR>';
- }
-
- confirm_change = confirm_change +
- '<TR><TD>' + argsHash['ship_address1'] +
- '</TD><TD>' + argsHash['new_ship_address1'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['ship_address2'] +
- '</TD><TD>' + argsHash['new_ship_address2'] + '</TD></TR>' +
- '<TR><TD>' + argsHash['ship_city'] + ', ' + argsHash['ship_state'] + ' ' + argsHash['ship_zip'] +
- '</TD><TD>' + argsHash['new_ship_city'] + ', ' + argsHash['new_ship_state'] + ' ' + argsHash['new_ship_zip'] + '</TD></TR>' +
- '<TR><TD>&nbsp;</TD><TD>&nbsp;</TD></TR>';
+ }
+}
- }
+function replace_address() {
- var addresses = 'address';
- var height = 268;
- if ( changed && ship_changed ) {
- addresses = 'addresses';
- height = 396; // #what
- }
+ var newaddr = returned['new'];
- confirm_change = confirm_change +
- '<TR><TD>' +
- '<BUTTON TYPE="button" onClick="post_standardization();"><IMG SRC="<%$p%>images/error.png" ALT=""> Use entered ' + addresses + '</BUTTON>' +
- '</TD><TD>' +
- '<BUTTON TYPE="button" onClick="standardize_address();"><IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized ' + addresses + '</BUTTON>' +
- '</TD></TR>' +
- '<TR><TD COLSPAN=2 ALIGN="center">' +
- '<BUTTON TYPE="button" onClick="document.<% $formname %>.submitButton.disabled=false; parent.cClick();"><IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission</BUTTON></TD></TR>' +
-
- '</TABLE></CENTER>';
+ var clean = newaddr['addr_clean'] == 'Y';
+ var ship_clean = newaddr['ship_addr_clean'] == 'Y';
+ var error = newaddr['error'];
+ var ship_error = newaddr['ship_error'];
- overlib( confirm_change, CAPTION, 'Confirm address standardization', STICKY, AUTOSTATUSCAP, CLOSETEXT, '', MIDX, 0, MIDY, 0, DRAGGABLE, WIDTH, 576, HEIGHT, height, BGCOLOR, '#333399', CGCOLOR, '#333399', TEXTSIZE, 3 );
+ var cf = document.<% $formname %>;
+ var state_el = cf.elements['<% $main_prefix %>state'];
+ var ship_state_el = cf.elements['<% $ship_prefix %>state'];
+% if ( !$onlyship ) {
+ if ( clean ) {
+% if ( $withfirm ) {
+ cf.elements['<% $main_prefix %>company'].value = newaddr['company'];
% }
+ cf.elements['<% $main_prefix %>address1'].value = newaddr['address1'];
+ cf.elements['<% $main_prefix %>address2'].value = newaddr['address2'];
+ cf.elements['<% $main_prefix %>city'].value = newaddr['city'];
+ setselect(cf.elements['<% $main_prefix %>state'], newaddr['state']);
+ cf.elements['<% $main_prefix %>zip'].value = newaddr['zip'];
+ cf.elements['<% $main_prefix %>addr_clean'].value = 'Y';
+
+ if ( cf.elements['<% $main_prefix %>coord_auto'].value ) {
+ cf.elements['<% $main_prefix %>latitude'].value = newaddr['latitude'];
+ cf.elements['<% $main_prefix %>longitude'].value = newaddr['longitude'];
+ }
+ }
+% }
- } else {
-
- post_standardization();
-
+ if ( ship_clean ) {
+% if ( $withfirm ) {
+ cf.elements['<% $ship_prefix %>company'].value = newaddr['ship_company'];
+% }
+ cf.elements['<% $ship_prefix %>address1'].value = newaddr['ship_address1'];
+ cf.elements['<% $ship_prefix %>address2'].value = newaddr['ship_address2'];
+ cf.elements['<% $ship_prefix %>city'].value = newaddr['ship_city'];
+ setselect(cf.elements['<% $ship_prefix %>state'], newaddr['ship_state']);
+ cf.elements['<% $ship_prefix %>zip'].value = newaddr['ship_zip'];
+ cf.elements['<% $ship_prefix %>addr_clean'].value = 'Y';
+ if ( cf.elements['<% $ship_prefix %>coord_auto'].value ) {
+ cf.elements['<% $ship_prefix %>latitude'].value = newaddr['latitude'];
+ cf.elements['<% $ship_prefix %>longitude'].value = newaddr['longitude'];
+ }
+ }
+% if ( $withcensus ) {
+% # then set the censustract if address_standardize provided one.
+ if ( ship_clean && newaddr['ship_censustract'] ) {
+ cf.elements['<% $main_prefix %>censustract'].value = newaddr['ship_censustract'];
}
+% }
+ post_standardization();
}
-function post_standardization() {
-
+function confirm_manual_address() {
+%# not much to do in this case, just confirm the censustract
+% if ( $withcensus ) {
var cf = document.<% $formname %>;
+ cf.elements['<% $main_prefix %>censustract'].value =
+ cf.elements['<% $main_prefix %>enter_censustract'].value;
+% }
+ post_standardization();
+}
+
+function post_standardization() {
% if ( $conf->exists('enable_taxproducts') ) {
@@ -262,6 +288,7 @@ my %opt = @_;
my $conf = new FS::Conf;
my $withfirm = 1;
+my $withcensus = 1;
my $formname = $opt{form} || 'CustomerForm';
my $onlyship = $opt{onlyship} || '';
@@ -271,5 +298,6 @@ my $taxpre = $main_prefix;
$taxpre = $ship_prefix if ( $conf->exists('tax-ship_address') || $onlyship );
my $post_geocode = $opt{callback} || 'post_geocode();';
$withfirm = 0 if $opt{no_company};
+$withcensus = 0 if $opt{no_census};
</%init>
diff --git a/httemplate/elements/tr-select-cust_location.html b/httemplate/elements/tr-select-cust_location.html
index b804f4548..7ffbd6c14 100644
--- a/httemplate/elements/tr-select-cust_location.html
+++ b/httemplate/elements/tr-select-cust_location.html
@@ -52,10 +52,11 @@ Example:
if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff';
% }
% if ( $opt{'alt_format'} ) {
- if ( what.form.location_type.options[what.form.location_type.selectedIndex].value ) {
+ if ( what.form.location_type &&
+ what.form.location_type.options[what.form.location_type.selectedIndex].value ) {
what.form.location_number.disabled = false;
what.form.location_number.style.backgroundColor = '#ffffff';
- }
+ }
% }
}
@@ -281,6 +282,8 @@ if ( $locationnum && $locationnum > 0 ) {
}
}
+$cust_location->coord_auto('Y');
+
my $location_sort = sub {
$a->country cmp $b->country
or lc($a->city) cmp lc($b->city)
diff --git a/httemplate/misc/change_pkg.cgi b/httemplate/misc/change_pkg.cgi
index 2ab9329a1..7b08f7b10 100755
--- a/httemplate/misc/change_pkg.cgi
+++ b/httemplate/misc/change_pkg.cgi
@@ -34,6 +34,7 @@
'form' => "OrderPkgForm",
'onlyship' => 1,
'no_company' => 1,
+ 'no_census' => 1,
'callback' => 'document.OrderPkgForm.submit();',
&>
diff --git a/httemplate/misc/confirm-address_standardize.html b/httemplate/misc/confirm-address_standardize.html
new file mode 100644
index 000000000..3603b95d9
--- /dev/null
+++ b/httemplate/misc/confirm-address_standardize.html
@@ -0,0 +1,123 @@
+<STYLE type="text/css">
+th { line-height: 150% }
+</STYLE>
+<CENTER><BR><B>
+% if ( $new{error} or $new{ship_error} ) {
+Address standardization error
+% }
+% else {
+Confirm address standardization
+% }
+
+</B><BR><BR>
+<TABLE WIDTH="100%">
+% for my $pre ('', 'ship_') {
+% next if !$pre and $old{onlyship};
+% my $name = $pre eq 'ship_' ? 'service' : 'billing';
+% if ( $new{$pre.'addr_clean'} ) {
+ <TR>
+ <TH>Entered <%$name%> address</TH>
+ <TH>Standardized <%$name%> address</TH>
+ </TR>
+ <TR>
+% if ( $old{$pre.'company'} ) {
+ <TR>
+ <TD><% $old{$pre.'company'} %></TD>
+ <TD><% $new{$pre.'company'} %></TD>
+ </TR>
+% }
+ <TR>
+ <TD><% $old{$pre.'address1'} %></TD>
+ <TD><% $new{$pre.'address1'} %></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'address2'} %></TD>
+ <TD><% $new{$pre.'address2'} %></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
+ <TD><% $new{$pre.'city'} %>, <% $new{$pre.'state'} %> <% $new{$pre.'zip'} %></TD>
+ </TR>
+
+% } # if addr_clean
+% elsif ( $new{$pre.'error'} ) {
+ <TR>
+ <TH>Entered <%$name%> address</TH>
+ </TR>
+% if ( $old{$pre.'company'} ) {
+ <TR>
+ <TD><% $old{$pre.'company'} %></TD>
+ </TR>
+% }
+ <TR>
+ <TD><% $old{$pre.'address1'} %></TD>
+ <TD ROWSPAN=3><FONT COLOR="#ff0000"><B><% $new{$pre.'error'} %></B></FONT></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'address2'} %></TD>
+ </TR>
+ <TR>
+ <TD><% $old{$pre.'city'} %>, <% $old{$pre.'state'} %> <% $old{$pre.'zip'} %></TD>
+ </TR>
+% } #if error
+% } # for $pre
+
+%# only do this part if address standardization provided a censustract
+% if ( $new{'ship_censustract'} ) {
+ <TR>
+ <TH>Entered census tract</TH>
+ <TH>Calculated census tract</TH>
+ </TR>
+ <TR>
+ <TD><% $old{'ship_censustract'} %></TD>
+ <TD>
+% if ( $new{'ship_census_error'} ) {
+ <FONT COLOR="#ff0000"><% $new{'ship_census_error'} %></FONT>
+% } else {
+ <% $new{'ship_censustract'} %>
+% }
+ </TD>
+ </TR>
+% } #if censustract
+
+% if ( $new{error} or $new{ship_error} ) {
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address();">
+ <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%>
+ </BUTTON></TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+ </BUTTON></TD>
+ </TR>
+% }
+% else {
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="confirm_manual_address()();">
+ <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered <%$addresses%>
+ </BUTTON></TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="replace_address();">
+ <IMG SRC="<%$p%>images/tick.png" ALT=""> Use standardized <%$addresses%>
+ </BUTTON></TD>
+ </TR>
+ <TR ALIGN="center"><TD COLSPAN=2>
+ <BUTTON TYPE="button" STYLE="width:205px" onclick="submit_abort();">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+ </BUTTON>
+ </TD></TR>
+</TABLE>
+% } # !error
+<%init>
+
+# slightly weird interface...
+my $q = decode_json($cgi->param('q'));
+#warn Dumper($q);
+my %old = %{ $q->{old} };
+my %new = %{ $q->{new} };
+
+my $addresses = $old{onlyship} ? 'address' : 'addresses';
+
+</%init>
diff --git a/httemplate/misc/confirm-censustract.html b/httemplate/misc/confirm-censustract.html
new file mode 100644
index 000000000..ae0ae3a6a
--- /dev/null
+++ b/httemplate/misc/confirm-censustract.html
@@ -0,0 +1,78 @@
+<CENTER><BR><B>
+% if ( $error ) {
+Census tract error
+% }
+% else {
+Confirm census tract
+% }
+</B><BR>
+% my $querystring = "census_year=$year&latitude=".$cache->get('latitude').'&longitude='.$cache->get('longitude');
+<A HREF="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?<% $querystring %>"
+ TARGET="_blank">Map service module location</A><BR>
+% $querystring = "census_year=$year&zip_code=".$cache->get('zip');
+<A HREF="http://maps.ffiec.gov/FFIECMapper/TGMapSrv.aspx?<% $querystring %>"
+ TARGET="_blank">Map zip code center</A><BR>
+<BR>
+<TABLE>
+ <TR>
+ <TH style="width:50%">Entered census tract</TH>
+ <TH style="width:50%">Calculated census tract</TH>
+ </TR>
+ <TR>
+ <TD><% $old_tract %></TD>
+% if ( $error ) {
+ <TD><FONT COLOR="#ff0000"><% $error %></FONT></TD>
+% } else {
+ <TD><% $new_tract %></TD>
+% }
+ </TR>
+ <TR>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button"
+ onclick="set_censustract('<% $old_tract %>', '<% $year %>')">
+ <IMG SRC="<%$p%>images/error.png" ALT=""> Use entered census tract
+ </BUTTON>
+ </TD>
+ <TD ALIGN="center">
+ <BUTTON TYPE="button"
+ onclick="set_censustract('<% $new_tract %>', '<% $year %>')">
+ <IMG SRC="<%$p%>images/tick.png" ALT=""> Use calculated census tract
+ </BUTTON>
+ </TD>
+ </TR>
+ <TR>
+ <TD COLSPAN=2 ALIGN="center">
+ <BUTTON TYPE="button" onclick="submit_abort()">
+ <IMG SRC="<%$p%>images/cross.png" ALT=""> Cancel submission
+ </BUTTON>
+ </TD>
+ </TR>
+</TABLE></CENTER>
+<%init>
+
+local $SIG{__DIE__}; #disable Mason error trap
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+warn $cgi->param('q') if $DEBUG;
+
+my $q = decode_json($cgi->param('q'))
+ or die "bad argument '".$cgi->param('q')."'";
+
+my %location = (
+ map { $_ => $q->{'ship_'.$_} }
+ qw( company address1 address2 city state zip country latitude longitude )
+);
+
+my $old_tract = $q->{'ship_censustract'};
+my $cache = eval { FS::GeocodeCache->new(%location) };
+$cache->set_censustract;
+my $year = FS::Conf->new->config('census_year');
+my $new_tract = $cache->get('censustract');
+my $error = $cache->get('censustract_error');
+
+warn Dumper($cache) if $DEBUG;
+
+</%init>
diff --git a/httemplate/misc/order_pkg.html b/httemplate/misc/order_pkg.html
index c5f4509ab..bfc7b6903 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -131,6 +131,7 @@
'form' => "OrderPkgForm",
'onlyship' => 1,
'no_company' => 1,
+ 'no_census' => 1,
'callback' => 'document.OrderPkgForm.submit();',
&>
diff --git a/httemplate/misc/xmlhttp-address_standardize.html b/httemplate/misc/xmlhttp-address_standardize.html
new file mode 100644
index 000000000..f53c35fca
--- /dev/null
+++ b/httemplate/misc/xmlhttp-address_standardize.html
@@ -0,0 +1,38 @@
+<% encode_json($return) %>
+<%init>
+
+local $SIG{__DIE__}; #disable Mason error trap
+
+my $DEBUG = 0;
+
+my $conf = new FS::Conf;
+
+my $sub = $cgi->param('sub');
+
+warn $cgi->param('arg') if $DEBUG;
+
+my %old = %{ decode_json($cgi->param('arg')) }
+ or die "bad argument '".$cgi->param('arg')."'";
+
+my %new;
+
+foreach my $pre ( '', 'ship_' ) {
+ next unless ($pre || !$old{onlyship});
+
+ my $location = {
+ map { $_ => $old{$pre.$_} }
+ qw( company address1 address2 city state zip country )
+ };
+
+ my $cache = eval { FS::GeocodeCache->standardize($location) };
+ $cache->set_coord;
+ # don't do set_censustract here, though censustract may be set by now
+
+ foreach ( keys(%$cache) ) {
+ $new{$pre.$_} = $cache->get($_);
+ }
+}
+
+my $return = { old => \%old, new => \%new };
+warn "result:\n".encode_json($return) if $DEBUG;
+</%init>
diff --git a/httemplate/misc/xmlhttp-cust_main-address_standardize.html b/httemplate/misc/xmlhttp-cust_main-address_standardize.html
deleted file mode 100644
index d0627cd59..000000000
--- a/httemplate/misc/xmlhttp-cust_main-address_standardize.html
+++ /dev/null
@@ -1,93 +0,0 @@
-<% objToJson($return) %>
-<%init>
-
-my $DEBUG = 0;
-
-my $conf = new FS::Conf;
-
-my $sub = $cgi->param('sub');
-
-my $return = {};
-
-if ( $sub eq 'address_standardize' ) {
-
- my %arg = $cgi->param('arg');
- $return = \%arg;
- warn join('', map "$_: $arg{$_}\n", keys %arg )
- if $DEBUG;
-
- my $userid = $conf->config('usps_webtools-userid');
- my $password = $conf->config('usps_webtools-password');
-
- if ( length($userid) && length($password) ) {
-
- my $verifier = Business::US::USPS::WebTools::AddressStandardization->new( {
- UserID => $userid, #$ENV{USPS_WEBTOOLS_USERID},
- Password => $password, #$ENV{USPS_WEBTOOLS_PASSWORD},
- #Testing => 1,
- } );
-
- foreach my $pre ( '', 'ship_' ) {
- next unless ($pre || !$arg{onlyship});
-
- my($zip5, $zip4) = split('-',$arg{$pre.'zip'});
-
- my %usps_args = (
- FirmName => $arg{$pre.'company'},
- Address2 => $arg{$pre.'address1'},
- Address1 => $arg{$pre.'address2'},
- City => $arg{$pre.'city'},
- State => $arg{$pre.'state'},
- Zip5 => $zip5,
- Zip4 => $zip4,
- );
- warn join('', map "$_: $usps_args{$_}\n", keys %usps_args )
- if $DEBUG;
-
- my $hash = $verifier->verify_address( %usps_args );
-
- warn $verifier->response
- if $DEBUG;
-
- unless ( $verifier->is_error ) {
-
- my $zip = $hash->{Zip5};
- $zip .= '-'. $hash->{Zip4} if $hash->{Zip4} =~ /\d/;
-
- $return = {
- %$return,
- "new_$pre".'company' => $hash->{FirmName},
- "new_$pre".'address1' => $hash->{Address2},
- "new_$pre".'address2' => $hash->{Address1},
- "new_$pre".'city' => $hash->{City},
- "new_$pre".'state' => $hash->{State},
- "new_$pre".'zip' => $zip,
- };
-
- my @fields = (qw( company address1 address2 city state zip )); #hmm
-
- my $changed =
- scalar( grep { $return->{$pre.$_} ne $return->{"new_$pre$_"} }
- @fields
- )
- ? 1 : 0;
-
- $return->{$pre.'address_standardized'} = $changed;
-
- } else {
-
- $return->{$pre.'error'} = "USPS WebTools error: ".
- $verifier->{error}{description};
-
-
- }
-
- }
-
- }
-
- $return;
-
-}
-
-</%init>