'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)',
'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');
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;
}
" 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;
}
--- /dev/null
+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;
bool
-=item select_referral
-
-bool, join to part_referral and select part_referral.referral
-
=back
=cut
'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 = (
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 = '';
? 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*$/
$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)$/
' 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";
--- /dev/null
+#!/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;
+
+
+
all_tags
all_pkg_classnums
any_pkg_status
- select_referral
);
for my $param ( @scalars ) {
<& /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>