sipwise export, part 2
authorMark Wells <mark@freeside.biz>
Fri, 26 Feb 2016 01:10:56 +0000 (17:10 -0800)
committerMark Wells <mark@freeside.biz>
Fri, 26 Feb 2016 01:11:10 +0000 (17:11 -0800)
FS/FS/Schema.pm
FS/FS/part_export/sipwise.pm
FS/FS/svc_acct.pm
FS/FS/svc_phone.pm
httemplate/edit/svc_phone.cgi
httemplate/elements/tr-select-svc_phone-forward.html [new file with mode: 0644]
httemplate/view/svc_phone.cgi

index 36418ac..d20385e 100644 (file)
@@ -5842,6 +5842,7 @@ sub tables_hashref {
         'pbxsvc',                         'int', 'NULL',      '', '', '',
         'domsvc',                         'int', 'NULL',      '', '', '', 
         'locationnum',                    'int', 'NULL',      '', '', '',
+        'forward_svcnum',                 'int', 'NULL',      '', '', '',
         'forwarddst',                 'varchar', 'NULL',      15, '', '', 
         'email',                      'varchar', 'NULL',     255, '', '', 
         'lnp_status',                 'varchar', 'NULL', $char_d, '', '',
index 690a14c..d2af7da 100644 (file)
@@ -5,7 +5,6 @@ use strict;
 
 use FS::Record qw(qsearch qsearchs dbh);
 use Tie::IxHash;
-use Carp;
 use LWP::UserAgent;
 use URI;
 use Cpanel::JSON::XS;
@@ -13,6 +12,7 @@ use HTTP::Request::Common qw(GET POST PUT DELETE);
 use FS::Misc::DateTime qw(parse_datetime);
 use DateTime;
 use Number::Phone;
+use Try::Tiny;
 
 our $me = '[sipwise]';
 our $DEBUG = 2;
@@ -34,7 +34,7 @@ tie my %options, 'Tie::IxHash',
 
 tie my %roles, 'Tie::IxHash',
   'subscriber'    => {  label     => 'Subscriber',
-                        svcdb     => 'svc_phone',
+                        svcdb     => 'svc_acct',
                         multiple  => 1,
                      },
   'did'           => {  label     => 'DID',
@@ -44,7 +44,7 @@ tie my %roles, 'Tie::IxHash',
 ;
 
 our %info = (
-  'svc'      => [qw( svc_phone )],
+  'svc'      => [qw( svc_acct svc_phone )],
   'desc'     => 'Provision to a Sipwise sip:provider server',
   'options'  => \%options,
   'roles'    => \%roles,
@@ -52,12 +52,12 @@ our %info = (
 <P>Export to a <b>sip:provider</b> server.</P>
 <P>This requires two service definitions to be configured on the same package:
   <OL>
-    <LI>A phone service for a SIP client account ("subscriber"). The
-    <i>phonenum</i> will be the SIP username. The <i>domsvc</i> should point
+    <LI>An account service for a SIP client account ("subscriber"). The
+    <i>username</i> will be the SIP username. The <i>domsvc</i> should point
     to a domain service to use as the SIP domain name.</LI>
     <LI>A phone service for a DID. The <i>phonenum</i> here will be a PSTN
-    number. The <i>forwarddst</i> field should be set to the SIP username
-    of the subscriber who should receive calls directed to this number.</LI>
+    number. The <i>forward_svcnum</i> field should be set to the account that
+    will receive calls at this number.
   </OL>
 </P>
 <P>Export options:
@@ -68,63 +68,61 @@ END
 sub export_insert {
   my($self, $svc_x) = (shift, shift);
 
-  local $@;
+  my $error;
   my $role = $self->svc_role($svc_x);
   if ( $role eq 'subscriber' ) {
 
-    eval { $self->insert_subscriber($svc_x) };
-    return "$me $@" if $@;
+    try { $self->insert_subscriber($svc_x) }
+    catch { $error = $_ };
 
   } elsif ( $role eq 'did' ) {
 
-    # only export the DID if it's set to forward to somewhere...
-    return if $svc_x->forwarddst eq '';
-    my $subscriber = qsearchs('svc_phone', { phonenum => $svc_x->forwarddst });
-    # and there is a service for the forwarding destination...
-    return if !$subscriber;
-    # and that service is managed by this export.
-    return if !$self->svc_role($subscriber);
-
-    eval { $self->replace_subscriber($subscriber) };
-    return "$me $@" if $@;
+    try { $self->export_did($svc_x) }
+    catch { $error = $_ };
 
   }
+  return "$me $error" if $error;
   '';
 }
 
 sub export_replace {
   my ($self, $svc_new, $svc_old) = @_;
   my $role = $self->svc_role($svc_new);
-  local $@;
+
+  my $error;
   if ( $role eq 'subscriber' ) {
-    eval { $self->replace_subscriber($svc_new, $svc_old) };
+
+    try { $self->replace_subscriber($svc_new, $svc_old) }
+    catch { $error = $_ };
+
   } elsif ( $role eq 'did' ) {
-    eval { $self->replace_did($svc_new, $svc_old) };
+
+    try { $self->export_did($svc_new, $svc_old) }
+    catch { $error = $_ };
+
   }
-  return "$me $@" if $@;
+  return "$me $error" if $error;
   '';
 }
 
 sub export_delete {
   my ($self, $svc_x) = (shift, shift);
   my $role = $self->svc_role($svc_x);
-  local $@;
+  my $error;
+
   if ( $role eq 'subscriber' ) {
 
     # no need to remove DIDs from it, just drop the subscriber record
-    eval { $self->delete_subscriber($svc_x) };
+    try { $self->delete_subscriber($svc_x) }
+    catch { $error = $_ };
 
   } elsif ( $role eq 'did' ) {
 
-    return if !$svc_x->forwarddst;
-    my $subscriber = qsearchs('svc_phone', { phonenum => $svc_x->forwarddst });
-    return if !$subscriber;
-    return if !$self->svc_role($subscriber);
-    eval { $self->delete_did($svc_x, $subscriber) };
+    try { $self->export_did($svc_x) }
+    catch { $error = $_ };
 
   }
-  return "$me $@" if $@;
+  return "$me $error" if $error;
   '';
 }
 
@@ -194,7 +192,7 @@ sub find_or_create_customer {
     ]
   );
   if (!$billing_profile) {
-    croak "can't find billing profile '". $self->option('billing_profile') . "'";
+    die "can't find billing profile '". $self->option('billing_profile') . "'\n";
   }
   my $bpid = $billing_profile->{id};
 
@@ -255,6 +253,41 @@ sub find_or_create_domain {
   );
 }
 
+########
+# DIDS #
+########
+
+=item acct_for_did SVC_PHONE
+
+Returns the subscriber svc_acct linked to SVC_PHONE.
+
+=cut
+
+sub acct_for_did {
+  my $self = shift;
+  my $svc_phone = shift;
+  my $svcnum = $svc_phone->forward_svcnum or return;
+  my $svc_acct = FS::svc_acct->by_key($svcnum) or return;
+  $self->svc_role($svc_acct) eq 'subscriber' or return;
+  $svc_acct;
+}
+
+=item export_did NEW, OLD
+
+Refreshes the subscriber information for the service the DID was linked to
+previously, and the one it's linked to now.
+
+=cut
+
+sub export_did {
+  my $self = shift;
+  my ($new, $old) = @_;
+  if ( $old and $new->forward_svcnum ne $old->forward_svcnum ) {
+    $self->replace_subscriber( $self->acct_for_did($old) );
+  }
+  $self->replace_subscriber( $self->acct_for_did($new) );
+}
+
 ###############
 # SUBSCRIBERS #
 ###############
@@ -270,7 +303,7 @@ sub get_subscriber {
   my $svc = shift;
 
   my $svcnum = $svc->svcnum;
-  my $svcid = "svc_phone#$svcnum";
+  my $svcid = "svc#$svcnum";
 
   my $pkgnum = $svc->cust_svc->pkgnum;
   my $custid = "cust_pkg#$pkgnum";
@@ -291,12 +324,11 @@ sub did_numbers_for_svc {
   my $self = shift;
   my $svc = shift;
   my @numbers;
-  my @possible_dids = qsearch({
+  my @dids = qsearch({
       'table'     => 'svc_phone',
-      'hashref'   => { 'forwarddst' => $svc->phonenum },
-      'order_by'  => ' ORDER BY phonenum'
+      'hashref'   => { 'forward_svcnum' => $svc->svcnum }
   });
-  foreach my $did (@possible_dids) {
+  foreach my $did (@dids) {
     # only include them if they're interesting to this export
     if ( $self->svc_role($did) eq 'did' ) {
       my $phonenum;
@@ -308,7 +340,7 @@ sub did_numbers_for_svc {
         $phonenum = Number::Phone->new($country, $did->phonenum);
       }
       if (!$phonenum) {
-        croak "Can't process phonenum ".$did->countrycode . $did->phonenum;
+        die "Can't process phonenum ".$did->countrycode . $did->phonenum . "\n";
       }
       push @numbers,
         { 'cc' => $phonenum->country_code,
@@ -325,7 +357,7 @@ sub insert_subscriber {
   my $svc = shift;
 
   my $cust = $self->find_or_create_customer($svc);
-  my $svcid = "svc_phone#" . $svc->svcnum;
+  my $svcid = "svc#" . $svc->svcnum;
   my $status = $svc->cust_svc->cust_pkg->susp ? 'locked' : 'active';
   my $domain = $self->find_or_create_domain($svc->domain);
 
@@ -336,14 +368,13 @@ sub insert_subscriber {
     {
       'alias_numbers'   => \@numbers,
       'customer_id'     => $cust->{id},
-      'display_name'    => $svc->phone_name,
+      'display_name'    => $svc->finger,
       'domain_id'       => $domain->{id},
-      'email'           => $svc->email,
       'external_id'     => $svcid,
-      'password'        => $svc->sip_password,
+      'password'        => $svc->_password,
       'primary_number'  => $first_number,
       'status'          => $status,
-      'username'        => $svc->phonenum,
+      'username'        => $svc->username,
     }
   );
 }
@@ -351,8 +382,8 @@ sub insert_subscriber {
 sub replace_subscriber {
   my $self = shift;
   my $svc = shift;
-  my $old = shift;
-  my $svcid = "svc_phone#" . $svc->svcnum;
+  my $old = shift || $svc->replace_old;
+  my $svcid = "svc#" . $svc->svcnum;
 
   my $cust = $self->find_or_create_customer($svc);
   my $status = $svc->cust_svc->cust_pkg->susp ? 'locked' : 'active';
@@ -365,7 +396,7 @@ sub replace_subscriber {
 
   if ( $subscriber ) {
     my $id = $subscriber->{id};
-    if ( $svc->phonenum ne $old->phonenum ) {
+    if ( $svc->username ne $old->username ) {
       # have to delete and recreate
       $self->api_delete("subscribers/$id");
       $self->insert_subscriber($svc);
@@ -374,14 +405,14 @@ sub replace_subscriber {
         {
           'alias_numbers'   => \@numbers,
           'customer_id'     => $cust->{id},
-          'display_name'    => $svc->phone_name,
+          'display_name'    => $svc->finger,
           'domain_id'       => $domain->{id},
           'email'           => $svc->email,
           'external_id'     => $svcid,
-          'password'        => $svc->sip_password,
+          'password'        => $svc->_password,
           'primary_number'  => $first_number,
           'status'          => $status,
-          'username'        => $svc->phonenum,
+          'username'        => $svc->username,
         }
       );
     }
@@ -394,7 +425,7 @@ sub replace_subscriber {
 sub delete_subscriber {
   my $self = shift;
   my $svc = shift;
-  my $svcid = "svc_phone#" . $svc->svcnum;
+  my $svcid = "svc#" . $svc->svcnum;
   my $pkgnum = $svc->cust_svc->pkgnum;
   my $custid = "cust_pkg#$pkgnum";
 
@@ -490,7 +521,7 @@ sub api_create {
   if ( $result->{location} ) {
     return $self->api_request('GET', $result->{location});
   } else {
-    croak $result->{message};
+    die $result->{message} . "\n";
   }
 }
 
@@ -508,7 +539,7 @@ sub api_update {
   my ($endpoint, $content) = @_;
   my $result = $self->api_request('PUT', $endpoint, $content);
   if ( $result->{message} ) {
-    croak $result->{message};
+    die $result->{message} . "\n";
   }
   return;
 }
@@ -529,7 +560,7 @@ sub api_delete {
     warn "$me api_delete $endpoint: does not exist\n";
     return;
   } elsif ( $result->{message} ) {
-    croak $result->{message};
+    die $result->{message} . "\n";
   }
   return;
 }
@@ -591,7 +622,7 @@ sub api_request {
     if ( $@ ) {
       # then it can't be parsed; probably a low-level error of some kind.
       warn "$me Parse error.\n".$response->content."\n\n";
-      croak $response->content;
+      die "$me Parse error:".$response->content . "\n";
     }
   }
   if ( $response->header('Location') ) {
index c2f5d71..67fce41 100644 (file)
@@ -847,6 +847,17 @@ sub delete {
     }
   }
 
+  foreach my $svc_phone (
+    qsearch( 'svc_phone', { 'forward_svcnum' => $self->svcnum })
+  ) {
+    $svc_phone->set('forward_svcnum', '');
+    my $error = $svc_phone->replace;
+    if ( $error ) {
+      $dbh->rollback if $oldAutoCommit;
+      return $error;
+    }
+  }
+
   my $error = $self->delete_password_history
            || $self->SUPER::delete; # usergroup here
   if ( $error ) {
index cf9d9b4..3a58b46 100644 (file)
@@ -93,9 +93,14 @@ Voicemail PIN
 
 Optional svcnum from svc_pbx
 
+=item forward_svcnum
+
+Forward destination, if it's another service. Some exports use this
+configuration.
+
 =item forwarddst
 
-Forwarding destination
+Forwarding destination, if it's not a service.
 
 =item email
 
@@ -225,6 +230,9 @@ sub table_info {
        'forwarddst' => {       label => 'Forward Destination', 
                                %dis2,
                        },
+        'forward_svcnum' => {   label => 'Route to service',
+                                %dis2,
+                            },
        'email' => {            label => 'Email',
                                %dis2,
                    },
@@ -529,7 +537,9 @@ sub check {
     || $self->ut_alphan('sms_account')
     || $self->ut_numbern('max_simultaneous')
     || $self->ut_foreign_keyn('locationnum', 'cust_location', 'locationnum')
-    || $self->ut_numbern('forwarddst')
+    || $self->ut_numbern('forward_svcnum')
+    || $self->ut_foreign_keyn('forward_svcnum', 'cust_svc', 'svcnum')
+    || $self->ut_textn('forwarddst')
     || $self->ut_textn('email')
     || $self->ut_numbern('lrn')
     || $self->ut_numbern('lnp_desired_due_date')
index f1471e2..e74ffbb 100644 (file)
@@ -60,6 +60,9 @@ my $begin_callback = sub {
                 type  => 'text',
                 maxlength => $conf->config('svc_phone-phone_name-max_length'),
               },
+              { field => 'forward_svcnum',
+                type  => 'select-svc_phone-forward',
+              },
              'forwarddst',
              'email',
 
diff --git a/httemplate/elements/tr-select-svc_phone-forward.html b/httemplate/elements/tr-select-svc_phone-forward.html
new file mode 100644 (file)
index 0000000..ef9ef17
--- /dev/null
@@ -0,0 +1,50 @@
+% if ( $hide ) {
+  <tr style="display: none"><td>
+  <INPUT TYPE="hidden" NAME="<% $opt{field} %>" VALUE="<% $opt{curr_value}%>">
+  </td></tr>
+% } else {
+  <& tr-select-table.html,
+    'table'       => 'svc_acct', # for now
+    'name_col'    => 'email',
+    'order_by'    => 'order by username',
+    'empty_label' => ' ',
+    %select_hash,
+    %opt
+  &>
+% } 
+<%init>
+
+my %opt = @_;
+my $pkgnum = $opt{pkgnum};
+my $svcpart = $opt{svcpart};
+
+my $field = $opt{'field'} ||= 'forward_svcnum';
+
+my $part_svc = FS::part_svc->by_key($svcpart);
+# kludgey assumptions for now:
+# - this is only used to route DIDs to their real destinations
+# - which is a svc_acct
+# - in the same package (part_export::svc_with_role assumes this)
+# - and shares an export
+
+my $cust_pkg = FS::cust_pkg->by_key($pkgnum);
+my @svcparts;
+foreach my $part_export ( $part_svc->part_export ) {
+  foreach my $export_svc ( $part_export->export_svc ) {
+    push @svcparts, $export_svc->svcpart;
+  }
+}
+
+$pkgnum =~ /^(\d+)$/ or die "bad pkgnum $pkgnum";
+
+my %select_hash = (
+  'addl_from' => ' JOIN cust_svc USING (svcnum) ',
+  'extra_sql' => "WHERE pkgnum = $pkgnum AND svcpart IN(".
+    join(',', @svcparts) . ")"
+);
+
+my $hide = 0;
+$hide = 1 if $part_svc->part_svc_column($field) eq 'F';
+$hide = 1 if !@svcparts;
+
+</%init>
index ab69c4f..416f138 100644 (file)
@@ -18,6 +18,23 @@ my %labels = map { $_ =>  ( ref($fields->{$_})
 
 my @fields = qw( countrycode phonenum sim_imsi );
 push @fields, 'domain' if $conf->exists('svc_phone-domain');
+
+$labels{forward_svcnum} = mt('Route to service');
+push @fields, { field => 'forward_svcnum',
+                link => [ $p.'view/cust_svc.cgi?', 'forward_svcnum' ],
+                value_callback => sub {
+                  my $self = shift;
+                  if ($self->forward_svcnum) {
+                    my $cust_svc = FS::cust_svc->by_key($self->forward_svcnum);
+                    if ( $cust_svc ) {
+                      return $cust_svc->svc_x->label;
+                    }
+                  }
+                  '';
+                },
+              };
+
+
 push @fields, qw( pbx_title );
 $labels{pbx_title} = 'PBX';