[freeside-commits] branch master updated. 48098d73a72b6cc4d4242ac66d497bf5a4466b7a

Jonathan Prykop jonathan at 420.am
Wed Mar 9 19:42:47 PST 2016


The branch, master has been updated
       via  48098d73a72b6cc4d4242ac66d497bf5a4466b7a (commit)
      from  7028a82d5c0f499ab582f7c452a759945fb915d0 (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 48098d73a72b6cc4d4242ac66d497bf5a4466b7a
Author: Jonathan Prykop <jonathan at freeside.biz>
Date:   Wed Mar 9 21:42:09 2016 -0600

    RT#40056: Export DID's to portaone switch [improvements]

diff --git a/FS/FS/part_export/portaone.pm b/FS/FS/part_export/portaone.pm
index 4779a92..be7e7be 100644
--- a/FS/FS/part_export/portaone.pm
+++ b/FS/FS/part_export/portaone.pm
@@ -23,12 +23,6 @@ PortaOne integration for Freeside
 
 This export offers basic svc_phone provisioning for PortaOne.
 
-During insert, this will add customers to portaone if they do not yet exist,
-using the customer prefix + custnum as the customer name.  An account will
-be created for the service and assigned to the customer, using account prefix
-+ svcnum as the account id.  During replace, the customer info will be updated
-if it already exists in the system.
-
 This module also provides generic methods for working through the L</PortaOne API>.
 
 =cut
@@ -42,10 +36,10 @@ tie my %options, 'Tie::IxHash',
                           default => '' },
   'port'             => { label => 'Port',
                           default => 443 },
-  'account_prefix'   => { label => 'Account ID Prefix',
-                          default => 'FREESIDE_CUST' },
-  'customer_prefix'  => { label => 'Customer Name Prefix',
-                          default => 'FREESIDE_SVC' },
+  'account_id'       => { label => 'Account ID',
+                          default => 'FREESIDE CUST $custnum' },
+  'customer_name'    => { label => 'Customer Name',
+                          default => 'FREESIDE SVC $svcnum' },
   'debug'            => { type => 'checkbox',
                           label => 'Enable debug warnings' },
 ;
@@ -56,13 +50,26 @@ tie my %options, 'Tie::IxHash',
   'options'         => \%options,
   'notes'           => <<'END',
 During insert, this will add customers to portaone if they do not yet exist,
-using the customer prefix + custnum as the customer name.  An account will
-be created for the service and assigned to the customer, using account prefix
-+ svcnum as the account id.  During replace, the customer info will be updated
-if it already exists in the system.
+using the "Customer Name" option with substitutions from the customer record 
+in freeside.  If option "Account ID" is also specified, an account will be 
+created for the service and assigned to the customer, using substitutions
+from the phone service record in freeside.
+
+During replace, if a matching account id for the old service can be found,
+the existing customer and account will be updated.  Otherwise, if a matching 
+customer name is found, the info for that customer will be updated.  
+Otherwise, nothing will be updated during replace.
+
+Use caution to avoid name/id conflicts when introducing this export to a portaone 
+system with existing customers/accounts.
 END
 );
 
