[freeside-commits] branch master updated. cbfeb5f6b7490f78361318ce6290bfb442dbfcbe

Ivan ivan at 420.am
Fri Aug 25 15:06:27 PDT 2017


The branch, master has been updated
       via  cbfeb5f6b7490f78361318ce6290bfb442dbfcbe (commit)
       via  8ffd7de981603a189cd0ea62ca948eaf3f66ca49 (commit)
       via  f2ed83fce519082408daf72c1a29d0bea7127abd (commit)
       via  e42acf0982119c844bf1cf67d93ccfcebdf445ae (commit)
       via  33b9c827617bb2cd71772dfd32a1ac3902532c36 (commit)
       via  803963e6151554f6087e2e1bc58f43ec8097e5a1 (commit)
       via  df2d2eb21e229b59bb3fbff9b41b7e6e051135de (commit)
       via  ac8d0caed938fb4dda9340e83397cb8daed43a8a (commit)
       via  f9ecf57e5bba89dbbc6f39b9b336a8a5549e1352 (commit)
       via  3a5a4f402f8a51fc5c3da7017150491a9a0afeb0 (commit)
       via  1fa46a8e2890c2c6f1e0117856f5c481306b1c59 (commit)
       via  b0d572bc74c5924ad73d247548d8227e06a58819 (commit)
       via  2c856415e89e7eb8a6cc18885c4fca20087aa5ce (commit)
       via  4f0b72e1e69fcd5aa4351d5f151be06b3909f74b (commit)
       via  d4496963925b57f117bdf336661e4cc33ce25c9d (commit)
       via  7b9fd8e2fbea349fe02cadd7141e47197ab60a92 (commit)
       via  7f7a4ecece296b4c0ff81732d3cffa4a7951ae37 (commit)
       via  352650e920955b22a2468c54633d9e4c5cfaff84 (commit)
       via  f9585a8a603cf4a83d740cf2980ef7a84a123335 (commit)
       via  c13da0ff08d1a691f8b4613e001b807472dd1d3c (commit)
       via  0f4da1d19bdb76f2b030d6ae961161426d11716a (commit)
       via  afed6387462fb38675f08a5840c632a405b01148 (commit)
       via  b4be57727f8d712e6508744cb5db1e5f47479f9d (commit)
       via  39cf034d6ae1c20de21028933aff05cae6dc4f77 (commit)
       via  79c995c1e0f66d333bc7738894c0b2359489078f (commit)
      from  5b5eb87bf66f1fac003a13dc2db48e8970c5c986 (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 cbfeb5f6b7490f78361318ce6290bfb442dbfcbe
Merge: 8ffd7de 5b5eb87
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 15:06:19 2017 -0700

    Merge branch 'master' of git.freeside.biz:/home/git/freeside


commit 8ffd7de981603a189cd0ea62ca948eaf3f66ca49
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 15:06:17 2017 -0700

    force employee logouts, employee session reports, RT#74953

diff --git a/FS/t/access_user_session_log.t b/FS/t/access_user_session_log.t
new file mode 100644
index 0000000..6306374
--- /dev/null
+++ b/FS/t/access_user_session_log.t
@@ -0,0 +1,5 @@
+BEGIN { $| = 1; print "1..1\n" }
+END {print "not ok 1\n" unless $loaded;}
+use FS::access_user_session_log;
+$loaded=1;
+print "ok 1\n";

commit f2ed83fce519082408daf72c1a29d0bea7127abd
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 15:06:07 2017 -0700

    force employee logouts, employee session reports, RT#74953

diff --git a/FS/FS/access_user_session_log.pm b/FS/FS/access_user_session_log.pm
new file mode 100644
index 0000000..d28ec85
--- /dev/null
+++ b/FS/FS/access_user_session_log.pm
@@ -0,0 +1,124 @@
+package FS::access_user_session_log;
+use base qw( FS::Record );
+
+use strict;
+#use FS::Record qw( qsearch qsearchs );
+
+=head1 NAME
+
+FS::access_user_session_log - Object methods for access_user_session_log records
+
+=head1 SYNOPSIS
+
+  use FS::access_user_session_log;
+
+  $record = new FS::access_user_session_log \%hash;
+  $record = new FS::access_user_session_log { 'column' => 'value' };
+
+  $error = $record->insert;
+
+  $error = $new_record->replace($old_record);
+
+  $error = $record->delete;
+
+  $error = $record->check;
+
+=head1 DESCRIPTION
+
+An FS::access_user_session_log object represents an log of an employee session.
+FS::access_user_session_log inherits from FS::Record.  The following fields
+are currently supported:
+
+=over 4
+
+=item sessionlognum
+
+primary key
+
+=item usernum
+
+usernum
+
+=item start_date
+
+start_date
+
+=item last_date
+
+last_date
+
+=item logout_date
+
+logout_date
+
+=item logout_type
+
+logout_type
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item new HASHREF
+
+Creates a new log entry.  To add the entry to the database, see L<"insert">.
+
+Note that this stores the hash reference, not a distinct copy of the hash it
+points to.  You can ask the object for a copy with the I<hash> method.
+
+=cut
+
+sub table { 'access_user_session_log'; }
+
+=item insert
+
+Adds this record to the database.  If there is an error, returns the error,
+otherwise returns false.
+
+=item delete
+
+Delete this record from the database.
+
+=item replace OLD_RECORD
+
+Replaces the OLD_RECORD with this one in the database.  If there is an error,
+returns the error, otherwise returns false.
+
+=item check
+
+Checks all fields to make sure this is a valid log entry.  If there is
+an error, returns the error, otherwise returns false.  Called by the insert
+and replace methods.
+
+=cut
+
+sub check {
+  my $self = shift;
+
+  my $error = 
+    $self->ut_number('usernum')
+    || $self->ut_numbern('start_date')
+    || $self->ut_numbern('last_date')
+    || $self->ut_numbern('logout_date')
+    || $self->ut_text('logout_type')
+  ;
+  return $error if $error;
+
+  $self->SUPER::check;
+}
+
+=back
+
+=head1 BUGS
+
+=head1 SEE ALSO
+
+L<FS::Record>
+
+=cut
+
+1;
+

commit e42acf0982119c844bf1cf67d93ccfcebdf445ae
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 15:05:35 2017 -0700

    force employee logouts, employee session reports, RT#74953

diff --git a/FS/FS/Conf.pm b/FS/FS/Conf.pm
index d41cc74..e2ebd09 100644
--- a/FS/FS/Conf.pm
+++ b/FS/FS/Conf.pm
@@ -5788,8 +5788,8 @@ and customer address. Include units.',
 
   {
     'key'         => 'logout-timeout',
-    'section'     => 'UI',
-    'description' => 'If set, automatically log users out of the backoffice after this many minutes.',
+    'section'     => 'deprecated',
+    'description' => 'Deprecated.  Used to automatically log users out of the backoffice after this many minutes.  Set session timeouts in employee groups instead.',
     'type'       => 'text',
   },
   

commit 33b9c827617bb2cd71772dfd32a1ac3902532c36
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 15:04:54 2017 -0700

    force employee logouts, employee session reports, RT#74953

diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm
index 9649e5e..9cccb01 100644
--- a/FS/FS/AccessRight.pm
+++ b/FS/FS/AccessRight.pm
@@ -329,7 +329,7 @@ tie my %rights, 'Tie::IxHash',
     'Usage: Unrateable CDRs',
     'Usage: Time worked',
     #gone in 4.x as a distinct ACL (for now?) { rightname=>'Employees: Commission Report', global=>1 },
-    { rightname=>'Employees: Audit Report', global=>1 },
+    { rightname=>'Employee Reports', global=>1 },
 
     #{ rightname => 'List customers of all agents', global=>1 },
   ],
