Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorIvan Kohler <ivan@freeside.biz>
Fri, 25 Aug 2017 22:06:19 +0000 (15:06 -0700)
committerIvan Kohler <ivan@freeside.biz>
Fri, 25 Aug 2017 22:06:19 +0000 (15:06 -0700)
29 files changed:
FS/FS/AccessRight.pm
FS/FS/Conf.pm
FS/FS/Cron/backup.pm
FS/FS/Cron/rt_tasks.pm
FS/FS/Record.pm
FS/FS/TaxEngine/suretax.pm
FS/FS/access_right.pm
FS/FS/access_user_session_log.pm [new file with mode: 0644]
FS/FS/cust_main/Billing_Realtime.pm
FS/FS/cust_main/Import.pm
FS/FS/cust_main_Mixin.pm
FS/FS/part_export/broadband_shellcommands.pm
FS/FS/part_export/broadband_shellcommands_expect.pm [new file with mode: 0644]
FS/FS/part_export/pbxware.pm
FS/FS/part_export/shellcommands.pm
FS/FS/part_export/shellcommands_expect.pm [new file with mode: 0644]
FS/FS/part_export/vitelity.pm
FS/bin/freeside-voipinnovations-cdrimport
FS/t/access_user_session_log.t [new file with mode: 0644]
debian/control
httemplate/edit/cust_main.cgi
httemplate/elements/menu.html
httemplate/misc/cancel_pkg.html
httemplate/search/employee_audit.html
httemplate/search/report_access_user_log.html
httemplate/search/report_employee_audit.html
httemplate/view/Status-db_size_detail.html [new file with mode: 0644]
httemplate/view/Status.html
httemplate/view/cust_main/payment_history/payment.html

index 161e466..ccabf27 100644 (file)
@@ -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 },
   ],
index bddeee9..7157336 100644 (file)
@@ -5796,8 +5796,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',
   },
   
index 7d868c8..5276565 100644 (file)
@@ -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");
index 01ea0b5..077f23c 100644 (file)
@@ -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'} ) {
index f2e9e6f..479f9b1 100644 (file)
@@ -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);
index 1a00cda..fe8764b 100644 (file)
@@ -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;
 }
 
index 409b441..4a36033 100644 (file)
@@ -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/FS/FS/access_user_session_log.pm b/FS/FS/access_user_session_log.pm
new file mode 100644 (file)
index 0000000..d28ec85
--- /dev/null
@@ -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;
+
index d62120b..f16752b 100644 (file)
@@ -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;
index 6464761..9624529 100644 (file)
@@ -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,24 @@ sub batch_import {
 
       if ( $cust_main{'payinfo'} =~ /^\s*(\d+\@[\d\.]+)\s*$/ ) {
 
-        $cust_main{'payby'}   = 'CHEK';
-        $cust_main{'payinfo'} = $1;
+        delete $cust_main{'payinfo'};
 
-      } else {
+        $cust_payby = new FS::cust_payby {
+          'payby'   => 'CHEK',
+          'payinfo' => $1,
+        };
 
-        $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;
-        }
+        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'},
+        };
 
       }
 
@@ -502,7 +510,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;
index 1ef5387..8b6569a 100644 (file)
@@ -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
index 44280a2..d3e495c 100644 (file)
@@ -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 (file)
index 0000000..ec525d3
--- /dev/null
@@ -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;
index 4373e7a..9458fca 100644 (file)
@@ -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);
index 647dc5f..775af17 100644 (file)
@@ -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 (file)
index 0000000..c2a4118
--- /dev/null
@@ -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;
index 332e457..51bb0aa 100644 (file)
@@ -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 '';
   }
index 23ea6bb..d64c870 100755 (executable)
@@ -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;
@@ -39,11 +40,14 @@ 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()
+  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
@@ -51,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
diff --git a/FS/t/access_user_session_log.t b/FS/t/access_user_session_log.t
new file mode 100644 (file)
index 0000000..6306374
--- /dev/null
@@ -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";
index a268ffd..ab58021 100644 (file)
@@ -16,8 +16,9 @@ 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
@@ -99,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-vitelity-perl (>= 0.05), libnet-sslglue-perl, libexpect-perl
 Conflicts: libparams-classify-perl (>= 0.013-6)
 Replaces: freeside (<<4)
 Breaks: freeside (<<4)
index e58441d..05bf437 100755 (executable)
@@ -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');
index 58a7d57..cadbd86 100644 (file)
@@ -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' ]
index 3c18622..96cf641 100755 (executable)
@@ -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);
index 2bc6ff4..991758c 100644 (file)
@@ -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',
index 0c8acb3..d43c742 100644 (file)
@@ -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+)$/ ) {
index 461849b..6008e1c 100644 (file)
@@ -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',
diff --git a/httemplate/view/Status-db_size_detail.html b/httemplate/view/Status-db_size_detail.html
new file mode 100644 (file)
index 0000000..96c2da0
--- /dev/null
@@ -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>
index e08bfe4..7fb03eb 100644 (file)
@@ -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',
index 6241f11..6402383 100644 (file)
@@ -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'}      )