[freeside-commits] branch 13763 created. 3fd1a0086512ada7b04e211161ac699d932ae1d0

Mark Wells mark at 420.am
Fri Mar 30 15:23:23 PDT 2012


The branch, 13763 has been created
        at  3fd1a0086512ada7b04e211161ac699d932ae1d0 (commit)

- Log -----------------------------------------------------------------
commit 3fd1a0086512ada7b04e211161ac699d932ae1d0
Author: Mark Wells <mark at freeside.biz>
Date:   Wed Mar 14 13:27:33 2012 -0700

    alternate address standardization method (TeleAtlas), #13763

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index 1b01aa6..04ca35a 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -3735,6 +3735,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',
+                       'teleatlas' => 'TeleAtlas',
+                     ],
+  },
+
+  {
     '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.',
@@ -3749,6 +3760,27 @@ and customer address. Include units.',
   },
 
   {
+    'key'         => 'teleatlas-path',
+    'section'     => 'UI',
+    'description' => 'Path to TeleAtlas libraries on the Freeside server.',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'teleatlas-userid',
+    'section'     => 'UI',
+    'description' => 'User ID for TeleAtlas EZ-Locate service.  See <a href="http://www.geocode.com/">the Tele Atlas website</a> for access and pricing information.',
+    'type'        => 'text',
+  },
+
+  {
+    'key'         => 'teleatlas-password',
+    'section'     => 'UI',
+    'description' => 'Password for TeleAtlas 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/Mason.pm b/FS/FS/Mason.pm
index 7143c72..62568eb 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;
@@ -304,6 +304,7 @@ if ( -e $addl_handler_use_file ) {
   use FS::tower;
   use FS::tower_sector;
   use FS::contact_class;
+  use FS::geocode_cache;
   # Sammath Naur
 
   if ( $FS::Mason::addl_handler_use ) {
diff --git a/FS/FS/Misc/Geo.pm b/FS/FS/Misc/Geo.pm
index d7375b0..174357c 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;
 use Data::Dumper;
 
+FS::UID->install_callback( sub {
+  $conf = new FS::Conf;
+} );
+
 $DEBUG = 0;
 
- at EXPORT_OK = qw( get_censustract get_district );
+ at 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" 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,128 @@ 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";
+
+  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} 
+    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 %teleatlas_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_teleatlas {
+  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')
+    or die "no teleatlas-path configured";
+  my $userid = $conf->config('teleatlas-userid')
+    or die "no teleatlas-userid configured";
+  my $password = $conf->config('teleatlas-password')
+    or die "no teleatlas-password configured";
+
+  local @INC = (@INC, $path);
+  eval "use $class;";
+  if ( $@ ) {
+    die "Loading $class failed:\n$@".
+        "\nMake sure the TeleAtlas Perl SDK is installed correctly.\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 "teleatlas returned match:\n".Dumper($match) if $DEBUG > 1;
+  # error handling - B codes indicate success
+  die $teleatlas_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}.$match->{CEN_TRCT},
+    addr_clean  => 'Y',
+  };
+}
+
 =back
 
 =cut
diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm
index 5147432..2deb884 100644
--- a/FS/FS/Schema.pm
+++ b/FS/FS/Schema.pm
@@ -858,6 +858,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, '', '', 
@@ -876,6 +877,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, '', '', 
@@ -1064,6 +1066,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 f863b10..9df8de6 100644
--- a/FS/FS/cust_location.pm
+++ b/FS/FS/cust_location.pm
@@ -186,6 +186,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' ] )
diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm
index 7d1a156..ebc0491 100644
--- a/FS/FS/cust_main.pm
+++ b/FS/FS/cust_main.pm
@@ -1498,30 +1498,30 @@ sub replace {
       && length($self->get($pre.'zip')) >= 10;
   }
 
-  for my $pre ( grep $old->get($_.'coord_auto'), ( '', 'ship_' ) ) {
-
-    $self->set($pre.'coord_auto', '') && next
-      if $self->get($pre.'latitude') && $self->get($pre.'longitude')
-      && (    $self->get($pre.'latitude')  != $old->get($pre.'latitude')
-           || $self->get($pre.'longitude') != $old->get($pre.'longitude')
-         );
-
-    $self->set_coord($pre)
-      if $old->get($pre.'address1') ne $self->get($pre.'address1')
-      || $old->get($pre.'city')     ne $self->get($pre.'city')
-      || $old->get($pre.'state')    ne $self->get($pre.'state')
-      || $old->get($pre.'country')  ne $self->get($pre.'country');
-
-  }
-
-  unless ( $import ) {
-    $self->set_coord
-      if ! $self->coord_auto && ! $self->latitude && ! $self->longitude;
-
-    $self->set_coord('ship_')
-      if $self->has_ship_address && ! $self->ship_coord_auto
-      && ! $self->ship_latitude && ! $self->ship_longitude;
-  }
+#  for my $pre ( grep $old->get($_.'coord_auto'), ( '', 'ship_' ) ) {
+#
+#    $self->set($pre.'coord_auto', '') && next
+#      if $self->get($pre.'latitude') && $self->get($pre.'longitude')
+#      && (    $self->get($pre.'latitude')  != $old->get($pre.'latitude')
+#           || $self->get($pre.'longitude') != $old->get($pre.'longitude')
+#         );
+#
+#    $self->set_coord($pre)
+#      if $old->get($pre.'address1') ne $self->get($pre.'address1')
+#      || $old->get($pre.'city')     ne $self->get($pre.'city')
+#      || $old->get($pre.'state')    ne $self->get($pre.'state')
+#      || $old->get($pre.'country')  ne $self->get($pre.'country');
+#
+#  }
+#
+#  unless ( $import ) {
+#    $self->set_coord
+#      if ! $self->coord_auto && ! $self->latitude && ! $self->longitude;
+#
+#    $self->set_coord('ship_')
+#      if $self->has_ship_address && ! $self->ship_coord_auto
+#      && ! $self->ship_latitude && ! $self->ship_longitude;
+#  }
 
   local($ignore_expired_card) = 1
     if $old->payby  =~ /^(CARD|DCRD)$/
@@ -1766,6 +1766,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_numbern('censusyear')
     || $self->ut_anything('comments')
     || $self->ut_numbern('referral_custnum')
diff --git a/FS/FS/geocode_cache.pm b/FS/FS/geocode_cache.pm
new file mode 100644
index 0000000..0041e37
--- /dev/null
+++ b/FS/FS/geocode_cache.pm
@@ -0,0 +1,212 @@
+package FS::geocode_cache;
+
+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::geocode_cache - An address undergoing the geocode process.
+
+=head1 SYNOPSIS
+
+  use FS::geocode_cache;
+
+  $record = FS::geocode_cache->standardize(%location_hash);
+
+=head1 DESCRIPTION
+
+An FS::geocode_cache object represents a street address in the process of 
+being geocoded.  FS::geocode_cache inherits from FS::geocode_Mixin.
+
+Most methods on this object throw an exception on error.
+
+FS::geocode_cache has the following fields, with the same meaning as in 
+L<FS::cust_location>:
+
+=over 4
+
+All other fields have the same meaning as in L<FS::cust_main> and 
+L<FS::cust_location>:
+
+=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::geocode_cache> 
+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/MANIFEST b/FS/MANIFEST
index f0a4a9d..76741ad 100644
--- a/FS/MANIFEST
+++ b/FS/MANIFEST
@@ -632,3 +632,5 @@ FS/h_svc_cert.pm
 t/h_svc_cert.t
 FS/contact_class.pm
 t/contact_class.t
+FS/geocode_cache.pm
+t/geocode_cache.t
diff --git a/FS/t/geocode_cache.t b/FS/t/geocode_cache.t
new file mode 100644
index 0000000..7cbc58d
--- /dev/null
+++ b/FS/t/geocode_cache.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::geocode_cache;
+$loaded=1;
+print "ok 1\n";
diff --git a/bin/generate-table-module b/bin/generate-table-module
index e7fc992..b536360 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 0000000..414ae4c
--- /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 3994313..ca9f867 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -318,6 +318,9 @@ if ( $cgi->param('error') ) {
   $stateid = '';
   $payinfo = '';
 
+  $cust_main->coord_auto('Y');
+  $cust_main->ship_coord_auto('Y');
+
   if ( $cgi->param('qualnum') =~ /^(\d+)$/ ) {
     my $qualnum = $1;
     my $qual = qsearchs('qual', { 'qualnum' => $qualnum } )
diff --git a/httemplate/edit/cust_main/bottomfixup.html b/httemplate/edit/cust_main/bottomfixup.html
index 60edcc1..b5d10c4 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 800864b..40bcbd5 100644
--- a/httemplate/edit/cust_main/bottomfixup.js
+++ b/httemplate/edit/cust_main/bottomfixup.js
@@ -7,8 +7,8 @@ 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, 'fetch_censustract'
+#    if $conf->exists('cust_main-require_censustract');
 
 push @fixups, 'check_unique'
     if $conf->exists('cust_main-check_unique') and !$opt{'custnum'};
@@ -18,15 +18,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 +39,7 @@ function submit_abort() {
 function bottomfixup(what) {
   fixup_position = 0;
   document.CustomerForm.submitButton.disabled = true;
+  running = true;
   submit_continue();
 }
 
@@ -63,107 +68,11 @@ 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();'
           )
 %>
 
-function fetch_censustract() {
-
-  //alert('fetch census tract data');
-  var cf = document.CustomerForm;
-  var state_el = cf.elements['ship_state'];
-  var census_data = new Array(
-    'year',     <% $conf->config('census_year') || '2012' %>,
-    'address1', cf.elements['ship_address1'].value,
-    'city',     cf.elements['ship_city'].value,
-    'state',    state_el.options[ state_el.selectedIndex ].value,
-    'zip',      cf.elements['ship_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['censustract'].value = newcensus;
-    submit_continue();
-
-  }
-
-  if (error || cf.elements['censustract'].value != newcensus) {
-    // popup an entry dialog
-
-    if (error) { newcensus = error; }
-    newcensus.replace(/.*ndefined.*/, 'Not found');
-
-    var latitude = cf.elements['latitude' ].value || '<% $company_latitude %>';
-    var longitude= cf.elements['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['ship_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['censustract'].value +
-        '</TD><TD>' + newcensus + '</TD></TR>' +
-        '<TR><TD> </TD><TD> </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 = '';
diff --git a/httemplate/edit/cust_main/contact.html b/httemplate/edit/cust_main/contact.html
index 57490b9..4140ec1 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 c606523..053e0e5 100644
--- a/httemplate/elements/location.html
+++ b/httemplate/elements/location.html
@@ -162,7 +162,7 @@ Example:
            NAME     = "<%$pre%>zip"
            ID       = "<%$pre%>zip"
            VALUE    = "<% $object->get($pre.'zip') |h %>"
-           SIZE     = 10
+           SIZE     = 11
            onChange = "<% $onchange %>"
            <% $disabled %>
            <% $style %>
@@ -220,6 +220,16 @@ Example:
 %   }
 % } 
 
+%# 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.$_%>" 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 4807359..84c7a82 100644
--- a/httemplate/elements/order_pkg.js
+++ b/httemplate/elements/order_pkg.js
@@ -19,13 +19,13 @@ 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;
+      //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 = '';
+      //form.start_date_button_disabled.style.display = '';
     }
 
   } else {
@@ -44,3 +44,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 9f8b71c..5a4ee0f 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 e6a4aa6..8bec87e 100644
--- a/httemplate/elements/standardize_locations.js
+++ b/httemplate/elements/standardize_locations.js
@@ -1,176 +1,171 @@
 function standardize_locations() {
 
+  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 state_el      = cf.elements['<% $main_prefix %>state'];
   var ship_state_el = cf.elements['<% $ship_prefix %>state'];
 
-  var address_info = new Array(
+  var changed = false; // have any of the address fields been changed?
+  var address_info = {
 % if ( $onlyship ) {
-    'onlyship', 1,
+    'onlyship': 1,
 % } else {
 %   if ( $withfirm ) {
-    'company',  cf.elements['<% $main_prefix %>company'].value,
+    'company':  cf.elements['<% $main_prefix %>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,
 % }
 % if ( $withfirm ) {
-    'ship_company',  cf.elements['<% $ship_prefix %>company'].value,
+    'ship_company':  cf.elements['<% $ship_prefix %>company'].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 );
-
-}
-
-var standardize_address;
-
-function update_address(arg) {
+% if ( $withcensus ) {
+    'ship_censustract': cf.elements['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,
+  };
+
+// 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') {
+
+  if ( cf.elements['<% $field %>'].value != cf.elements['old_<% $field %>'].value ) {
+    cf.elements['<% $pre %>coord_auto'].value = '';
+  }
 
-  var argsHash = eval('(' + arg + ')');
+%   }
+// unless they're both empty
+  if ( cf.elements['<% $pre %>latitude'] == '' &&
+       cf.elements['<% $pre %>longitude'] == '' ) {
+    cf.elements['<% $pre %>coord_auto'].value = 'Y';
+    changed = true;
+  }
 
-  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 () {
+  // 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 == '' ) ) {
 
-    var cf = document.<% $formname %>;
-    var state_el      = cf.elements['<% $main_prefix %>state'];
-    var ship_state_el = cf.elements['<% $ship_prefix %>state'];
+    changed = true;
 
-% 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'];
+  }
+  // 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 ( ship_changed ) {
-% if ( $withfirm ) {
-      cf.elements['<% $ship_prefix %>company'].value  = argsHash['new_ship_company'];
+  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';
 % }
-      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();
-
   }
+}
 
+var returned;
 
+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);
 
-  if ( changed || ship_changed ) {
-
-%   if ( $conf->exists('cust_main-auto_standardize_address') ) {
-
-    standardize_address();
-
-%   } else {
-
-    // popup a confirmation popup
-
-    var confirm_change =
-      '<CENTER><BR><B>Confirm address standardization</B><BR><BR>' +
-      '<TABLE>';
-    
-    if ( changed ) {
+  if ( <% $conf->exists('cust_main-auto_standardize_address') || 0 %> ) {
 
-      confirm_change = confirm_change + 
-        '<TR><TH>Entered billing address</TH>' +
-          '<TH>Standardized billing address</TH></TR>';
-        // + '<TR><TD> </TD><TD> </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> </TD><TD> </TD></TR>';
-
-    }
+    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> </TD><TD> </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> </TD><TD> </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 ( $withcensus ) {
+      cf.elements['<% $main_prefix %>censustract'].value = newaddr['ship_censustract']
+%    }
+      if ( cf.elements['<% $ship_prefix %>coord_auto'].value ) {
+        cf.elements['<% $ship_prefix %>latitude'].value = newaddr['latitude'];
+        cf.elements['<% $ship_prefix %>longitude'].value = newaddr['longitude'];
+      }
   }
 
+  post_standardization();
 
 }
 
@@ -265,6 +260,7 @@ my %opt = @_;
 my $conf = new FS::Conf;
 
 my $withfirm = 1;
+my $withcensus = 1;
 
 my $formname =  $opt{form} || 'CustomerForm';
 my $onlyship =  $opt{onlyship} || '';
@@ -274,5 +270,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 0ca255b..801152e 100644
--- a/httemplate/elements/tr-select-cust_location.html
+++ b/httemplate/elements/tr-select-cust_location.html
@@ -53,7 +53,8 @@ Example:
       if( ftype != 'SELECT') what.form.<%$_%>.style.backgroundColor = '#ffffff';
 %   } 
 
-    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';
     }
@@ -294,6 +295,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 2ab9329..7b08f7b 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 0000000..1f94dd9
--- /dev/null
+++ b/httemplate/misc/confirm-address_standardize.html
@@ -0,0 +1,122 @@
+<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><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
+
+% if ( $old{'ship_censustract'} or $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="post_standardization();">
+      <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="post_standardization();">
+      <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/order_pkg.html b/httemplate/misc/order_pkg.html
index 2332f20..66717b5 100644
--- a/httemplate/misc/order_pkg.html
+++ b/httemplate/misc/order_pkg.html
@@ -114,6 +114,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 0000000..f305a24
--- /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::geocode_cache->standardize($location) };
+  $cache->set_censustract if $pre;
+  $cache->set_coord;
+
+  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 d0627cd..0000000
--- 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>

-----------------------------------------------------------------------




More information about the freeside-commits mailing list