[freeside-commits] branch master updated. cf409374019e51863c71fdce28272ad71e53bfa7

Mark Wells mark at 420.am
Sat Feb 27 20:31:15 PST 2016


The branch, master has been updated
       via  cf409374019e51863c71fdce28272ad71e53bfa7 (commit)
       via  3824fc8dc74f8ec088fc7af810f62ee85afac34c (commit)
       via  fd6aec2041579fbb5d790371c5e5e5152e3075ce (commit)
      from  5ebeeda12a588c1db792fcc8954aeeedb54a84b2 (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 cf409374019e51863c71fdce28272ad71e53bfa7
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Feb 27 20:01:35 2016 -0800

    sipwise export, part 4, #40097

diff --git a/debian/control b/debian/control
index badd99d..d183d24 100644
--- a/debian/control
+++ b/debian/control
@@ -94,7 +94,8 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,latex-xcolor,
  libsymbol-global-name-perl, libdate-extract-perl, librole-basic-perl,
  libhtml-formattext-withlinks-andtables-perl, libcrypt-x509-perl,
  libdata-guid-perl, libparams-classify-perl (>= 0.013-5.1),
- libcpanel-json-xs-perl, libarchive-zip-perl
+ libcpanel-json-xs-perl, libarchive-zip-perl,
+ libtry-tiny-perl, libnumber-phone-perl
 Conflicts: libparams-classify-perl (>= 0.013-6)
 Suggests: libbusiness-onlinepayment-perl
 Description: Libraries for Freeside billing and trouble ticketing

commit 3824fc8dc74f8ec088fc7af810f62ee85afac34c
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Feb 27 19:24:04 2016 -0800

    don't send nonstandard phone numbers to Suretax, #32043

diff --git a/FS/FS/TaxEngine/suretax.pm b/FS/FS/TaxEngine/suretax.pm
index 2c418f9..e18b668 100644
--- a/FS/FS/TaxEngine/suretax.pm
+++ b/FS/FS/TaxEngine/suretax.pm
@@ -216,9 +216,9 @@ sub build_item {
       my %hash = (
         %base_item,
         'LineNumber'      => 'C' . $cdr->acctid,
-        'OrigNumber'      => $cdr->src,
-        'TermNumber'      => $cdr->dst,
-        'BillToNumber'    => $cdr->charged_party,
+        'OrigNumber'      => '',
+        'TermNumber'      => '',
+        'BillToNumber'    => '',
         'TransDate'       => $calldate,
         'Revenue'         => $cdr->rated_price, # 4 decimal places
         'Units'           => 0, # right?
@@ -258,11 +258,13 @@ sub build_item {
         if !$taxproduct;
 
     my $tsr = $TSR_GENERAL;
+    # when billing on cancellation there are no units
+    my $units = $self->{cancel} ? 0 : $cust_bill_pkg->units;
     my %hash = (
       %base_item,
       'LineNumber'      => 'R' . $billpkgnum,
       'Revenue'         => $recur_without_usage, # 4 decimal places
-      'Units'           => $cust_bill_pkg->units,
+      'Units'           => $units,
       'TaxSitusRule'    => $tsr,
       'TransTypeCode'   => $taxproduct,
     );
diff --git a/FS/FS/part_export/sipwise.pm b/FS/FS/part_export/sipwise.pm
index 5cbe89c..8fec013 100644
--- a/FS/FS/part_export/sipwise.pm
+++ b/FS/FS/part_export/sipwise.pm
@@ -27,7 +27,7 @@ tie my %options, 'Tie::IxHash',
     default           => 'default',
   },
   'subscriber_profile_set' => {
-    label             => 'Subscriber profile set name',
+    label             => 'Subscriber profile set name (optional)',
   },
   'reseller_id'     => { label => 'Reseller ID' },
   'ssl_no_verify'   => { label => 'Skip SSL certificate validation',
@@ -63,8 +63,6 @@ our %info = (
     will receive calls at this number.
   </OL>
 </P>
-<P>Export options:
-</P>
 END
 );
 

commit fd6aec2041579fbb5d790371c5e5e5152e3075ce
Author: Mark Wells <mark at freeside.biz>
Date:   Sat Feb 27 19:13:54 2016 -0800

    sipwise export, part 3, #40097

diff --git a/FS/FS/part_export/sipwise.pm b/FS/FS/part_export/sipwise.pm
index d2af7da..5cbe89c 100644
--- a/FS/FS/part_export/sipwise.pm
+++ b/FS/FS/part_export/sipwise.pm
@@ -15,7 +15,7 @@ use Number::Phone;
 use Try::Tiny;
 
 our $me = '[sipwise]';
-our $DEBUG = 2;
+our $DEBUG = 0;
 
 tie my %options, 'Tie::IxHash',
   'port'            => { label => 'Port' },
@@ -23,8 +23,11 @@ tie my %options, 'Tie::IxHash',
   'password'        => { label => 'API password', },
   'debug'           => { label => 'Enable debugging', type => 'checkbox', value => 1 },
   'billing_profile' => {
-    label             => 'Billing profile',
-    default           => 'default', # that's what it's called
+    label             => 'Billing profile handle',
+    default           => 'default',
+  },
+  'subscriber_profile_set' => {
+    label             => 'Subscriber profile set name',
   },
   'reseller_id'     => { label => 'Reseller ID' },
   'ssl_no_verify'   => { label => 'Skip SSL certificate validation',
@@ -68,6 +71,7 @@ END
 sub export_insert {
   my($self, $svc_x) = (shift, shift);
 
+  local $SIG{__DIE__};
   my $error;
   my $role = $self->svc_role($svc_x);
   if ( $role eq 'subscriber' ) {
@@ -87,6 +91,8 @@ sub export_insert {
 
 sub export_replace {
   my ($self, $svc_new, $svc_old) = @_;
+  local $SIG{__DIE__};
+
   my $role = $self->svc_role($svc_new);
 
   my $error;
@@ -107,6 +113,8 @@ sub export_replace {
 
 sub export_delete {
   my ($self, $svc_x) = (shift, shift);
+  local $SIG{__DIE__};
+
   my $role = $self->svc_role($svc_x);
   my $error;
 
@@ -126,14 +134,17 @@ sub export_delete {
   '';
 }
 
-# XXX NOT DONE YET
+# logic to set subscribers to locked/active is in replace_subscriber
+
 sub export_suspend {
   my $self = shift;
   my $svc_x = shift;
   my $role = $self->svc_role($svc_x);
-  return if $role ne 'subacct'; # can't suspend DIDs directly
-
-  my $error = $self->replace_subacct($svc_x, $svc_x); # will disable it
+  my $error;
+  if ( $role eq 'subscriber' ) {
+    try { $self->replace_subscriber($svc_x, $svc_x) }
+    catch { $error = $_ };
+  }
   return "$me $error" if $error;
   '';
 }
@@ -142,10 +153,12 @@ sub export_unsuspend {
   my $self = shift;
   my $svc_x = shift;
   my $role = $self->svc_role($svc_x);
-  return if $role ne 'subacct'; # can't suspend DIDs directly
-
-  $svc_x->set('unsuspended', 1); # hack to tell replace_subacct to do it
-  my $error = $self->replace_subacct($svc_x, $svc_x); #same
+  my $error;
+  if ( $role eq 'subscriber' ) {
+    $svc_x->set('unsuspended', 1);
+    try { $self->replace_subscriber($svc_x, $svc_x) }
+    catch { $error = $_ };
+  }
   return "$me $error" if $error;
   '';
 }
@@ -182,6 +195,7 @@ sub find_or_create_customer {
   my $cust_main = $cust_pkg->cust_main;
   my $cust_location = $cust_pkg->cust_location;
   my ($email) = $cust_main->invoicing_list_emailonly;
+  die "Customer contact email required\n" if !$email;
   my $custid = 'cust_pkg#' . $cust_pkg->pkgnum;
 
   # find the billing profile
@@ -283,9 +297,11 @@ 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) );
+    my $old_svc_acct = $self->acct_for_did($old);
+    $self->replace_subscriber( $old_svc_acct ) if $old_svc_acct;
   }
-  $self->replace_subscriber( $self->acct_for_did($new) );
+  my $new_svc_acct = $self->acct_for_did($new);
+  $self->replace_subscriber( $new_svc_acct ) if $new_svc_acct;
 }
 
 ###############
@@ -352,6 +368,18 @@ sub did_numbers_for_svc {
   @numbers;
 }
 
+sub get_subscriber_profile_set_id {
+  my $self = shift;
+  if ( my $setname = $self->option('subscriber_profile_set') ) {
+    my ($set) = $self->api_query('subscriberprofilesets',
+      [ name => $setname ]
+    );
+    die "Subscriber profile set '$setname' not found" unless $set;
+    return $set->{id};
+  }
+  '';
+}
+
 sub insert_subscriber {
   my $self = shift;
   my $svc = shift;
@@ -359,11 +387,13 @@ sub insert_subscriber {
   my $cust = $self->find_or_create_customer($svc);
   my $svcid = "svc#" . $svc->svcnum;
   my $status = $svc->cust_svc->cust_pkg->susp ? 'locked' : 'active';
+  $status = 'active' if $svc->get('unsuspended');
   my $domain = $self->find_or_create_domain($svc->domain);
 
   my @numbers = $self->did_numbers_for_svc($svc);
   my $first_number = shift @numbers;
 
+  my $profile_set_id = $self->get_subscriber_profile_set_id;
   my $subscriber = $self->api_create('subscribers',
     {
       'alias_numbers'   => \@numbers,
@@ -373,6 +403,7 @@ sub insert_subscriber {
       'external_id'     => $svcid,
       'password'        => $svc->_password,
       'primary_number'  => $first_number,
+      'profile_set_id'  => $profile_set_id,
       'status'          => $status,
       'username'        => $svc->username,
     }
@@ -387,6 +418,7 @@ sub replace_subscriber {
 
   my $cust = $self->find_or_create_customer($svc);
   my $status = $svc->cust_svc->cust_pkg->susp ? 'locked' : 'active';
+  $status = 'active' if $svc->get('unsuspended');
   my $domain = $self->find_or_create_domain($svc->domain);
   
   my @numbers = $self->did_numbers_for_svc($svc);
@@ -401,6 +433,7 @@ sub replace_subscriber {
       $self->api_delete("subscribers/$id");
       $self->insert_subscriber($svc);
     } else {
+      my $profile_set_id = $self->get_subscriber_profile_set_id;
       $self->api_update("subscribers/$id",
         {
           'alias_numbers'   => \@numbers,
@@ -411,6 +444,7 @@ sub replace_subscriber {
           'external_id'     => $svcid,
           'password'        => $svc->_password,
           'primary_number'  => $first_number,
+          'profile_set_id'  => $profile_set_id,
           'status'          => $status,
           'username'        => $svc->username,
         }
@@ -462,6 +496,124 @@ sub delete_subscriber {
   }
 }
 
+################
+# CALL DETAILS #
+################
+
+=item import_cdrs START, END
+
+Retrieves CDRs for calls in the date range from START to END and inserts them
+as a CDR batch. On success, returns a new cdr_batch object. On failure,
+returns an error message. If there are no new CDRs, returns nothing.
+
+=cut
+
+sub import_cdrs {
+  my ($self, $start, $end) = @_;
+  $start ||= 0;
+  $end ||= time;
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  
+  ($start, $end) = ($end, $start) if $end < $start;
+  $start = DateTime->from_epoch(epoch => $start, time_zone => 'local');
+  $end = DateTime->from_epoch(epoch => $end, time_zone => 'local');
+  $end->subtract(seconds => 1); # filter by >= and <= only, so...
+
+  # a little different from the usual: we have to fetch these subscriber by
+  # subscriber, not all at once.
+  my @svcs = qsearch({
+      'table'     => 'svc_acct',
+      'addl_from' => ' JOIN cust_svc USING (svcnum)' .
+                     ' JOIN export_svc USING (svcpart)',
+      'extra_sql' => ' WHERE export_svc.role = \'subscriber\''.
+                     ' AND export_svc.exportnum = '.$self->exportnum
+  });
+  my $cdr_batch;
+  my @args = ( 'start_ge' => $start->iso8601,
+               'start_le' => $end->iso8601,
+              );
+
+  my $error;
+  SVC: foreach my $svc (@svcs) {
+    my $subscriber = $self->get_subscriber($svc);
+    if (!$subscriber) {
+      warn "$me user ".$svc->label." is not configured on the SIP server.\n";
+      next;
+    }
+    my $id = $subscriber->{id};
+    my @calls;
+    try {
+      # alias_field tells "calllists" which field from the source and
+      # destination to use as the "own_cli" and "other_cli" of the call.
+      # "user" = username at domain.
+      @calls = $self->api_query('calllists', [
+          'subscriber_id' => $id,
+          'alias_field'   => 'user',
+          @args
+      ]);
+    } catch {
+      $error = "$me $_ (retrieving records for ".$svc->label.")";
+    };
+
+    if (@calls and !$cdr_batch) {
+      # create a cdr_batch if needed
+      my $cdrbatchname = 'sipwise-' . $self->exportnum . '-' . $end->epoch;
+      $cdr_batch = FS::cdr_batch->new({ cdrbatch => $cdrbatchname });
+      $error = $cdr_batch->insert;
+    }
+
+    last SVC if $error;
+
+    foreach my $c (@calls) {
+      # avoid double-importing
+      my $uniqueid = $c->{call_id};
+      if ( FS::cdr->row_exists("uniqueid = ?", $uniqueid) ) {
+        warn "skipped call with uniqueid = '$uniqueid' (already imported)\n"
+          if $DEBUG;
+        next;
+      }
+      my $src = $c->{own_cli};
+      my $dst = $c->{other_cli};
+      if ( $c->{direction} eq 'in' ) { # then reverse them
+        ($src, $dst) = ($dst, $src);
+      }
+      # parse duration from H:MM:SS format
+      my $duration;
+      if ( $c->{duration} =~ /^(\d+):(\d+):(\d+)$/ ) {
+        $duration = $3 + (60 * $2) + (3600 * $1);
+      } else {
+        $error = "call $uniqueid: unparseable duration '".$c->{duration}."'";
+      }
+
+      # use the username at domain label for src and/or dst if possible
+      my $cdr = FS::cdr->new({
+          uniqueid        => $uniqueid,
+          upstream_price  => $c->{customer_cost},
+          startdate       => parse_datetime($c->{start_time}),
+          disposition     => $c->{status},
+          duration        => $duration,
+          billsec         => $duration,
+          src             => $src,
+          dst             => $dst,
+      });
+      $error ||= $cdr->insert;
+      last SVC if $error;
+    }
+  } # foreach $svc
+
+  if ( $error ) {
+    dbh->rollback if $oldAutoCommit;
+    return $error;
+  } elsif ( $cdr_batch ) {
+    dbh->commit if $oldAutoCommit;
+    return $cdr_batch;
+  } else { # no CDRs
+    return;
+  }
+}
+
 ##############
 # API ACCESS #
 ##############
@@ -484,6 +636,8 @@ sub api_query {
   if ( ref $content eq 'HASH' ) {
     $content = [ %$content ];
   }
+  my $page = 1;
+  push @$content, ('rows' => 100, 'page' => 1); # 'page' is always last
   my $result = $self->api_request('GET', $resource, $content);
   my @records;
   # depaginate
@@ -494,8 +648,14 @@ sub api_query {
       push @records, $things;
     }
     if ( my $linknext = $result->{_links}{next} ) {
-      warn "$me continued at $linknext\n" if $DEBUG;
-      $result = $self->api_request('GET', $linknext);
+      # unfortunately their HAL isn't entirely functional
+      # it returns "next" links that contain "page" and "rows" but no other
+      # parameters. so just count the pages:
+      $page++;
+      $content->[-1] = $page;
+
+      warn "$me continued: $page\n" if $DEBUG;
+      $result = $self->api_request('GET', $resource, $content);
     } else {
       last;
     }
diff --git a/FS/FS/svc_acct.pm b/FS/FS/svc_acct.pm
index 67fce41..8283e6f 100644
--- a/FS/FS/svc_acct.pm
+++ b/FS/FS/svc_acct.pm
@@ -2319,8 +2319,8 @@ sub last_login_text {
 
 Returns a paged search (L<FS::PagedSearch>) for Call Detail Records
 associated with this service. For svc_acct, "associated with" means that
-either the "src" or the "charged_party" field of the CDR matches the
-"username" field of the service.
+either the "src" or the "charged_party" field of the CDR matches either
+the "username" field of the service or the username at domain label.
 
 =cut
 
@@ -2331,6 +2331,7 @@ sub psearch_cdrs {
   my @where;
 
   my $did = dbh->quote($self->username);
+  my $diddomain = dbh->quote($self->label);
 
   my $prefix = $options{'default_prefix'} || ''; #convergent.au '+61'
   my $prefixdid = dbh->quote($prefix . $self->username);
@@ -2346,12 +2347,16 @@ sub psearch_cdrs {
   if (!$options{'disable_charged_party'}) {
     push @orwhere,
       "charged_party = $did",
-      "charged_party = $prefixdid";
+      "charged_party = $prefixdid",
+      "charged_party = $diddomain"
+      ;
   }
   if (!$options{'disable_src'}) {
     push @orwhere,
       "src = $did AND charged_party IS NULL",
-      "src = $prefixdid AND charged_party IS NULL";
+      "src = $prefixdid AND charged_party IS NULL",
+      "src = $diddomain AND charged_party IS NULL"
+      ;
   }
   push @where, '(' . join(' OR ', @orwhere) . ')';
 

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

Summary of changes:
 FS/FS/TaxEngine/suretax.pm   |   10 ++-
 FS/FS/part_export/sipwise.pm |  192 ++++++++++++++++++++++++++++++++++++++----
 FS/FS/svc_acct.pm            |   13 ++-
 debian/control               |    3 +-
 4 files changed, 192 insertions(+), 26 deletions(-)




More information about the freeside-commits mailing list