[freeside-commits] branch master updated. 59958b6c8ec8418cf30d679a9afea478bab1f366

Mark Wells mark at 420.am
Mon Apr 28 23:29:12 PDT 2014


The branch, master has been updated
       via  59958b6c8ec8418cf30d679a9afea478bab1f366 (commit)
      from  00013557f2aecf4422d7f09ddcf97eea9f2b0289 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 59958b6c8ec8418cf30d679a9afea478bab1f366
Author: Mark Wells <mark at freeside.biz>
Date:   Mon Apr 28 23:29:03 2014 -0700

    make cust_main location upgrade non-blocking, and make the system somewhat functional before the upgrade is complete, #28883

diff --git a/FS/FS/Cursor.pm b/FS/FS/Cursor.pm
index 469a678..d94151f 100644
--- a/FS/FS/Cursor.pm
+++ b/FS/FS/Cursor.pm
@@ -2,10 +2,11 @@ package FS::Cursor;
 
 use strict;
 use vars qw($DEBUG $buffer);
-use FS::Record qw(dbh);
+use FS::Record;
+use FS::UID qw(myconnect);
 use Scalar::Util qw(refaddr);
 
-$DEBUG = 0;
+$DEBUG = 2;
 
 # this might become a parameter at some point, but right now, you can
 # "local $FS::Cursor::buffer = X;"
