Merge branch 'master' of git.freeside.biz:/home/git/freeside
authorMark Wells <mark@freeside.biz>
Fri, 13 May 2016 21:31:18 +0000 (14:31 -0700)
committerMark Wells <mark@freeside.biz>
Fri, 13 May 2016 21:31:18 +0000 (14:31 -0700)
FS/FS/ConfDefaults.pm
FS/FS/UI/Web.pm
FS/FS/cdr/conexiant.pm [new file with mode: 0644]
FS/FS/cust_main/Search.pm
FS/FS/cust_payby.pm
FS/bin/freeside-cdr-conexiant-import [new file with mode: 0755]
httemplate/search/cust_main.html
httemplate/search/report_cust_main.html

index b24a300..4c37175 100644 (file)
@@ -77,6 +77,9 @@ sub cust_fields_avail { (
   'Cust# | Cust. Status | Name | Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Latitude | (bill) Longitude | Day phone | Night phone | Mobile phone | Fax number | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Latitude | (service) Longitude | Invoicing email(s) | Payment Type | Current Balance' =>
     'custnum | Status | Last, First | Company | (address+coord) | (all phones) | (service address+coord) | Invoicing email(s) | Payment Type | Current Balance',
 
+  'Cust# | Cust. Status | Name | Company | (bill) Address 1 | (bill) Address 2 | (bill) City | (bill) State | (bill) Zip | (bill) Country | (bill) Latitude | (bill) Longitude | Day phone | Night phone | Mobile phone | Fax number | (service) Address 1 | (service) Address 2 | (service) City | (service) State | (service) Zip | (service) Country | (service) Latitude | (service) Longitude | Invoicing email(s) | Payment Type | Current Balance | Advertising Source' =>
+    'custnum | Status | Last, First | Company | (address+coord) | (all phones) | (service address+coord) | Invoicing email(s) | Payment Type | Current Balance | Advertising Source',
+
   'Invoicing email(s)' => 'Invoicing email(s)',
   'Cust# | Invoicing email(s)' => 'custnum | Invoicing email(s)',
 
index 8f10011..e07e682 100644 (file)
@@ -346,6 +346,7 @@ sub cust_header {
     'Payment Type'             => 'cust_payby',
     'Current Balance'          => 'current_balance',
     'Agent Cust#'              => 'agent_custid',
+    'Advertising Source'       => 'referral',
   );
   $header2method{'Cust#'} = 'display_custnum'
     if $conf->exists('cust_main-default_agent_custid');
@@ -455,6 +456,9 @@ sub cust_sql_fields {
     push @extra_fields, FS::cust_main->balance_sql . " AS current_balance";
   }
 
+  push @extra_fields, 'part_referral_x.referral AS referral'
+    if grep { $_ eq 'referral' } @cust_fields;
+
   map("cust_main.$_", @fields), @location_fields, @extra_fields;
 }
 
@@ -519,6 +523,10 @@ sub join_cust_main {
             " ON (ship_location.locationnum = $location_table.$locationnum) ";
   }
 
+  if ( !@cust_fields or grep { $_ eq 'referral' } @cust_fields ) {
+    $sql .= ' LEFT JOIN (select refnum, referral from part_referral) AS part_referral_x ON (cust_main.refnum = part_referral_x.refnum) ';
+  }
+
   $sql;
 }
 
diff --git a/FS/FS/cdr/conexiant.pm b/FS/FS/cdr/conexiant.pm
new file mode 100644 (file)
index 0000000..852c2f6
--- /dev/null
@@ -0,0 +1,43 @@
+package FS::cdr::conexiant;
+use base qw( FS::cdr );
+
+use strict;
+use vars qw( %info );
+use FS::Record qw( qsearchs );
+use FS::cdr qw( _cdr_date_parser_maker _cdr_min_parser_maker );
+
+%info = (
+  'name'          => 'Conexiant',
+  'weight'        => 600,
+  'header'        => 1,
+  'type'          => 'csv',
+  'import_fields' => [
+    skip(3),               #LookupError,Direction,LegType
+    sub {                  #CallId
+      my($cdr,$value,$conf,$param) = @_;
+      if (qsearchs('cdr',{'uniqueid' => $value})) {
+        $param->{'skiprow'} = 1;
+        $param->{'empty_ok'} = 1;
+      } else {
+        $cdr->uniqueid($value);
+      }
+    },
+    'upstream_rateplanid', #ClientRateSheetId
+    skip(1),               #ClientRouteId
+    'src',                 #SourceNumber
+    skip(1),               #RawNumber
+    'dst',                 #DestNumber
+    skip(1),               #DestLRN
+    _cdr_date_parser_maker('startdate'),  #CreatedOn
+    _cdr_date_parser_maker('answerdate'), #AnsweredOn
+    _cdr_date_parser_maker('enddate'),    #HangupOn
+    skip(4),               #CallCause,SipCode,Price,USFCharge
+    'upstream_price',      #TotalPrice
+    _cdr_min_parser_maker('billsec'),     #PriceDurationMins
+    skip(2),               #SipEndpointId, SipEndpointName
+  ],
+);
+
+sub skip { map {''} (1..$_[0]) }
+
+1;
index cc23f50..11a106a 100644 (file)
@@ -620,10 +620,6 @@ listref (list returned by FS::UI::Web::parse_lt_gt($cgi, 'current_balance'))
 
 bool
 
