"on hold" package ordering and status, RT#28508
[freeside.git] / FS / FS / cust_main / Location.pm
index d1d6d67..9899f72 100644 (file)
@@ -4,11 +4,12 @@ use strict;
 use vars qw( $DEBUG $me @location_fields );
 use FS::Record qw(qsearch qsearchs);
 use FS::UID qw(dbh);
+use FS::Cursor;
 use FS::cust_location;
 
 use Carp qw(carp);
 
-$DEBUG = 1;
+$DEBUG = 0;
 $me = '[FS::cust_main::Location]';
 
 my $init = 0;
@@ -18,16 +19,19 @@ BEGIN {
     no strict 'refs';
     @location_fields = 
       qw( address1 address2 city county state zip country district
-        latitude longitude coord_auto censustract censusyear geocode );
+        latitude longitude coord_auto censustract censusyear geocode
+        addr_clean );
 
     foreach my $f (@location_fields) {
       *{"FS::cust_main::Location::$f"} = sub {
         carp "WARNING: tried to set cust_main.$f with accessor" if (@_ > 1);
-        shift->bill_location->$f
+        my $l = shift->bill_location;
+        $l ? $l->$f : '';
       };
       *{"FS::cust_main::Location::ship_$f"} = sub {
         carp "WARNING: tried to set cust_main.ship_$f with accessor" if (@_ > 1);
-        shift->ship_location->$f
+        my $l = shift->ship_location;
+        $l ? $l->$f : '';
       };
     }
     $init++;