diff --git a/FS/FS/access_right.pm b/FS/FS/access_right.pm
index 29c91b0..3926faf 100644
--- a/FS/FS/access_right.pm
+++ b/FS/FS/access_right.pm
@@ -155,6 +155,7 @@ sub _upgrade_data { # class method
     'Refund payment'  => [ 'Refund credit card payment', 'Refund Echeck payment' ],
     'Regular void'    => [ 'Void payments' ],
     'Unvoid'          => [ 'Unvoid payments', 'Unvoid invoices' ],
+    'Employees: Audit Report' => [ 'Employee Reports' ],
   );
 
   foreach my $oldright (keys %migrate) {
@@ -233,9 +234,7 @@ sub _upgrade_data { # class method
                             'Usage: Unrateable CDRs',
                           ],
     'Provision customer service' => [ 'Edit password' ],
-    'Financial reports' => [ 'Employees: Commission Report',
-                             'Employees: Audit Report',
-                           ],
+    'Financial reports' => 'Employee Reports',
     'Change customer package' => 'Detach customer package',
     'Services: Accounts' => 'Services: Cable Subscribers',
     'Bulk change customer packages' => 'Bulk move customer services',
diff --git a/httemplate/elements/menu.html b/httemplate/elements/menu.html
index 58a7d57..cadbd86 100644
--- a/httemplate/elements/menu.html
+++ b/httemplate/elements/menu.html
@@ -428,8 +428,6 @@ $report_logs{'Billing events'} =  [ $fsurl.'search/report_cust_event.html', 'Sea
   if $curuser->access_right('Billing event reports');
 $report_logs{'Credit limit incidents'} = [ $fsurl.'search/report_cust_main_credit_limit.html', '' ]
   if $curuser->access_right('List rating data');
-$report_logs{'Employee activity'} = [ $fsurl.'search/report_employee_audit.html', '' ]
-  if $curuser->access_right('Employees: Audit Report');
 $report_logs{'System log'} = [ $fsurl.'search/log.html', 'View system events and debugging information.' ],
   if $curuser->access_right('View system logs')
   || $curuser->access_right('Configuration');
@@ -437,6 +435,12 @@ $report_logs{'Outgoing messages'} = [ $fsurl.'search/cust_msg.html', 'View outgo
   if $curuser->access_right('View email logs')
   || $curuser->access_right('Configuration');
 
+tie my %report_employee, 'Tie::IxHash',
+  'Employee activity' => [ $fsurl.'search/report_employee_audit.html', '' ],
+  'Employee sessions' => [ $fsurl.'search/report_access_user_session_log.html', '' ],
+  'Access log statistics' => [ $fsurl.'search/report_access_user_log.html?group_by=path', '' ],
+;
+
 tie my %report_menu, 'Tie::IxHash';
 $report_menu{'Saved searches'} = [ \%report_saved_searches, 'My saved searches' ]
   if keys(%report_saved_searches);
@@ -475,6 +479,8 @@ $report_menu{'Financial (Receivables)'} = [ \%report_financial, 'Financial repor
 $report_menu{'Financial (Payables)'} = [ \%report_payable, 'Financial reports (Payables)' ]
   if $curuser->access_right('Financial reports');
 
+$report_menu{'Employees'}      = [ \%report_employee, 'Employee reports' ]
+  if $curuser->access_right('Employee Reports');
 $report_menu{'Logs'}           = [ \%report_logs, 'System and email logs' ]
   if (keys %report_logs); # empty if the user has no rights to it
 $report_menu{'SQL Query'}      = [ $fsurl.'search/report_sql.html', 'SQL Query']
@@ -554,8 +560,6 @@ $tools_system{'Status'} = [ $fsurl.'view/Status.html', 'System status' ]
   if $curuser->access_right('Configuration'); # 'View system status');
 $tools_system{'Job Queue'} =  [ $fsurl.'search/queue.html', 'View pending job queue' ]
   if $curuser->access_right('Job queue');
-$tools_system{'Access log statistics'} = [ $fsurl.'search/report_access_user_log.html?group_by=path', '' ]
-  if $curuser->access_right('Configuration'); # 'View profiling data');
 
 tie my %tools_menu, 'Tie::IxHash', ();
 $tools_menu{'Customers'} = [ \%tools_customers, 'Customer tools' ]
diff --git a/httemplate/search/employee_audit.html b/httemplate/search/employee_audit.html
index 2bc6ff4..991758c 100644
--- a/httemplate/search/employee_audit.html
+++ b/httemplate/search/employee_audit.html
@@ -7,7 +7,7 @@
 <%init>
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report');
+  unless $FS::CurrentUser::CurrentUser->access_right('Employee Reports');
 
 my %tables = (
     cust_pay        => 'Payments',
diff --git a/httemplate/search/report_access_user_log.html b/httemplate/search/report_access_user_log.html
index 0c8acb3..d43c742 100644
--- a/httemplate/search/report_access_user_log.html
+++ b/httemplate/search/report_access_user_log.html
@@ -27,7 +27,7 @@
 <%init>
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+  unless $FS::CurrentUser::CurrentUser->access_right('Employee Reports');
 
 my $group_by = '';
 if ( $cgi->param('group_by') =~ /^(\w+)$/ ) {
diff --git a/httemplate/search/report_employee_audit.html b/httemplate/search/report_employee_audit.html
index 461849b..6008e1c 100644
--- a/httemplate/search/report_employee_audit.html
+++ b/httemplate/search/report_employee_audit.html
@@ -23,7 +23,7 @@
 <%init>
 
 die "access denied"
-  unless $FS::CurrentUser::CurrentUser->access_right('Employees: Audit Report');
+  unless $FS::CurrentUser::CurrentUser->access_right('Employee Reports');
 
 my %tables = (
     cust_pay        => 'Payments',

commit 803963e6151554f6087e2e1bc58f43ec8097e5a1
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 12:41:05 2017 -0700

    restore void tooltip on v4, RT#77279

diff --git a/httemplate/view/cust_main/payment_history/payment.html b/httemplate/view/cust_main/payment_history/payment.html
index 6241f11..6402383 100644
--- a/httemplate/view/cust_main/payment_history/payment.html
+++ b/httemplate/view/cust_main/payment_history/payment.html
@@ -180,17 +180,18 @@ if (    $cust_pay->closed !~ /^Y/i
 }
 
 my $void = '';
-# note: "TOKN" is not yet supported in stock freeside
-my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK|TOKN)$/
+my $voidmsg = $cust_pay->payby =~ /^(CARD|CHEK)$/
               ? ' (' . emt('do not send anything to the payment gateway').')'
               : '';
 $void = ' ('.
-               include( '/elements/popup_link.html',
-                    'label'    => emt('void'),
-                    'action'   => "${p}misc/void-cust_pay.html?".$cust_pay->paynum,
-                    'actionlabel' => emt('Void payment'),
-                ).
-          ')'
+           include( '/elements/popup_link.html',
+                'label'    => emt('void'),
+                'action'   => "${p}misc/void-cust_pay.html?".$cust_pay->paynum,
+                'actionlabel' => emt('Void payment'),
+                'title'       => emt('Void this payment from the database').
+                                 $voidmsg,
+           ).
+         ')'
   if $cust_pay->closed !~ /^Y/i
   && (    ( $cust_pay->payby eq 'CARD'          && $opt{'Credit card void'} )
        || ( $cust_pay->payby eq 'CHEK'          && $opt{'Echeck void'}      )

commit df2d2eb21e229b59bb3fbff9b41b7e6e051135de
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 10:40:26 2017 -0700

    fix 4.x cust_payby vs legacy customer import, RT#77221

diff --git a/FS/FS/cust_main/Import.pm b/FS/FS/cust_main/Import.pm
index f9b167c..9624529 100644
--- a/FS/FS/cust_main/Import.pm
+++ b/FS/FS/cust_main/Import.pm
@@ -410,6 +410,8 @@ sub batch_import {
 
       if ( $cust_main{'payinfo'} =~ /^\s*(\d+\@[\d\.]+)\s*$/ ) {
 
+        delete $cust_main{'payinfo'};
+
         $cust_payby = new FS::cust_payby {
           'payby'   => 'CHEK',
           'payinfo' => $1,
@@ -417,9 +419,14 @@ sub batch_import {
 
       } elsif ($cust_main{'payinfo'} =~ /^\s*([AD]?)(.*)\s*$/) {
 
+        delete $cust_main{'payinfo'};
+
         $cust_payby = new FS::cust_payby {
           'payby'   => ($1 eq 'D') ? 'DCRD' : 'CARD',
           'payinfo' => $2,
+          'paycvv'  => delete $cust_main{'paycvv'},
+          'paydate' => delete $cust_main{'paydate'},
+          'payname' => $cust_main{'first'}. ' '. $cust_main{'last'},
         };
 
       }
@@ -504,7 +511,7 @@ sub batch_import {
     }
 
     my %options = ('invoicing_list' => $invoicing_list);
-    $options{'cust_payby'} = $cust_payby if $cust_payby;
+    $options{'cust_payby'} = [ $cust_payby ] if $cust_payby;
 
     my $error = $cust_main->insert( \%hash, %options );
 

commit ac8d0caed938fb4dda9340e83397cb8daed43a8a
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Fri Aug 25 10:05:37 2017 -0700

    fix 4.x cust_payby vs legacy customer import, RT#77221

diff --git a/FS/FS/cust_main/Import.pm b/FS/FS/cust_main/Import.pm
index 6464761..f9b167c 100644
--- a/FS/FS/cust_main/Import.pm
+++ b/FS/FS/cust_main/Import.pm
@@ -325,6 +325,7 @@ sub batch_import {
     my %svc_x = ();
     my %bill_location = ();
     my %ship_location = ();
+    my $cust_payby = '';
     foreach my $field ( @fields ) {
 
       if ( $field =~ /^cust_pkg\.(pkgpart|setup|bill|susp|adjourn|expire|cancel)$/ ) {
@@ -409,17 +410,17 @@ sub batch_import {
 
       if ( $cust_main{'payinfo'} =~ /^\s*(\d+\@[\d\.]+)\s*$/ ) {
 
-        $cust_main{'payby'}   = 'CHEK';
-        $cust_main{'payinfo'} = $1;
+        $cust_payby = new FS::cust_payby {
+          'payby'   => 'CHEK',
+          'payinfo' => $1,
+        };
 
-      } else {
-
-        $cust_main{'payby'} = 'CARD';
+      } elsif ($cust_main{'payinfo'} =~ /^\s*([AD]?)(.*)\s*$/) {
 
-        if ($cust_main{'payinfo'} =~ /^\s*([AD]?)(.*)\s*$/) {
-          $cust_main{'payby'} = 'DCRD' if $1 eq 'D';
-          $cust_main{'payinfo'} = $2;
-        }
+        $cust_payby = new FS::cust_payby {
+          'payby'   => ($1 eq 'D') ? 'DCRD' : 'CARD',
+          'payinfo' => $2,
+        };
 
       }
 
@@ -502,7 +503,10 @@ sub batch_import {
       $hash{$cust_pkg} = \@svc_x;
     }
 
-    my $error = $cust_main->insert( \%hash, $invoicing_list );
+    my %options = ('invoicing_list' => $invoicing_list);
+    $options{'cust_payby'} = $cust_payby if $cust_payby;
+
+    my $error = $cust_main->insert( \%hash, %options );
 
     if ( $error ) {
       $dbh->rollback if $oldAutoCommit;

commit f9ecf57e5bba89dbbc6f39b9b336a8a5549e1352
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Aug 23 11:57:28 2017 -0700

    correctly parse error response from e911 provisioning, RT#76262

diff --git a/FS/FS/part_export/vitelity.pm b/FS/FS/part_export/vitelity.pm
index 332e457..51bb0aa 100644
--- a/FS/FS/part_export/vitelity.pm
+++ b/FS/FS/part_export/vitelity.pm
@@ -425,7 +425,7 @@ sub e911_send {
 
   my $e911_result = $self->vitelity_command('e911send', %e911send);
 
-  unless ( $e911_result =~ /^(missingdata|invalid)/i ) {
+  unless ( $e911_result =~ /status=(missingdata|invalid)/i ) {
     warn "Vitelity response: $e911_result" if $self->option('debug');
     return '';
   }

commit 3a5a4f402f8a51fc5c3da7017150491a9a0afeb0
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Aug 19 15:49:08 2017 -0700

    expect-style ssh interaction, for interation w/cisco and other networking eqipment, RT#77180

diff --git a/FS/FS/part_export/broadband_shellcommands.pm b/FS/FS/part_export/broadband_shellcommands.pm
index 44280a2..d3e495c 100644
--- a/FS/FS/part_export/broadband_shellcommands.pm
+++ b/FS/FS/part_export/broadband_shellcommands.pm
@@ -70,7 +70,18 @@ sub _export_command {
   my $command = $self->option($action);
   return '' if $command =~ /^\s*$/;
 
-  #set variables for the command
+  my $command_string = $self->_export_subvars( $svc_broadband, $command );
+
+  $self->shellcommands_queue( $svc_broadband->svcnum,
+    user    => $self->option('user')||'root',
+    host    => $self->machine,
+    command => $command_string,
+  );
+}
+
+sub _export_subvars {
+  my( $self, $svc_broadband, $command ) = @_;
+
   no strict 'vars';
   {
     no strict 'refs';
@@ -85,20 +96,25 @@ sub _export_command {
   $locationnum = $cust_pkg ? $cust_pkg->locationnum : '';
   $custnum = $cust_pkg ? $cust_pkg->custnum : '';
 
-  #done setting variables for the command
+  eval(qq("$command"));
+}
 
-  $self->shellcommands_queue( $svc_broadband->svcnum,
+sub _export_replace {
+  my($self, $new, $old ) = (shift, shift, shift);
+  my $command = $self->option('replace');
+
+  my $command_string = $self->_export_subvars_replace( $new, $old, $command );
+
+  $self->shellcommands_queue( $new->svcnum,
     user    => $self->option('user')||'root',
     host    => $self->machine,
-    command => eval(qq("$command")),
+    command => $command_string,
   );
 }
 
-sub _export_replace {
-  my($self, $new, $old ) = (shift, shift, shift);
-  my $command = $self->option('replace');
+sub _export_subvars_replace {
+  my( $self, $new, $old, $command ) = @_;
 
-  #set variable for the command
   no strict 'vars';
   {
     no strict 'refs';
@@ -120,15 +136,10 @@ sub _export_replace {
   $new_locationnum = $new_cust_pkg ? $new_cust_pkg->locationnum : '';
   $new_custnum = $new_cust_pkg ? $new_cust_pkg->custnum : '';
 
-  #done setting variables for the command
-
-  $self->shellcommands_queue( $new->svcnum,
-    user    => $self->option('user')||'root',
-    host    => $self->machine,
-    command => eval(qq("$command")),
-  );
+  eval(qq("$command"));
 }
 
+
 #a good idea to queue anything that could fail or take any time
 sub shellcommands_queue {
   my( $self, $svcnum ) = (shift, shift);
diff --git a/FS/FS/part_export/broadband_shellcommands_expect.pm b/FS/FS/part_export/broadband_shellcommands_expect.pm
new file mode 100644
index 0000000..ec525d3
--- /dev/null
+++ b/FS/FS/part_export/broadband_shellcommands_expect.pm
@@ -0,0 +1,19 @@
+package FS::part_export::broadband_shellcommands_expect;
+use base qw( FS::part_export::shellcommands_expect );
+
+use strict;
+use FS::part_export::broadband_shellcommands;
+
+our %info = %FS::part_export::shellcommands_expect::info;
+$info{'svc'}  = 'svc_broadband';
+$info{'desc'} = 'Real time export via remote SSH, with interactive ("Expect"-like) scripting, for svc_broadband services';
+
+sub _export_subvars {
+  FS::part_export::broadband_shellcommands::_export_subvars(@_)
+}
+
+sub _export_subvars_replace {
+  FS::part_export::broadband_shellcommands::_export_subvars_replace(@_)
+}
+
+1;
diff --git a/FS/FS/part_export/shellcommands.pm b/FS/FS/part_export/shellcommands.pm
index 647dc5f..775af17 100644
--- a/FS/FS/part_export/shellcommands.pm
+++ b/FS/FS/part_export/shellcommands.pm
@@ -4,6 +4,7 @@ use vars qw(@ISA %info);
 use Tie::IxHash;
 use Date::Format;
 use String::ShellQuote;
+use Net::OpenSSH;
 use FS::part_export;
 use FS::Record qw( qsearch qsearchs );
 
@@ -296,7 +297,7 @@ sub _export_command_or_super {
   } else {
     $self->_export_command($action, @_);
   }
-};
+}
 
 sub _export_command {
   my ( $self, $action, $svc_acct) = (shift, shift, shift);
@@ -305,6 +306,41 @@ sub _export_command {
   return '' if $command =~ /^\s*$/;
   my $stdin = $self->option($action."_stdin");
 
+  my( $command_string, $stdin_string ) =
+    $self->_export_subvars( $svc_acct, $command, $stdin );
+
+  $self->ssh_or_queue( $svc_acct, $command_string, $stdin_string );
+}
+
+sub ssh_or_queue {
+  my( $self, $svc_acct, $command_string, $stdin_string ) = @_;
+
+  my @ssh_cmd_args = (
+    user          => $self->option('user') || 'root',
+    host          => $self->svc_machine($svc_acct),
+    command       => $command_string,
+    stdin_string  => $stdin_string,
+    ignored_errors    => $self->option('ignored_errors') || '',
+    ignore_all_errors => $self->option('ignore_all_errors'),
+    fail_on_output    => $self->option('fail_on_output'),
+ );
+
+  if ( $self->option($action. '_no_queue') ) {
+    # discard return value just like freeside-queued.
+    eval { ssh_cmd(@ssh_cmd_args) };
+    $error = $@;
+    $error = $error->full_message if ref $error; # Exception::Class::Base
+    return $error.
+             ' ('. $self->exporttype. ' to '. $self->svc_machine($svc_acct). ')'
+      if $error;
+  } else {
+    $self->shellcommands_queue( $svc_acct->svcnum, @ssh_cmd_args );
+  }
+}
+
+sub _export_subvars {
+  my( $self, $svc_acct, $command, $stdin ) = @_;
+
   no strict 'vars';
   {
     no strict 'refs';
@@ -412,27 +448,7 @@ sub _export_command {
   my $command_string = eval(qq("$command"));
   return "error filling in command: $@" if $@;
 
-  my @ssh_cmd_args = (
-    user          => $self->option('user') || 'root',
-    host          => $self->svc_machine($svc_acct),
-    command       => $command_string,
-    stdin_string  => $stdin_string,
-    ignored_errors    => $self->option('ignored_errors') || '',
-    ignore_all_errors => $self->option('ignore_all_errors'),
-    fail_on_output    => $self->option('fail_on_output'),
- );
-
-  if ( $self->option($action. '_no_queue') ) {
-    # discard return value just like freeside-queued.
-    eval { ssh_cmd(@ssh_cmd_args) };
-    $error = $@;
-    $error = $error->full_message if ref $error; # Exception::Class::Base
-    return $error.
-             ' ('. $self->exporttype. ' to '. $self->svc_machine($svc_acct). ')'
-      if $error;
-  } else {
-    $self->shellcommands_queue( $svc_acct->svcnum, @ssh_cmd_args );
-  }
+  ( $command_string, $stdin_string );
 }
 
 sub _export_replace {
@@ -440,6 +456,16 @@ sub _export_replace {
   my $command = $self->option('usermod');
   return '' if $command =~ /^\s*$/;
   my $stdin = $self->option('usermod_stdin');
+
+  my( $command_string, $stdin_string ) =
+    $self->_export_subvars_replace( $new, $old, $command, $stdin );
+
+  $self->ssh_or_queue( $new, $command_string, $stdin_string );
+}
+  
+sub _export_subvars_replace {
+  my( $self, $new, $old, $command, $stdin ) = @_;
+
   no strict 'vars';
   {
     no strict 'refs';
@@ -511,27 +537,7 @@ sub _export_replace {
 
   my $command_string = eval(qq("$command"));
 
-  my @ssh_cmd_args = (
-    user          => $self->option('user') || 'root',
-    host          => $self->svc_machine($new),
-    command       => $command_string,
-    stdin_string  => $stdin_string,
-    ignored_errors    => $self->option('ignored_errors') || '',
-    ignore_all_errors => $self->option('ignore_all_errors'),
-    fail_on_output    => $self->option('fail_on_output'),
-  );
-
-  if($self->option('usermod_no_queue')) {
-    # discard return value just like freeside-queued.
-    eval { ssh_cmd(@ssh_cmd_args) };
-    $error = $@;
-    $error = $error->full_message if ref $error; # Exception::Class::Base
-    return $error. ' ('. $self->exporttype. ' to '. $self->svc_machine($new). ')'
-      if $error;
-  }
-  else {
-    $self->shellcommands_queue( $new->svcnum, @ssh_cmd_args );
-  }
+  ( $command_string, $stdin_string );
 }
 
 #a good idea to queue anything that could fail or take any time
@@ -545,7 +551,6 @@ sub shellcommands_queue {
 }
 
 sub ssh_cmd { #subroutine, not method
-  use Net::OpenSSH;
   my $opt = { @_ };
   open my $def_in, '<', '/dev/null' or die "unable to open /dev/null\n";
   my $ssh = Net::OpenSSH->new(
diff --git a/FS/FS/part_export/shellcommands_expect.pm b/FS/FS/part_export/shellcommands_expect.pm
new file mode 100644
index 0000000..c2a4118
--- /dev/null
+++ b/FS/FS/part_export/shellcommands_expect.pm
@@ -0,0 +1,128 @@
+package FS::part_export::shellcommands_expect;
+use base qw( FS::part_export::shellcommands );
+
+use strict;
+use Tie::IxHash;
+use Net::OpenSSH;
+use Expect;
+#use FS::Record qw( qsearch qsearchs );
+
+tie my %options, 'Tie::IxHash',
+  'user'      => { label =>'Remote username', default=>'root' },
+  'useradd'   => { label => 'Insert commands',    type => 'textarea', },
+  'userdel'   => { label => 'Delete commands',    type => 'textarea', },
+  'usermod'   => { label => 'Modify commands',    type => 'textarea', },
+  'suspend'   => { label => 'Suspend commands',   type => 'textarea', },
+  'unsuspend' => { label => 'Unsuspend commands', type => 'textarea', },
+  'debug'     => { label => 'Enable debugging',
+                   type  => 'checkbox',
+                   value => 1,
+                 },
+;
+
+our %info = (
+  'svc'     => 'svc_acct',
+  'desc'    => 'Real time export via remote SSH, with interactive ("Expect"-like) scripting, for svc_acct services',
+  'options' => \%options,
+  'notes'   => q[
+Interactively run commands via SSH in a remote terminal, like "Expect".  In
+most cases, you probably want a regular shellcommands (or broadband_shellcommands, etc.) export instead, unless
+you have a specific need to interact with a terminal-based interface in an
+"Expect"-like fashion.
+<BR><BR>
+
+Each line specifies a string to match and a command to
+run after that string is found, separated by the first space.  For example, to
+run "exit" after a prompt ending in "#" is sent, "# exit".  You will need to
+<a href="http://www.freeside.biz/mediawiki/index.php/Freeside:1.9:Documentation:Administration:SSH_Keys">setup SSH for unattended operation</a>.
+<BR><BR>
+
+In commands, all variable substitutions of the regular shellcommands (or
+broadband_shellcommands, etc.) export are available (use a backslash to escape
+a literal $).
+]
+);
+
+sub _export_command {
+  my ( $self, $action, $svc_acct) = (shift, shift, shift);
+  my @lines = split("\n", $self->option($action) );
+
+  return '' unless @lines;
+
+  my @commands = ();
+  foreach my $line (@lines) {
+    my($match, $command) = split(' ', $line, 2);
+    my( $command_string ) = $self->_export_subvars( $svc_acct, $command, '' );
+    push @commands, [ $match, $command_string ];
+  }
+
+  $self->shellcommands_expect_queue( $svc_acct->svcnum, @commands );
+}
+
+sub _export_replace {
+  my( $self, $new, $old ) = (shift, shift, shift);
+  my @lines = split("\n", $self->option('replace') );
+
+  return '' unless @lines;
+
+  my @commands = ();
+  foreach my $line (@lines) {
+    my($match, $command) = split(' ', $line, 2);
+    my( $command_string ) = $self->_export_subvars_replace( $new, $old, $command, '' );
+    push @commands, [ $match, $command_string ];
+  }
+
+  $self->shellcommands_expect_queue( $new->svcnum, @commands );
+}
+
+sub shellcommands_expect_queue {
+  my( $self, $svcnum, @commands ) = @_;
+
+  my $queue = new FS::queue {
+    'svcnum' => $svcnum,
+    'job'    => "FS::part_export::shellcommands_expect::ssh_expect",
+  };
+  $queue->insert(
+    user          => $self->option('user') || 'root',
+    host          => $self->machine,
+    debug         => $self->option('debug'),
+    commands      => \@commands,
+  );
+}
+
+sub ssh_expect { #subroutine, not method
+  my $opt = { @_ };
+
+  my $dest = $opt->{'user'}.'@'.$opt->{'host'};
+
+  open my $def_in, '<', '/dev/null' or die "unable to open /dev/null\n";
+  my $ssh = Net::OpenSSH->new( $dest, 'default_stdin_fh' => $def_in );
+  # ignore_all_errors doesn't override SSH connection/auth errors--
+  # probably correct
+  die "Couldn't establish SSH connection to $dest: ". $ssh->error
+    if $ssh->error;
+
+  my ($pty, $pid) = $ssh->open2pty
+    or die "Couldn't start a remote terminal session";
+  my $expect = Expect->init($pty);
+  #not useful #$expect->debug($opt->{debug} ? 3 : 0);
+
+  foreach my $line ( @{ $opt->{commands} } ) {
+    my( $match, $command ) = @$line;
+
+    warn "Waiting for '$match'\n" if $opt->{debug};
+
+    my $matched = $expect->expect(30, $match);
+    unless ( $matched ) {
+      my $err = "Never saw '$match'\n";
+      warn $err;
+      die $err;
+    }
+    warn "Running '$command'\n" if $opt->{debug};
+    $expect->send("$command\n");
+  }
+
+  '';
+}
+
+1;
diff --git a/debian/control b/debian/control
index bdcce2d..ab58021 100644
--- a/debian/control
+++ b/debian/control
@@ -100,7 +100,7 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,latex-xcolor,
  libxml-writer-perl, libio-socket-ssl-perl,
  libmap-splat-perl, libdatetime-format-ical-perl, librest-client-perl,
  libgeo-streetaddress-us-perl, libbusiness-onlinepayment-perl,
- libnet-vitelity-perl (>= 0.05), libnet-sslglue-perl
+ libnet-vitelity-perl (>= 0.05), libnet-sslglue-perl, libexpect-perl
 Conflicts: libparams-classify-perl (>= 0.013-6)
 Replaces: freeside (<<4)
 Breaks: freeside (<<4)

commit 1fa46a8e2890c2c6f1e0117856f5c481306b1c59
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Sat Aug 19 14:50:15 2017 -0700

    fix cancel message verb for suspensions, RT#29087

diff --git a/httemplate/misc/cancel_pkg.html b/httemplate/misc/cancel_pkg.html
index 3c18622..96cf641 100755
--- a/httemplate/misc/cancel_pkg.html
+++ b/httemplate/misc/cancel_pkg.html
@@ -197,25 +197,27 @@ my $cust_pkg = qsearchs('cust_pkg', {'pkgnum' => $pkgnum})
 
 my $part_pkg = $cust_pkg->part_pkg;
 
-my @unprovision_warning;
-{
-    my @services_w_export;
-    for ( $cust_pkg->cust_svc ) {
-        push( @services_w_export, ($_->label)[0] . ': ' . ($_->label)[1], )
-          if $_->part_svc->export_svc;
-    }
-    if ( @services_w_export ) {
-        push( @unprovision_warning, 'NOTE: This package has '
-          . @services_w_export . ' ' . PL( "service", @services_w_export )
-          . ' that will be unprovisioned', );
-        if ( @services_w_export < 10 ) {
-            $unprovision_warning[0] .= ':';
-            push( @unprovision_warning, @services_w_export, );
-        }
-        else {
-            $unprovision_warning[0] .= '.';
-        }
+my @unprovision_warning = ();
+unless ( $method =~ /^(resume|uncancel)$/ ) {
+  my @services_w_export = map { my @l = $_->label; $l[0]. ': '. $l[1]; }
+                            grep $_->part_svc->export_svc,
+                              $cust_pkg->cust_svc;
+  if ( @services_w_export ) {
+
+    my $actioned = ($method =~ /^(suspend|adjourn)$/) ? 'suspended'
+                                                      : 'unprovisioned';
+    push @unprovision_warning,
+      'NOTE: This package has '. @services_w_export. ' '.
+      PL( 'service', @services_w_export ). " that will be $actioned";
+
+    if ( @services_w_export < 10 ) {
+      $unprovision_warning[0] .= ':';
+      push @unprovision_warning, @services_w_export;
+    } else {
+      $unprovision_warning[0] .= '.';
     }
+
+  }
 }
 
 $date ||= $cust_pkg->get($method);

commit b0d572bc74c5924ad73d247548d8227e06a58819
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Aug 15 18:36:39 2017 -0700

    have base freeside deb package depend on same versions

diff --git a/debian/control b/debian/control
index b1acea2..bdcce2d 100644
--- a/debian/control
+++ b/debian/control
@@ -18,8 +18,7 @@ Pre-Depends: freeside-lib
 # dbconfig-common
 Depends: ${perl:Depends}, ${shlibs:Depends}, ${misc:Depends},
  freeside-webui (= ${binary:Version}), freeside-lib (= ${binary:Version}),
- debconf, cron, openbsd-inetd, tcpd, undersmtpd, ssmtp,
-
+ debconf, cron, openbsd-inetd, tcpd, undersmtpd, ssmtp
 Description: Billing and trouble ticketing for service providers
  Freeside is a web-based billing, trouble ticketing and network monitoring
  application.  It includes features for ISPs and WISPs, hosting providers and

commit 2c856415e89e7eb8a6cc18885c4fca20087aa5ce
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Aug 15 18:34:40 2017 -0700

    have base freeside deb package depend on same versions

diff --git a/debian/control b/debian/control
index 615b764..b1acea2 100644
--- a/debian/control
+++ b/debian/control
@@ -16,8 +16,10 @@ Package: freeside
 Architecture: all
 Pre-Depends: freeside-lib
 # dbconfig-common
-Depends: ${perl:Depends}, ${shlibs:Depends}, ${misc:Depends}, freeside-webui,
- debconf, cron, openbsd-inetd, tcpd, undersmtpd, ssmtp, freeside-lib (>= 4.0~git-20160211)
+Depends: ${perl:Depends}, ${shlibs:Depends}, ${misc:Depends},
+ freeside-webui (= ${binary:Version}), freeside-lib (= ${binary:Version}),
+ debconf, cron, openbsd-inetd, tcpd, undersmtpd, ssmtp,
+
 Description: Billing and trouble ticketing for service providers
  Freeside is a web-based billing, trouble ticketing and network monitoring
  application.  It includes features for ISPs and WISPs, hosting providers and

commit 4f0b72e1e69fcd5aa4351d5f151be06b3909f74b
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Aug 15 08:04:16 2017 -0700

    SureTax: use less memory, provide additional debugging, RT#76987

diff --git a/FS/FS/TaxEngine/suretax.pm b/FS/FS/TaxEngine/suretax.pm
index 1a00cda..fe8764b 100644
--- a/FS/FS/TaxEngine/suretax.pm
+++ b/FS/FS/TaxEngine/suretax.pm
@@ -14,7 +14,7 @@ our $DEBUG = 1; # prints progress messages
 #   $DEBUG = 2; # prints decoded request and response (noisy, be careful)
 #   $DEBUG = 3; # prints raw response from the API, ridiculously unreadable
 
-our $json = Cpanel::JSON::XS->new->pretty(1);
+our $json = Cpanel::JSON::XS->new->pretty(0)->shrink(1);
 
 our %taxproduct_cache;
 
@@ -328,13 +328,14 @@ sub make_taxlines {
     return;
   }
 
-  warn "sending SureTax request\n" if $DEBUG;
+  warn "encoding SureTax request\n" if $DEBUG;
   my $request_json = $json->encode($request);
   warn $request_json if $DEBUG > 1;
 
   my $host = $conf->config('suretax-hostname');
   $host ||= 'testapi.taxrating.net';
 
+  warn "sending SureTax request\n" if $DEBUG;
   # We are targeting the "V05" interface:
   # - accepts both telecom and general sales transactions
   # - produces results broken down by "invoice" (Freeside line item)
@@ -346,8 +347,8 @@ sub make_taxlines {
     'Accept'        => 'application/json',
   );
 
+  warn "received SureTax response\n" if $DEBUG;
   my $raw_response = $http_response->content;
-  warn "received response\n" if $DEBUG;
   warn $raw_response if $DEBUG > 2;
   my $response;
   if ( $raw_response =~ /^<\?xml/ ) {
@@ -356,6 +357,8 @@ sub make_taxlines {
     $response = XMLin( $raw_response );
     $raw_response = $response->{content};
   }
+
+  warn "decoding SureTax response\n" if $DEBUG;
   $response = eval { $json->decode($raw_response) }
     or die "$raw_response\n";
 
@@ -375,6 +378,7 @@ sub make_taxlines {
   }
 
   return if !$response->{GroupList};
+  warn "creating FS objects from SureTax data\n" if $DEBUG;
   foreach my $taxable ( @{ $response->{GroupList} } ) {
     # each member of this array here corresponds to what SureTax calls an
     # "invoice" and we call a "line item". The invoice number is 
@@ -420,6 +424,7 @@ sub make_taxlines {
       });
     }
   }
+  warn "TaxEngine/suretax.pm make_taxlines done; returning FS objects\n" if $DEBUG;
   return @elements;
 }
 

commit d4496963925b57f117bdf336661e4cc33ce25c9d
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Tue Aug 15 06:13:45 2017 -0700

    voip innovations FTPS should be passive, RT#76784

diff --git a/FS/bin/freeside-voipinnovations-cdrimport b/FS/bin/freeside-voipinnovations-cdrimport
index e0ba785..d64c870 100755
--- a/FS/bin/freeside-voipinnovations-cdrimport
+++ b/FS/bin/freeside-voipinnovations-cdrimport
@@ -40,7 +40,7 @@ my $tempdir = tempdir( CLEANUP => !$opt_v );
 my $format = 'voip_innovations';
 my $hostname = 'customercdr.voipinnovations.com';
 
-my $ftp = Net::FTP->new($hostname, Debug => $opt_d)
+my $ftp = Net::FTP->new($hostname, Passive => 1, Debug => $opt_d)
   or die "Can't connect to $hostname: $@\n";
 
 $ftp->starttls()

commit 7b9fd8e2fbea349fe02cadd7141e47197ab60a92
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 21:49:55 2017 -0700

    exclude big non-operational history tables from backup

diff --git a/FS/FS/Cron/backup.pm b/FS/FS/Cron/backup.pm
index 7d868c8..5276565 100644
--- a/FS/FS/Cron/backup.pm
+++ b/FS/FS/Cron/backup.pm
@@ -25,7 +25,7 @@ sub backup {
 
   my $ext;
   if ( driver_name eq 'Pg' ) {
-    system("pg_dump -Fc $database >/var/tmp/$database.Pg");
+    system("pg_dump -Fc -T h_cdr -T h_queue -T h_queue_arg $database >/var/tmp/$database.Pg");
     $ext = 'Pg';
   } elsif ( driver_name eq 'mysql' ) {
     system("mysqldump $database >/var/tmp/$database.sql");

commit 7f7a4ecece296b4c0ff81732d3cffa4a7951ae37
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 13:00:44 2017 -0700

    database size

diff --git a/httemplate/view/Status-db_size_detail.html b/httemplate/view/Status-db_size_detail.html
new file mode 100644
index 0000000..96c2da0
--- /dev/null
+++ b/httemplate/view/Status-db_size_detail.html
@@ -0,0 +1,39 @@
+<& /elements/header-popup.html, {
+     'title'         => 'Database size details',
+   }
+&>
+
+<& /search/elements/search.html, 
+     'name_singular' => 'table',
+     'header'        => [ 'Table', 'Size' ],
+     'query'         => $query,
+     'count_query'   => $count_query,
+     'nohtmlheader'  => 1,
+&>
+
+<& /elements/footer-popup.html &>
+<%init>
+
+my $query = q{
+
+  SELECT table_name, pg_size_pretty(total_bytes) AS total 
+    FROM (
+    SELECT * FROM (
+        SELECT relname AS TABLE_NAME,
+               pg_total_relation_size(c.oid) AS total_bytes
+            FROM pg_class c
+            LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+            WHERE relkind = 'r'
+              AND nspname = 'public'
+    ) a
+  ) a order by total_bytes desc
+};
+
+my $count_query = q{
+  SELECT count(*) FROM pg_class c
+    LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
+            WHERE relkind = 'r'
+              AND nspname = 'public'
+};
+
+</%init>
diff --git a/httemplate/view/Status.html b/httemplate/view/Status.html
index e08bfe4..7fb03eb 100644
--- a/httemplate/view/Status.html
+++ b/httemplate/view/Status.html
@@ -1,4 +1,7 @@
 <& /elements/header.html, 'System Status' &>
+
+<& /elements/init_overlib.html &>
+
 % foreach my $section ( keys %status ) {
 <FONT CLASS="fsinnerbox-title"><% mt($section) |h %></FONT>
 <TABLE CLASS="fsinnerbox">
@@ -11,6 +14,7 @@
 </TABLE>
 <BR><BR>
 % }
+
 <& /elements/footer.html &>
 <%init>
 
@@ -40,8 +44,13 @@ if ( $db eq 'PostgreSQL' && $db_ver =~ /^\s*PostgreSQL\s+([\w\.]+)\s+on\s+/ ) {
 my $db_size = 'Unknown';
 if ( $db eq 'PostgreSQL' ) {
   $db_size = FS::Record->scalar_sql(qq(
-    SELECT pg_size_pretty(pg_database_size('freeside'))
-  ));
+               SELECT pg_size_pretty(pg_database_size('freeside'))
+             )). ' '.
+             include('/elements/popup_link.html',
+                       'action'      => 'Status-db_size_detail.html',
+                       'label'       => '(details)',
+                       'actionlabel' => 'Database size details',
+             );
 }
 
 tie my %status, 'Tie::IxHash',

commit 352650e920955b22a2468c54633d9e4c5cfaff84
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 12:54:49 2017 -0700

    voip innovations CDR import: don't cd / anymore, RT#76784

diff --git a/FS/bin/freeside-voipinnovations-cdrimport b/FS/bin/freeside-voipinnovations-cdrimport
index 10c4bca..e0ba785 100755
--- a/FS/bin/freeside-voipinnovations-cdrimport
+++ b/FS/bin/freeside-voipinnovations-cdrimport
@@ -55,7 +55,7 @@ $ftp->login($login, $password)
 
 warn "Retrieving directory listing\n" if $opt_v;
 
-$ftp->cwd('/');
+#$ftp->cwd('/');
 my @dirs = $ftp->ls();
 warn scalar(@dirs)." directories found.\n" if $opt_v;
 # apply date range

commit f9585a8a603cf4a83d740cf2980ef7a84a123335
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 12:52:41 2017 -0700

    enable all debugging when you turn on the checkbox, RT#76756

diff --git a/FS/FS/part_export/pbxware.pm b/FS/FS/part_export/pbxware.pm
index 4373e7a..9458fca 100644
--- a/FS/FS/part_export/pbxware.pm
+++ b/FS/FS/part_export/pbxware.pm
@@ -137,7 +137,7 @@ sub import_cdrs {
       # page's IDs or something.
       my $uniqueid = md5_hex(join(',',@$row));
       if ( FS::cdr->row_exists('uniqueid = ?', $uniqueid) ) {
-        warn "skipped duplicate row in page $page\n" if $DEBUG > 1;
+        warn "skipped duplicate row in page $page\n" if $DEBUG;
         next CDR;
       }
 
@@ -186,7 +186,7 @@ local $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
     ]
   );
   warn "$me $method\n" if $DEBUG;
-  warn $request->as_string."\n" if $DEBUG > 1;
+  warn $request->as_string."\n" if $DEBUG;
 
   my $ua = LWP::UserAgent->new;
   my $response = $ua->request($request);

commit c13da0ff08d1a691f8b4613e001b807472dd1d3c
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 12:51:35 2017 -0700

    fix agent display on reports when set in conf setting cust-fields, RT#76948

diff --git a/FS/FS/cust_main_Mixin.pm b/FS/FS/cust_main_Mixin.pm
index 1955746..f8f1173 100644
--- a/FS/FS/cust_main_Mixin.pm
+++ b/FS/FS/cust_main_Mixin.pm
@@ -262,6 +262,17 @@ sub cust_statuscolor {
     : '000000';
 }
 
+=item agent_name
+
+=cut
+
+sub agent_name {
+  my $self = shift;
+  $self->cust_linked
+    ? $self->cust_main->agent_name
+    : $self->cust_unlinked_msg;
+}
+
 =item prospect_sql
 
 =item active_sql

commit 0f4da1d19bdb76f2b030d6ae961161426d11716a
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 12:50:50 2017 -0700

    missing a use B:OP causes an error in rare edge cases with batching, RT#77003

diff --git a/FS/FS/cust_main/Billing_Realtime.pm b/FS/FS/cust_main/Billing_Realtime.pm
index d62120b..f16752b 100644
--- a/FS/FS/cust_main/Billing_Realtime.pm
+++ b/FS/FS/cust_main/Billing_Realtime.pm
@@ -6,6 +6,7 @@ use vars qw( $realtime_bop_decline_quiet ); #ugh
 use Carp;
 use Data::Dumper;
 use Business::CreditCard 0.35;
+use Business::OnlinePayment;
 use FS::UID qw( dbh myconnect );
 use FS::Record qw( qsearch qsearchs );
 use FS::payby;

commit afed6387462fb38675f08a5840c632a405b01148
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 12:49:52 2017 -0700

    add fulltexrt indexing to RT daily tasks

diff --git a/FS/FS/Cron/rt_tasks.pm b/FS/FS/Cron/rt_tasks.pm
index 01ea0b5..077f23c 100644
--- a/FS/FS/Cron/rt_tasks.pm
+++ b/FS/FS/Cron/rt_tasks.pm
@@ -31,6 +31,8 @@ sub rt_daily {
   my $system = $FS::TicketSystem::system;
   return if !defined($system) || $system ne 'RT_Internal';
 
+  system('/opt/rt3/sbin/rt-fulltext-indexer --quiet --limit 5400 &');
+
   # if -d or -y is in use, bail out.  There's no reliable way to tell RT 
   # to use an alternate system time.
   if ( $opt{'d'} or $opt{'y'} ) {

commit b4be57727f8d712e6508744cb5db1e5f47479f9d
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Mon Aug 14 12:48:44 2017 -0700

    add tilde to allowable punctuation, RT#77086

diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm
index f2e9e6f..479f9b1 100644
--- a/FS/FS/Record.pm
+++ b/FS/FS/Record.pm
@@ -2647,7 +2647,7 @@ sub ut_currency {
 =item ut_text COLUMN
 
 Check/untaint text.  Alphanumerics, spaces, and the following punctuation
-symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < >
+symbols are currently permitted: ! @ # $ % & ( ) - + ; : ' " , . ? / = [ ] < > ~
 May not be null.  If there is an error, returns the error, otherwise returns
 false.
 
@@ -2661,7 +2661,7 @@ sub ut_text {
   # \p{Word} = alphanumerics, marks (diacritics), and connectors
   # see perldoc perluniprops
   $self->getfield($field)
-    =~ /^([\p{Word} \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>$money_char]+)$/
+    =~ /^([\p{Word} \!\@\#\$\%\&\(\)\-\+\;\:\'\"\,\.\?\/\=\[\]\<\>\~$money_char]+)$/
       or return gettext('illegal_or_empty_text'). " $field: ".
                  $self->getfield($field);
   $self->setfield($field,$1);

commit 39cf034d6ae1c20de21028933aff05cae6dc4f77
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Thu Aug 3 08:47:45 2017 -0700

    fix agent-specific invoice_default_terms on new customer add, RT#76862

diff --git a/httemplate/edit/cust_main.cgi b/httemplate/edit/cust_main.cgi
index e58441d..05bf437 100755
--- a/httemplate/edit/cust_main.cgi
+++ b/httemplate/edit/cust_main.cgi
@@ -296,8 +296,13 @@ if ( $cgi->param('error') ) {
 
   $custnum='';
   $cust_main = new FS::cust_main ( {} );
+
+  my @agentnums = $curuser->agentnums;
+  $cust_main->agentnum( $agentnums[0] )
+    if scalar(@agentnums) == 1;
   $cust_main->agentnum( $conf->config('default_agentnum') )
     if $conf->exists('default_agentnum');
+
   $cust_main->referral_custnum( $cgi->param('referral_custnum') );
   $cust_main->set('postal_invoice', 'Y')
     unless $conf->exists('disablepostalinvoicedefault');

commit 79c995c1e0f66d333bc7738894c0b2359489078f
Author: Ivan Kohler <ivan at freeside.biz>
Date:   Wed Aug 2 11:54:48 2017 -0700

    VoIP innovations CDR import using SSL, RT#76784

diff --git a/FS/bin/freeside-voipinnovations-cdrimport b/FS/bin/freeside-voipinnovations-cdrimport
index 23ea6bb..10c4bca 100755
--- a/FS/bin/freeside-voipinnovations-cdrimport
+++ b/FS/bin/freeside-voipinnovations-cdrimport
@@ -4,7 +4,8 @@ use strict;
 use Getopt::Std;
 use Date::Format;
 use File::Temp 'tempdir';
-use Net::FTP;
+use Net::SSLGlue::FTP; #at least until the Deb 9 transition is done, then
+                       # regular Net::FTP has SSL support
 use FS::UID qw(adminsuidsetup datasrc dbh);
 use FS::cdr;
 use FS::cdr_batch;
@@ -42,8 +43,11 @@ my $hostname = 'customercdr.voipinnovations.com';
 my $ftp = Net::FTP->new($hostname, Debug => $opt_d)
   or die "Can't connect to $hostname: $@\n";
 
+$ftp->starttls()
+  or die "TLS initialization failed: ". $ftp->message. "\n";
+
 $ftp->login($login, $password)
-  or die "Login failed: ".$ftp->message."\n";
+  or die "Login failed: ". $ftp->message. "\n";
 
 ###
 # get the file list
diff --git a/debian/control b/debian/control
index a268ffd..615b764 100644
--- a/debian/control
+++ b/debian/control
@@ -99,7 +99,7 @@ Depends: aspell-en,gnupg,ghostscript,gsfonts,gzip,latex-xcolor,
  libxml-writer-perl, libio-socket-ssl-perl,
  libmap-splat-perl, libdatetime-format-ical-perl, librest-client-perl,
  libgeo-streetaddress-us-perl, libbusiness-onlinepayment-perl,
- libnet-vitelity-perl (>= 0.05)
+ libnet-vitelity-perl (>= 0.05), libnet-sslglue-perl
 Conflicts: libparams-classify-perl (>= 0.013-6)
 Replaces: freeside (<<4)
 Breaks: freeside (<<4)

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

Summary of changes:
 FS/FS/AccessRight.pm                               |    2 +-
 FS/FS/Conf.pm                                      |    4 +-
 FS/FS/Cron/backup.pm                               |    2 +-
 FS/FS/Cron/rt_tasks.pm                             |    2 +
 FS/FS/Record.pm                                    |    4 +-
 FS/FS/TaxEngine/suretax.pm                         |   11 +-
 FS/FS/access_right.pm                              |    5 +-
 .../{queue_stat.pm => access_user_session_log.pm}  |   59 +++++----
 FS/FS/cust_main/Billing_Realtime.pm                |    1 +
 FS/FS/cust_main/Import.pm                          |   29 +++--
 FS/FS/cust_main_Mixin.pm                           |   11 ++
 FS/FS/part_export/broadband_shellcommands.pm       |   41 ++++---
 .../part_export/broadband_shellcommands_expect.pm  |   19 +++
 FS/FS/part_export/pbxware.pm                       |    4 +-
 FS/FS/part_export/shellcommands.pm                 |   93 +++++++-------
 FS/FS/part_export/shellcommands_expect.pm          |  128 ++++++++++++++++++++
 FS/FS/part_export/vitelity.pm                      |    2 +-
 FS/bin/freeside-voipinnovations-cdrimport          |   12 +-
 FS/t/{AccessRight.t => access_user_session_log.t}  |    2 +-
 debian/control                                     |    7 +-
 httemplate/edit/cust_main.cgi                      |    5 +
 httemplate/elements/menu.html                      |   12 +-
 httemplate/misc/cancel_pkg.html                    |   38 +++---
 httemplate/search/employee_audit.html              |    2 +-
 httemplate/search/report_access_user_log.html      |    2 +-
 httemplate/search/report_employee_audit.html       |    2 +-
 httemplate/view/Status-db_size_detail.html         |   39 ++++++
 httemplate/view/Status.html                        |   13 +-
 .../view/cust_main/payment_history/payment.html    |   17 +--
 29 files changed, 410 insertions(+), 158 deletions(-)
 copy FS/FS/{queue_stat.pm => access_user_session_log.pm} (54%)
 create mode 100644 FS/FS/part_export/broadband_shellcommands_expect.pm
 create mode 100644 FS/FS/part_export/shellcommands_expect.pm
 copy FS/t/{AccessRight.t => access_user_session_log.t} (75%)
 create mode 100644 httemplate/view/Status-db_size_detail.html




More information about the freeside-commits mailing list