-=item select_referral
-
-bool, join to part_referral and select part_referral.referral
-
 =back
 
 =cut
@@ -1017,6 +1013,10 @@ sub search {
       'ON (cust_main.'.$pre.'locationnum = '.$pre.'location.locationnum) ';
   }
 
+  # always make referral available in results
+  #   (maybe we should be using FS::UI::Web::join_cust_main instead?)
+  $addl_from .= ' LEFT JOIN (select refnum, referral from part_referral) AS part_referral_x ON (cust_main.refnum = part_referral_x.refnum) ';
+
   my $count_query = "SELECT COUNT(*) FROM cust_main $addl_from $extra_sql";
 
   my @select = (
@@ -1031,13 +1031,6 @@ sub search {
   my(@extra_headers) = ();
   my(@extra_fields)  = ();
 
-  if ($params->{'select_referral'}) {
-    $addl_from .= ' LEFT JOIN part_referral ON ( cust_main.refnum = part_referral.refnum ) ';
-    push @select, 'part_referral.referral';
-    push @extra_headers, 'Advertising Source';
-    push @extra_fields, 'referral';
-  }
-    
   if ($params->{'flattened_pkgs'}) {
 
     #my $pkg_join = '';
index 50d9ee0..fd75567 100644 (file)
@@ -196,10 +196,6 @@ sub replace {
               ? shift
               : $self->replace_old;
 
-  if ( length($old->paycvv) && $self->paycvv =~ /^\s*[\*x]*\s*$/ ) {
-    $self->paycvv($old->paycvv);
-  }
-
   if ( $self->payby =~ /^(CARD|DCRD)$/
        && (    $self->payinfo =~ /xx/
             || $self->payinfo =~ /^\s*N\/A\s+\(tokenized\)\s*$/
@@ -221,6 +217,17 @@ sub replace {
     $self->payinfo($new_account.'@'.$new_aba);
   }
 
+  # don't preserve paycvv if it was passed blank and payinfo changed
+  unless ( $self->payby =~ /^(CARD|DCRD)$/
+       && $old->payinfo ne $self->payinfo
+       && $old->paymask ne $self->paymask
+       && $self->paycvv =~ /^\s*$/ )
+  {
+    if ( length($old->paycvv) && $self->paycvv =~ /^\s*[\*x]*\s*$/ ) {
+      $self->paycvv($old->paycvv);
+    }
+  }
+
   local($ignore_expired_card) = 1
     if $old->payby  =~ /^(CARD|DCRD)$/
     && $self->payby =~ /^(CARD|DCRD)$/
@@ -831,6 +838,9 @@ sub search_sql {
       ' LEFT JOIN cust_location AS '.$pre.'location '.
       'ON (cust_main.'.$pre.'locationnum = '.$pre.'location.locationnum) ';
   }
+  # always make referral available in results
+  #   (maybe we should be using FS::UI::Web::join_cust_main instead?)
+  $addl_from .= ' LEFT JOIN (select refnum, referral from part_referral) AS part_referral_x ON (cust_main.refnum = part_referral_x.refnum) ';
 
   my $count_query = "SELECT COUNT(*) FROM cust_payby $addl_from $extra_sql";
 
diff --git a/FS/bin/freeside-cdr-conexiant-import b/FS/bin/freeside-cdr-conexiant-import
new file mode 100755 (executable)
index 0000000..a79477c
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/perl
+
+use strict;
+
+use Cpanel::JSON::XS;
+use Getopt::Long;
+use LWP::UserAgent;
+use MIME::Base64;
+use Net::HTTPS::Any qw(https_post https_get);
+
+use FS::UID qw(adminsuidsetup);
+use FS::Record qw(qsearchs);
+use FS::cdr;
+use FS::cdr_batch;
+
+sub usage {
+"Usage:
+freeside-cdr-conexiant-import -h -u username -p apikey [-v] freesideuser
+
+Downloads any existing CDR files with the BilledCallsOnly flag and 
+imports records that have not been imported yet.  Silently skips 
+records that have already been imported.
+";
+}
+
+# should really be using a module for this
+`which unzip` or die "can't find unzip executable";
+
+my ($username,$password,$verbose);
+GetOptions(
+  "password=s"  => \$password,
+  "username=s"  => \$username,
+  "verbose"     => \$verbose,
+);
+
+my $fsuser = $ARGV[-1];
+
+die usage() unless $fsuser;
+
+adminsuidsetup($fsuser);
+
+my ( $page, $response, %reply_headers ) = https_post(
+  'host'    => 'api.conexiant.net',
+  'port'    => '443',
+  'path'    => '/v1/Cdrs/SearchCdrsDownloads',
+  'headers' => {
+    'Authorization' => 'Basic ' . MIME::Base64::encode("$username:$password",'')
+  },
+  'content' => '{}',
+);
+
+die "Bad response from conexiant server: $response"
+  unless $response =~ /^200/;
+
+my $result = decode_json($page);
+
+die "Error from conexiant: " . ($result->{'ErrorInfo'} || 'No error message')
+  unless $result->{'Success'};
+
+my $files = $result->{'Data'}->{'Result'};
+
+die "Unexpected results from conexiant, not an array"
+  unless ref($files) eq 'ARRAY';
+
+my $dir = $FS::UID::cache_dir. "/cache.". $FS::UID::datasrc;
+my $ua  = LWP::UserAgent->new;
+
+# Download files are created automatically at regular frequent intervals,
+# but they contain overlapping data.
+#
+# FS::cdr::conexiant automatically skips previously imported cdrs,
+# though if it does so for all records in a file, 
+# then batch_import thinks the file is empty
+foreach my $file (@$files) {
+  next unless $file->{'BilledCallsOnly'};
+  my $cdrbatch = 'conexiant-' . $file->{'Identifier'};
+  # files that were "empty" will unfortunately be re-downloaded,
+  # but the alternative is to leave an excess of empty batches in system,
+  # and re-downloading is harmless (all files expire after 48 hours anyway)
+  if (qsearchs('cdr_batch',{ 'cdrbatch' => $cdrbatch })) {
+    print "$cdrbatch already imported\n" if $verbose;
+    next;
+  }
+  if ($verbose) {
+    print "Downloading $cdrbatch\n".
+          "  Created ".$file->{'CreatedOn'}."\n".
+          "  Start   ".$file->{'QueryStart'}."\n".
+          "  End     ".$file->{'QueryEnd'}."\n".
+          "  Link    ".$file->{'ValidLink'}."\n";
+  }
+  my $zfh = new File::Temp( TEMPLATE => 'conexiant.XXXXXXXX',
+                           SUFFIX   => '.zip',
+                           DIR      => $dir,
+                         )
+    or die "can't open temporary file to store download: $!\n";
+  my $cfh = new File::Temp( TEMPLATE => 'conexiant.XXXXXXXX',
+                           SUFFIX   => '.csv',
+                           DIR      => $dir,
+                         )
+    or die "can't open temporary file to unzip download: $!\n";
+  # yeah, these files ain't secured in any way
+  my $response = $ua->get($file->{'ValidLink'}, ':content_file' => $zfh->filename);
+  unless ($response->is_success) {
+    die "Error downloading $cdrbatch: ".$response->status_line;
+  }
+  my $zfilename = $zfh->filename;
+  print $cfh `unzip -p $zfilename 'Conexiant Cdrs.csv'`;
+  seek($cfh,0,0);
+  print "Importing batch $cdrbatch\n" if $verbose;
+  my $error = FS::cdr::batch_import({
+    'batch_namevalue' => $cdrbatch,
+    'file'            => $cfh->filename,
+    'format'          => 'conexiant'
+  });
+  if ($error eq 'Empty file!') {
+    print "File contains no new cdrs, no batch created\n" if $verbose;
+    $error = '';
+  } elsif ($verbose && !$error) {
+    print "File successfully imported\n";
+  }
+  die "Error importing $cdrbatch: $error" if $error;
+}
+
+exit;
+
+
+
index 84eee1b..672c201 100755 (executable)
@@ -50,7 +50,6 @@ my @scalars = qw (
   all_tags
   all_pkg_classnums
   any_pkg_status
-  select_referral
 );
 
 for my $param ( @scalars ) {
index 2e8f67b..ba7c99a 100755 (executable)
     <& /elements/tr-select-cust-fields.html &>
 
     <TR>
-      <TD ALIGN="right" VALIGN="center"><% mt('Add advertising source column') |h %></TD>
-        <TD><INPUT TYPE="checkbox" NAME="select_referral"></TD>
-    </TR>
-
-    <TR>
       <TD ALIGN="right" VALIGN="center"><% mt('Add package columns') |h %></TD>
         <TD><INPUT TYPE="checkbox" NAME="flattened_pkgs"></TD>
     </TR>