@@ -38,11 +39,13 @@ and returns an FS::Cursor object to fetch the rows one at a time.
 sub new {
   my $class = shift;
   my $q = FS::Record::_query(@_); # builds the statement and parameter list
+  my $dbh = myconnect();
 
   my $self = {
     query => $q,
     class => 'FS::' . ($q->{table} || 'Record'),
     buffer => [],
+    dbh   => $dbh,
   };
   bless $self, $class;
 
@@ -55,8 +58,8 @@ sub new {
   $self->{id} = sprintf('cursor%08x', refaddr($self));
   my $statement = "DECLARE ".$self->{id}." CURSOR FOR ".$q->{statement};
 
-  my $sth = dbh->prepare($statement)
-    or die dbh->errstr;
+  my $sth = $dbh->prepare($statement)
+    or die $dbh->errstr;
   my $bind = 1;
   foreach my $value ( @{ $q->{value} } ) {
     my $bind_type = shift @{ $q->{bind_type} };
@@ -65,7 +68,7 @@ sub new {
 
   $sth->execute or die $sth->errstr;
 
-  $self->{fetch} = dbh->prepare("FETCH FORWARD $buffer FROM ".$self->{id});
+  $self->{fetch} = $dbh->prepare("FETCH FORWARD $buffer FROM ".$self->{id});
 
   $self;
 }
@@ -105,7 +108,10 @@ sub refill {
 sub DESTROY {
   my $self = shift;
   return unless $self->{pid} eq $$;
-  dbh->do('CLOSE '. $self->{id}) or die dbh->errstr; # clean-up the cursor in Pg
+  $self->{dbh}->do('CLOSE '. $self->{id})
+    or die $self->{dbh}->errstr; # clean-up the cursor in Pg
+  $self->{dbh}->rollback;
+  $self->{dbh}->disconnect;
 }
 
 =back
diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm
index ef9c01a..034601d 100644
--- a/FS/FS/cust_bill_pkg.pm
+++ b/FS/FS/cust_bill_pkg.pm
@@ -970,7 +970,13 @@ sub tax_locationnum {
 
 sub tax_location {
   my $self = shift;
-  FS::cust_location->by_key($self->tax_locationnum);
+  if ( $self->pkgnum ) { # normal sales
+    return $self->cust_pkg->tax_location;
+  } elsif ( $self->feepart ) { # fees
+    return $self->cust_bill->cust_main->ship_location;
+  } else { # taxes
+    return;
+  }
 }
 
 =item part_X
@@ -1576,6 +1582,14 @@ sub _upgrade_data {
   });
   # call it kind of like a class method, not that it matters much
   $job->insert($class, 's' => str2time('2012-01-01'));
+  # if there's a customer location upgrade queued also, wait for it to 
+  # finish
+  my $location_job = qsearchs('queue', {
+      job => 'FS::cust_main::Location::process_upgrade_location'
+    });
+  if ( $location_job ) {
+    $job->depend_insert($location_job->jobnum);
+  }
   # Then mark the upgrade as done, so that we don't queue the job twice
   # and somehow run two of them concurrently.
   FS::upgrade_journal->set_done($upgrade);
diff --git a/FS/FS/cust_main/Location.pm b/FS/FS/cust_main/Location.pm
index 9899f72..560736d 100644
--- a/FS/FS/cust_main/Location.pm
+++ b/FS/FS/cust_main/Location.pm
@@ -70,7 +70,11 @@ Returns an L<FS::cust_location> object for the customer's billing address.
 sub bill_location {
   my $self = shift;
   $self->hashref->{bill_location} 
-    ||= FS::cust_location->by_key($self->bill_locationnum);
+    ||= FS::cust_location->by_key($self->bill_locationnum)
+    # degraded mode--let the system keep running during upgrades
+    ||  FS::cust_location->new({
+        map { $_ => $self->get($_) } @location_fields
+      })
 }
 
 =item ship_location
@@ -82,7 +86,11 @@ Returns an L<FS::cust_location> object for the customer's service address.
 sub ship_location {
   my $self = shift;
   $self->hashref->{ship_location}
-    ||= FS::cust_location->by_key($self->ship_locationnum);
+    ||= FS::cust_location->by_key($self->ship_locationnum)
+    ||  FS::cust_location->new({
+        map { $_ => $self->get('ship_'.$_) || $self->get($_) } @location_fields
+      })
+
 }
 
 =item location TYPE
@@ -127,11 +135,6 @@ 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'})
@@ -160,147 +163,19 @@ sub _upgrade_data {
     }
   }
 
-  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(),
-      }
-    );
-    $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') ) {
-      # 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;
-        }
-      }
-
-      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 }
-        grep { $cust_main->get($_) ne $cust_main->get("ship_$_") }
-        qw( last first company daytime night fax mobile );
-
-      if ( %unlike ) {
-        # then there IS a service contact
-        my $contact = FS::contact->new({
-          'custnum'     => $custnum,
-          'classnum'    => $service_contact_class->classnum,
-          'locationnum' => $ship_location->locationnum,
-          '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'));
-        }
-        $error = $contact->insert;
-        die "error migrating service contact for customer $custnum: $error"
-          if $error;
-
-        foreach ( grep { $unlike{$_} } qw( daytime night fax mobile ) ) {
-          my $phone = $cust_main->get("ship_$_");
-          next if !$phone;
-          my $contact_phone = FS::contact_phone->new({
-            'contactnum'    => $contact->contactnum,
-            'phonetypenum'  => $phone_type{$_}->phonetypenum,
-            FS::contact::_parse_phonestring( $phone )
-          });
-          $error = $contact_phone->insert;
-          # die "whose responsible this"
-          die "error migrating service contact phone for customer $custnum: $error"
-            if $error;
-          $cust_main->set("ship_$_" => '');
-        }
-
-        $cust_main->set("ship_$_" => '') foreach qw(last first company);
-      } #if %unlike
-    } #if ship_address1
-
-    # 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);
-      }
+  my $num_to_upgrade = FS::cust_main->count('bill_locationnum is null or ship_locationnum is null');
+  my $num_jobs = FS::queue->count('job = \'FS::cust_main::Location::process_upgrade_location\' and status != \'failed\'');
+  if ( $num_to_upgrade > 0 ) {
+    warn "Need to migrate $num_to_upgrade customer locations.\n";
+    if ( $num_jobs > 0 ) {
+      warn "Upgrade already queued.\n";
+    } else {
+      warn "Scheduling upgrade.\n";
+      my $job = FS::queue->new({ job => 'FS::cust_main::Location::process_upgrade_location' });
+      $job->insert;
     }
 
-    # 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();
-    $cust_main->set($_ => '') foreach location_fields();
-
-    $error = $cust_main->replace;
-    die "error migrating addresses for customer $custnum: $error"
-      if $error;
-
-    # Step 4: set packages at the "default service location" to ship_location
-    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;
-      die "error migrating package ".$cust_pkg->pkgnum.": $error"
-        if $error;
-    }
-
-  } #while (my $cust_main...)
+  }
 
   # repair an error in earlier upgrades
   if (!FS::upgrade_journal->is_done('cust_location_censustract_repair')
@@ -332,9 +207,198 @@ sub _upgrade_data {
     } # foreach $cust_location
     FS::upgrade_journal->set_done('cust_location_censustract_repair');
   }
