X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Fportaone.pm;h=986a556bad3a07d72519570e022171e5325a4e2e;hp=4779a92232d63ab7e614a8f509ab4117a3c808b6;hb=5372897f367498972c96f5494e142e6e11b29eb8;hpb=96d579596bf4dfbb4cd959bf8dc350ef8dc7fa06 diff --git a/FS/FS/part_export/portaone.pm b/FS/FS/part_export/portaone.pm index 4779a9223..986a556ba 100644 --- a/FS/FS/part_export/portaone.pm +++ b/FS/FS/part_export/portaone.pm @@ -4,6 +4,7 @@ use strict; use base qw( FS::part_export ); +use Date::Format 'time2str'; use Cpanel::JSON::XS; use Net::HTTPS::Any qw(https_post); @@ -23,12 +24,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. =cut @@ -42,10 +37,11 @@ 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' }, + 'customer_name' => { label => 'Customer Name', + default => 'FREESIDE CUST $custnum' }, + 'account_id' => { label => 'Account ID', + default => 'SVC$svcnum' }, + 'product_id' => { label => 'Account Product ID' }, 'debug' => { type => 'checkbox', label => 'Enable debug warnings' }, ; @@ -56,13 +52,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 options "Account ID" and "Account Product ID" are also specified, +an account will be created for the service and assigned to the customer, using +substitutions from the phone service record in freeside for the Account ID. + +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 +80,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 +102,59 @@ 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); + my $product_id = $self->option('product_id'); + if ($account_id && $product_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' => $account_id, + 'i_customer' => $i_customer, + 'iso_4217' => ($conf->config('currency') || 'USD'), + 'i_product' => $product_id, + 'activation_date' => time2str("%Y-%m-%d",time), + 'billing_model' => 1, # '1' for credit, '-1' for debit, could make this an export option + 'h323_password' => $svc_phone->sip_password, + } + },'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 +172,45 @@ 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'}; + # if nothing changed, no need to update account + $i_account = undef + if ($account_info->{'i_product'} eq $self->option('product_id')) + && ($account_id eq $self->portaone_account_id($svc_phone)); + # 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 +240,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 +313,21 @@ sub api_error { return $self->{'__portaone_error'} || ''; } +=head2 api_error_logout + +Attempts L, but returns L 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 +361,36 @@ 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. + +=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_product' => $self->option('product_id'), + 'h323_password' => $svc_phone->sip_password, + }, + },'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 +406,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