+### NOTE:  If we provision DIDs, conflicts with existing data and changes
+### to the name/id scheme will be non-issues, as we can load DID by number 
+### and then load account/customer from there, but provisioning DIDs has
+### not yet been implemented....
+
 sub _export_insert {
   my ($self, $svc_phone) = @_;
 
@@ -71,22 +78,18 @@ sub _export_insert {
   return "Could not load service customer" unless $cust_main;
   my $conf = new FS::Conf;
 
+  # make sure customer name is configured
+  my $customer_name = $self->portaone_customer_name($cust_main);
+  return "No customer name configured, nothing to export"
+    unless $customer_name;
+
   # initialize api session
   $self->api_login;
   return $self->api_error if $self->api_error;
 
-  # load DID, abort if it is already assigned
-#  my $number_info = $self->api_call('DID','get_number_info',{
-#    'number' => $svc_phone->countrycode . $svc_phone->phonenum
-#  },'number_info');
-#  return $self->api_error if $self->api_error;
-#  return "Number is already assigned" if $number_info->{'i_account'};
-
-  # reserve DID
-
   # check if customer already exists
   my $customer_info = $self->api_call('Customer','get_customer_info',{
-    'name' => $self->option('customer_prefix') . $cust_main->custnum,
+    'name' => $customer_name,
   },'customer_info');
   my $i_customer = $customer_info ? $customer_info->{'i_customer'} : undef;
 
@@ -97,44 +100,54 @@ sub _export_insert {
   unless ($i_customer) {
     $i_customer = $self->api_call('Customer','add_customer',{
       'customer_info' => {
-        'name' => $self->option('customer_prefix') . $cust_main->custnum,
+        'name' => $customer_name,
         'iso_4217' => ($conf->config('currency') || 'USD'),
       }
     },'i_customer');
-    return $self->api_error if $self->api_error;
-    return "Error creating customer" unless $i_customer;
+    return $self->api_error_logout if $self->api_error;
+    unless ($i_customer) {
+      $self->api_logout;
+      return "Error creating customer";
+    }
   }
 
-  # check if account already exists
-  my $account_info = $self->api_call('Account','get_account_info',{
-    'id' => $self->option('account_prefix') . $svc_phone->svcnum,
-  },'account_info');
-
-  my $i_account;
-  if ($account_info) {
-    # there shouldn't be any time account already exists on insert,
-    # but if custnum & svcnum match, should be safe to run with it
-    return "Account " . $svc_phone->svcnum . " already exists"
-      unless $account_info->{'i_customer'} eq $i_customer;
-    $i_account = $account_info->{'i_account'};
-  } else {
-    # normal case--insert account for this service
-    $i_account = $self->api_call('Account','add_account',{
-      'account_info' => {
-        'id' => $self->option('account_prefix') . $svc_phone->svcnum,
-        'i_customer' => $i_customer,
-        'iso_4217' => ($conf->config('currency') || 'USD'),
+  # export account if account id is configured
+  my $account_id = $self->portaone_account_id($svc_phone);
+  if ($account_id) {
+    # check if account already exists
+    my $account_info = $self->api_call('Account','get_account_info',{
+      'id' => $account_id,
+    },'account_info');
+
+    my $i_account;
+    if ($account_info) {
+      # there shouldn't be any time account already exists on insert,
+      # but if custnum matches, should be safe to run with it
+      unless ($account_info->{'i_customer'} eq $i_customer) {
+        $self->api_logout;
+        return "Account $account_id already exists";
       }
-    },'i_account');
-    return $self->api_error if $self->api_error;
+      $i_account = $account_info->{'i_account'};
+    } else {
+      # normal case--insert account for this service
+      $i_account = $self->api_call('Account','add_account',{
+        'account_info' => {
+          'id' => $self->portaone_account_id($svc_phone),
+          'i_customer' => $i_customer,
+          'iso_4217' => ($conf->config('currency') || 'USD'),
+        }
+      },'i_account');
+      return $self->api_error_logout if $self->api_error;
+    }
+    unless ($i_account) {
+      $self->api_logout;
+      return "Error creating account";
+    }
   }
-  return "Error creating account" unless $i_account;
-
-  # assign DID to account
 
   # update customer, including name
   $self->api_update_customer($i_customer,$cust_main);
-  return $self->api_error if $self->api_error;
+  return $self->api_error_logout if $self->api_error;
 
   # end api session
   return $self->api_logout;
@@ -152,23 +165,41 @@ sub _export_replace {
   $self->api_login;
   return $self->api_error if $self->api_error;
 
-  # check for existing customer
-  #   should be loading this from DID...
-  my $customer_info = $self->api_call('Customer','get_customer_info',{
-    'name' => $cust_main->custnum,
-  },'customer_info');
-  my $i_customer = $customer_info ? $customer_info->{'i_customer'} : undef;
+  # if we ever provision DIDs, we should load from DID rather than account
 
-  return "Customer not found in portaone" unless $i_customer;
+  # check for existing account
+  my $account_id = $self->portaone_account_id($svc_phone_old);
+  my $account_info = $self->api_call('Account','get_account_info',{
+    'id' => $account_id,
+  },'account_info');
+  my $i_account = $account_info ? $account_info->{'i_account'} : undef;
 
-  # if did changed
-  #   make sure new did is available, reserve
-  #   release old did from account
-  #   assign new did to account
+  # if account exists, use account customer
+  my $i_customer;
+  if ($account_info) {
+    $i_account  = $account_info->{'i_account'};
+    $i_customer = $account_info->{'i_customer'};
+  # otherwise, check for existing customer
+  } else {
+    my $customer_name = $self->portaone_customer_name($cust_main);
+    my $customer_info = $self->api_call('Customer','get_customer_info',{
+      'name' => $customer_name,
+    },'customer_info');
+    $i_customer = $customer_info ? $customer_info->{'i_customer'} : undef;
+  }
+
+  unless ($i_customer) {
+    $self->api_logout;
+    return "Neither customer nor account found in portaone";
+  }
 
   # update customer info
-  $self->api_update_customer($i_customer,$cust_main);
-  return $self->api_error if $self->api_error;
+  $self->api_update_customer($i_customer,$cust_main) if $i_customer;
+  return $self->api_error_logout if $self->api_error;
+
+  # update account info
+  $self->api_update_account($i_account,$svc_phone) if $i_account;
+  return $self->api_error_logout if $self->api_error;
 
   # end api session
   return $self->api_logout();
@@ -198,12 +229,11 @@ set in the export options.
 	die $export->api_error if $export->api_error;
 
 	my $customer_info = $export->api_call('Customer','get_customer_info',{
-      'name' => $export->option('customer_prefix') . $cust_main->custnum,
+      'name' => $export->portaone_customer_name($cust_main),
     },'customer_info');
-	die $export->api_error if $export->api_error;
+	die $export->api_error_logout if $export->api_error;
 
-	$export->api_logout;
-	die $export->api_error if $export->api_error;
+	return $export->api_logout;
 
 =cut
 
@@ -272,6 +302,21 @@ sub api_error {
   return $self->{'__portaone_error'} || '';
 }
 
+=head2 api_error_logout
+
+Attempts L</api_logout>, but returns L</api_error> message from
+before logout was attempted.  Useful for logging out
+properly after an error.
+
+=cut
+
+sub api_error_logout {
+  my $self = shift;
+  my $error = $self->api_error;
+  $self->api_logout;
+  return $error;
+}
+
 =head2 api_login
 
 Initializes an api session using the credentials for this export.
@@ -305,6 +350,34 @@ sub api_logout {
   return $self->api_error;
 }
 
+=head2 api_update_account
+
+Accepts I<$i_account> and I<$svc_phone>.  Updates the account
+specified by I<$i_account> with the current values of I<$svc_phone>
+(currently only updates account_id.)
+Always returns empty.  Retrieve error messages using L</api_error>.
+
+=cut
+
+sub api_update_account {
+  my ($self,$i_account,$svc_phone) = @_;
+  my $newid = $self->portaone_account_id($svc_phone);
+  unless ($newid) {
+    $self->{'__portaone_error'} = "Error loading account id during update_account";
+    return;
+  }
+  my $updated_account = $self->api_call('Account','update_account',{
+    'account_info' => {
+      'i_account' => $i_account,
+      'id' => $newid,
+    },
+  },'i_account');
+  return if $self->api_error;
+  $self->{'__portaone_error'} = "Account updated, but account id mismatch detected"
+    unless $updated_account eq $i_account; # should never happen
+  return;
+}
+
 =head2 api_update_customer
 
 Accepts I<$i_customer> and I<$cust_main>.  Updates the customer
@@ -320,24 +393,72 @@ sub api_update_customer {
     $self->{'__portaone_error'} = "Could not load customer location";
     return;
   }
+  my $newname = $self->portaone_customer_name($cust_main);
+  unless ($newname) {
+    $self->{'__portaone_error'} = "Error loading customer name during update_customer";
+    return;
+  }
   my $updated_customer = $self->api_call('Customer','update_customer',{
-    'i_customer' => $i_customer,
-    'companyname' => $cust_main->company,
-    'firstname' => $cust_main->first,
-    'lastname' => $cust_main->last,
-    'baddr1' => $location->address1,
-    'baddr2' => $location->address2,
-    'city' => $location->city,
-    'state' => $location->state,
-    'zip' => $location->zip,
-    'country' => $location->country,
-    # could also add contact phones & email here
+    'customer_info' => {
+      'i_customer' => $i_customer,
+      'name' => $newname,
+      'companyname' => $cust_main->company,
+      'firstname' => $cust_main->first,
+      'lastname' => $cust_main->last,
+      'baddr1' => $location->address1,
+      'baddr2' => $location->address2,
+      'city' => $location->city,
+      'state' => $location->state,
+      'zip' => $location->zip,
+      'country' => $location->country,
+      # could also add contact phones & email here
+    },
   },'i_customer');
+  return if $self->api_error;
   $self->{'__portaone_error'} = "Customer updated, but custnum mismatch detected"
-    unless $updated_customer eq $i_customer;
+    unless $updated_customer eq $i_customer; # should never happen
   return;
 }
 
+sub _substitute {
+  my ($self, $string, @objects) = @_;
+  return '' unless $string;
+  foreach my $object (@objects) {
+    next unless $object;
+    foreach my $field ($object->fields) {
+      next unless $field;
+      my $value = $object->get($field);
+      $string =~ s/\$$field/$value/g;
+    }
+  }
+  # strip leading/trailing whitespace
+  $string =~ s/^\s//g;
+  $string =~ s/\s$//g;
+  return $string;
+}
+
+=head2 portaone_customer_name
+
+Accepts I<$cust_main> and returns customer name with substitutions.
+
+=cut
+
+sub portaone_customer_name {
+  my ($self, $cust_main) = @_;
+  $self->_substitute($self->option('customer_name'),$cust_main);
+}
+
+=head2 portaone_account_id
+
+Accepts I<$svc_phone> and returns account id with substitutions.
+
+=cut
+
+sub portaone_account_id {
+  my ($self, $svc_phone) = @_;
+  $self->_substitute($self->option('account_id'),$svc_phone);
+}
+
 =head1 SEE ALSO
 
 L<FS::part_export>
diff --git a/FS/bin/freeside-cdr-portaone-import b/FS/bin/freeside-cdr-portaone-import
index e2023c8..dfd130b 100644
--- a/FS/bin/freeside-cdr-portaone-import
+++ b/FS/bin/freeside-cdr-portaone-import
@@ -16,12 +16,14 @@ use FS::cdr_batch;
 
 sub usage {
   "Usage:
+freeside-cdr-portaone-import -x exportnum [-s startdate] [-e enddate] [-v] freesideuser
+
 freeside-cdr-portaone-import -h 'your.domain.com:443' -u switchusername -p switchpass 
   [-s startdate] [-e enddate] [-v] freesideuser
 ";
 }
 
-my ($host,$username,$password,$startdate,$enddate,$verbose);
+my ($host,$username,$password,$startdate,$enddate,$verbose,$exportnum);
 GetOptions(
   "enddate=s"   => \$enddate,
   "host=s"      => \$host,
@@ -29,11 +31,12 @@ GetOptions(
   "startdate=s" => \$startdate,
   "username=s"  => \$username,
   "verbose"     => \$verbose,
+  "x=s"         => \$exportnum,
 );
 
 my $fsuser = $ARGV[-1];
 
-die usage() unless $host && $password && $username && $fsuser;
+die usage() unless $fsuser;
 
 adminsuidsetup($fsuser);
 
@@ -43,6 +46,19 @@ if ($host =~ /^(.*)\:(.*)$/) {
   $port = $2;
 }
 
+if ($exportnum) {
+  my $export = qsearchs('part_export', { 'exportnum' => $exportnum });
+  die "Export not found" unless $export;
+  $host = $export->machine;
+  $port = $export->option('port');
+  $username = $export->option('username');
+  $password = $export->option('password');
+  die "Auth info not fully specified in export"
+    unless $host && $port && $username && $password;
+}
+
+die usage() unless $host && $password && $username;
+
 if ($startdate) {
   $startdate = str2time($startdate) or die "Can't parse startdate $startdate";
   $startdate = time2str("%Y-%m-%d %H:%M:%S",$startdate);

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

Summary of changes:
 FS/FS/part_export/portaone.pm       |  283 +++++++++++++++++++++++++----------
 FS/bin/freeside-cdr-portaone-import |   20 ++-
 2 files changed, 220 insertions(+), 83 deletions(-)




More information about the freeside-commits mailing list