@@ -123,12 +127,18 @@ sub _upgrade_data {
   local $DEBUG = 0;
   my $error;
 
+  my $tax_prefix = 'bill_';
+  if ( FS::Conf->new->exists('tax-ship_address') ) {
+    $tax_prefix = 'ship_';
+  }
+
   # Step 0: set up contact classes and phone types
   my $service_contact_class = 
     qsearchs('contact_class', { classname => 'Service'})
     || new FS::contact_class { classname => 'Service'};
 
   if ( !$service_contact_class->classnum ) {
+    warn "Creating service contact class.\n";
     $error = $service_contact_class->insert;
     die "error creating contact class for Service: $error" if $error;
   }
@@ -146,37 +156,48 @@ sub _upgrade_data {
     # just in case someone still doesn't have these
     if ( !$phone_type{$_}->phonetypenum ) {
       $error = $phone_type{$_}->insert;
-      die "error creating phone type '$_': $error";
+      die "error creating phone type '$_': $error" if $error;
     }
   }
 
-  foreach my $cust_main (qsearch('cust_main', { bill_locationnum => '' })) {
+  warn "Migrating customer locations.\n";
+  my $search = FS::Cursor->new('cust_main',
+                        { bill_locationnum  => '',
+                          address1          => { op=>'!=', value=>'' }
+                        });
+  while (my $cust_main = $search->fetch) {
     # Step 1: extract billing and service addresses into cust_location
     my $custnum = $cust_main->custnum;
     my $bill_location = FS::cust_location->new(
       {
         custnum => $custnum,
-        map { $_ => $cust_main->get($_) } location_fields()
+        map { $_ => $cust_main->get($_) } location_fields(),
       }
     );
-    $error = $bill_location->insert;
-    die "error migrating billing address for customer $custnum: $error"
-      if $error;
-
-    $cust_main->set(bill_locationnum => $bill_location->locationnum);
+    $bill_location->set('censustract', '');
+    $bill_location->set('censusyear', '');
+     # properly goes with ship_location; if they're the same, will be set
+     # on ship_location before inserting either one
+    my $ship_location = $bill_location; # until proven otherwise
 
     if ( $cust_main->get('ship_address1') ) {
-      my $ship_location = FS::cust_location->new(
-        {
-          custnum => $custnum,
-          map { $_ => $cust_main->get("ship_$_") } location_fields()
+      # detect duplicates
+      my $same = 1;
+      foreach (location_fields()) {
+        if ( length($cust_main->get("ship_$_")) and
+             $cust_main->get($_) ne $cust_main->get("ship_$_") ) {
+          $same = 0;
         }
-      );
-      $error = $ship_location->insert;
-      die "error migrating service address for customer $custnum: $error"
-        if $error;
+      }
 
-      $cust_main->set(ship_locationnum => $ship_location->locationnum);
+      if ( !$same ) {
+        $ship_location = FS::cust_location->new(
+          {
+            custnum => $custnum,
+            map { $_ => $cust_main->get("ship_$_") } location_fields()
+          }
+        );
+      } # else it stays equal to $bill_location
 
       # Step 2: Extract shipping address contact fields into contact
       my %unlike = map { $_ => 1 }
@@ -192,6 +213,14 @@ sub _upgrade_data {
           'last'        => $cust_main->get('ship_last'),
           'first'       => $cust_main->get('ship_first'),
         });
+        if ( !$cust_main->get('ship_last') or !$cust_main->get('ship_first') )
+        {
+          warn "customer $custnum has no service contact name; substituting ".
+               "customer name\n";
+          $contact->set('last' => $cust_main->get('last'));
+          $contact->set('first' => $cust_main->get('first'));
+        }
+
         if ( $unlike{'company'} ) {
           # there's no contact.company field, but keep a record of it
           $contact->set(comment => 'Company: '.$cust_main->get('ship_company'));
@@ -218,10 +247,39 @@ sub _upgrade_data {
         $cust_main->set("ship_$_" => '') foreach qw(last first company);
       } #if %unlike
     } #if ship_address1
-    else {
-      $cust_main->set(ship_locationnum => $bill_location->locationnum);
+
+    # special case: should go with whichever location is used to calculate
+    # taxes, because that's the one it originally came from
+    if ( my $geocode = $cust_main->get('geocode') ) {
+      $bill_location->set('geocode' => '');
+      $ship_location->set('geocode' => '');
+
+      if ( $tax_prefix eq 'bill_' ) {
+        $bill_location->set('geocode', $geocode);
+      } elsif ( $tax_prefix eq 'ship_' ) {
+        $ship_location->set('geocode', $geocode);
+      }
     }
 
+    # this always goes with the ship_location (whether it's the same as
+    # bill_location or not)
+    $ship_location->set('censustract', $cust_main->get('censustract'));
+    $ship_location->set('censusyear',  $cust_main->get('censusyear'));
+
+    $error = $bill_location->insert;
+    die "error migrating billing address for customer $custnum: $error"
+      if $error;
+
+    $cust_main->set(bill_locationnum => $bill_location->locationnum);
+
+    if (!$ship_location->locationnum) {
+      $error = $ship_location->insert;
+      die "error migrating service address for customer $custnum: $error"
+        if $error;
+    }
+
+    $cust_main->set(ship_locationnum => $ship_location->locationnum);
+
     # Step 3: Wipe the migrated fields and update the cust_main
 
     $cust_main->set("ship_$_" => '') foreach location_fields();
@@ -232,9 +290,9 @@ sub _upgrade_data {
       if $error;
 
     # Step 4: set packages at the "default service location" to ship_location
-    foreach my $cust_pkg (
-      qsearch('cust_pkg', { custnum => $custnum, locationnum => '' })  
-    ) {
+    my $pkg_search =
+      FS::Cursor->new('cust_pkg', { custnum => $custnum, locationnum => '' });
+    while (my $cust_pkg = $pkg_search->fetch) {
       # not a location change
       $cust_pkg->set('locationnum', $cust_main->ship_locationnum);
       $error = $cust_pkg->replace;
@@ -242,7 +300,39 @@ sub _upgrade_data {
         if $error;
     }
 
-  } #foreach $cust_main
+  } #while (my $cust_main...)
+
+  # repair an error in earlier upgrades
+  if (!FS::upgrade_journal->is_done('cust_location_censustract_repair')
+       and FS::Conf->new->exists('cust_main-require_censustract') ) {
+
+    foreach my $cust_location (
+      qsearch('cust_location', { 'censustract' => '' })
+    ) {
+      my $custnum = $cust_location->custnum;
+      next if !$custnum; # avoid doing this for prospect locations
+      my $address1 = $cust_location->address1;
+      # find the last history record that had that address
+      my $last_h = qsearchs({
+          table     => 'h_cust_main',
+          extra_sql => " WHERE custnum = $custnum AND address1 = ".
+                        dbh->quote($address1) .
+                        " AND censustract IS NOT NULL",
+          order_by  => " ORDER BY history_date DESC LIMIT 1",
+      });
+      if (!$last_h) {
+        # this is normal; just means it never had a census tract before
+        next;
+      }
+      $cust_location->set('censustract' => $last_h->get('censustract'));
+      $cust_location->set('censusyear'  => $last_h->get('censusyear'));
+      my $error = $cust_location->replace;
+      warn "Error setting census tract for customer #$custnum:\n  $error\n"
+        if $error;
+    } # foreach $cust_location
+    FS::upgrade_journal->set_done('cust_location_censustract_repair');
+  }
+
 }
 
 =back