+}
+
+sub process_upgrade_location {
+  my $class = shift;
+
+  my $dbh = dbh;
+  local $FS::cust_location::import = 1;
+  local $FS::UID::AutoCommit = 0;
+
+  my $tax_prefix = 'bill_';
+  if ( FS::Conf->new->exists('tax-ship_address') ) {
+    $tax_prefix = 'ship_';
+  }
+
+  # load some records that were created during the initial upgrade
+  my $service_contact_class = 
+    qsearchs('contact_class', { classname => 'Service'});
+
+  my %phone_type = (
+    daytime => 'Work',
+    night   => 'Home',
+    mobile  => 'Mobile',
+    fax     => 'Fax'
+  );
+  foreach (keys %phone_type) {
+    $phone_type{$_} = qsearchs('phone_type', { typename => $phone_type{$_}});
+  }
 
+  my %opt = (
+    tax_prefix            => $tax_prefix,
+    service_contact_class => $service_contact_class,
+    phone_type            => \%phone_type,
+  );
+
+  my $search = FS::Cursor->new('cust_main',
+                        { bill_locationnum => '',
+                          address1         => { op=>'!=', value=>'' }
+                        });
+  while (my $cust_main = $search->fetch) {
+    my $error = $cust_main->upgrade_location(%opt);
+    if ( $error ) {
+      warn "cust#".$cust_main->custnum.": $error\n";
+      $dbh->rollback;
+    } else {
+      # commit as we go
+      $dbh->commit;
+    }
+  }
 }
 
+sub upgrade_location { # instance method
+  my $cust_main = shift;
+  my %opt = @_;
+  my $error;
+
+  # 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(),
+    }
+  );
+  $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') ) {
+    # 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;
+      }
+    }
+
+    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 }
+      grep { $cust_main->get($_) ne $cust_main->get("ship_$_") }
+      qw( last first company daytime night fax mobile );
+
+    if ( %unlike ) {
+      # then there IS a service contact
+      my $contact = FS::contact->new({
+        'custnum'     => $custnum,
+        'classnum'    => $opt{service_contact_class}->classnum,
+        'locationnum' => $ship_location->locationnum,
+        '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'));
+      }
+      $error = $contact->insert;
+      return "error migrating service contact for customer $custnum: $error"
+        if $error;
+
+      foreach ( grep { $unlike{$_} } qw( daytime night fax mobile ) ) {
+        my $phone = $cust_main->get("ship_$_");
+        next if !$phone;
+        my $contact_phone = FS::contact_phone->new({
+          'contactnum'    => $contact->contactnum,
+          'phonetypenum'  => $opt{phone_type}->{$_}->phonetypenum,
+          FS::contact::_parse_phonestring( $phone )
+        });
+        $error = $contact_phone->insert;
+        return "error migrating service contact phone for customer $custnum: $error"
+          if $error;
+        $cust_main->set("ship_$_" => '');
+      }
+
+      $cust_main->set("ship_$_" => '') foreach qw(last first company);
+    } #if %unlike
+  } #if ship_address1
+
+  # 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 ( $opt{tax_prefix} eq 'bill_' ) {
+      $bill_location->set('geocode', $geocode);
+    } elsif ( $opt{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;
+  return "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;
+    return "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();
+  $cust_main->set($_ => '') foreach location_fields();
+
+  $error = $cust_main->replace;
+  return "error migrating addresses for customer $custnum: $error"
+    if $error;
+
+  # Step 4: set packages at the "default service location" to ship_location
+  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;
+    return "error migrating package ".$cust_pkg->pkgnum.": $error"
+      if $error;
+  }
+  '';
+
+}
+
+
 =back
 
 =cut
diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm
index d546e55..cf9e324 100644
--- a/FS/FS/cust_pkg.pm
+++ b/FS/FS/cust_pkg.pm
@@ -3416,7 +3416,16 @@ Returns the L<FS::cust_location> object for tax_locationnum.
 
 sub tax_location {
   my $self = shift;
-  FS::cust_location->by_key( $self->tax_locationnum )
+  my $conf = FS::Conf->new;
+  if ( $conf->exists('tax-pkg_address') and $self->locationnum ) {
+    return FS::cust_location->by_key($self->locationnum);
+  }
+  elsif ( $conf->exists('tax-ship_address') ) {
+    return $self->cust_main->ship_location;
+  }
+  else {
+    return $self->cust_main->bill_location;
+  }
 }
 
 =item seconds_since TIMESTAMP

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

Summary of changes:
 FS/FS/Cursor.pm             |   18 ++-
 FS/FS/cust_bill_pkg.pm      |   16 ++-
 FS/FS/cust_main/Location.pm |  356 +++++++++++++++++++++++++------------------
 FS/FS/cust_pkg.pm           |   11 ++-
 4 files changed, 247 insertions(+), 154 deletions(-)




More information about the freeside-commits mailing list