1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
|
#!/usr/bin/perl
use strict;
use Date::Format 'time2str';
use Date::Parse 'str2time';
use Getopt::Long;
use Cpanel::JSON::XS;
use Net::HTTPS::Any qw(https_post);
use Time::Local;
use FS::Record qw(qsearchs dbh);
use FS::UID qw(adminsuidsetup);
use FS::cdr;
use FS::cdr_batch;
sub usage {
"Usage:
freeside-cdr-portaone-import -h 'your.domain.com:443' -u switchusername -p switchpass
[-s startdate] [-e enddate] [-v] freesideuser
";
}
my ($host,$username,$password,$startdate,$enddate,$verbose);
GetOptions(
"enddate=s" => \$enddate,
"host=s" => \$host,
"password=s" => \$password,
"startdate=s" => \$startdate,
"username=s" => \$username,
"verbose" => \$verbose,
);
my $fsuser = $ARGV[-1];
die usage() unless $host && $password && $username && $fsuser;
adminsuidsetup($fsuser);
my $port = 443;
if ($host =~ /^(.*)\:(.*)$/) {
$host = $1;
$port = $2;
}
if ($startdate) {
$startdate = str2time($startdate) or die "Can't parse startdate $startdate";
$startdate = time2str("%Y-%m-%d %H:%M:%S",$startdate);
}
unless ($startdate) {
my $lastbatch = qsearchs({
'table' => 'cdr_batch',
'hashref' => { 'cdrbatch' => {op=>'like', value=>'portaone-import%'}},
'order_by' => 'ORDER BY _date DESC LIMIT 1',
});
$startdate = time2str("%Y-%m-%d %H:%M:%S", $lastbatch->_date) if $lastbatch;
}
$startdate ||= '2010-01-01 00:00:00'; #seems decently in the past
my @now = localtime();
my $now = timelocal(0,0,0,$now[3],$now[4],$now[5]); #most recent midnight
if ($enddate) {
$enddate = str2time($enddate) or die "Can't parse enddate $enddate";
$now = $enddate;
$enddate = time2str("%Y-%m-%d %H:%M:%S",$enddate);
}
$enddate ||= time2str("%Y-%m-%d %H:%M:%S",$now);
$FS::UID::AutoCommit = 0;
my $cdrbatchname = 'portaone-import-'. time2str('%Y/%m/%d-%T',$now);
die "Batch $cdrbatchname already exists, please specify a different end date. " . usage()
if FS::cdr_batch->row_exists('cdrbatch = ?', $cdrbatchname);
my $cdr_batch = new FS::cdr_batch({
'cdrbatch' => $cdrbatchname,
'_date' => $now,
});
my $error = $cdr_batch->insert;
if ($error) {
dbh->rollback;
die "Error creating batch: $error";
}
print "Downloading records from $startdate to $enddate\n" if $verbose;
my $auth_info; # needs to be declared undef for call_api
$auth_info = call_api('Session','login',{
'login' => $username,
'password' => $password,
});
my $results = {};
my $custlist = call_api('Customer','get_customer_list');
my @custnum = map { $_->{'i_customer'} } @{$custlist->{'customer_list'}};
foreach my $custnum (@custnum) {
print "Retrieving for customer $custnum\n" if $verbose;
my $step = 500; # too many records was crashing server, so we request in chunks
my $lastcount = $step; # to get the while loop rolling
my $totalcount = 0; # for verbose display only
my $offset = 0;
while ($lastcount == $step) {
my $xdrs = call_api('Customer','get_customer_xdrs',{
'i_customer' => $custnum,
'from_date' => $startdate,
'to_date' => $enddate,
'cdr_entity' => 'A',
'limit' => $step,
'offset' => $offset,
});
my @xdrs = @{$xdrs->{'xdr_list'}};
print "Retrieved ".@xdrs." records\n" if $verbose && @xdrs;
my $skipped = 0; # for verbose display only
foreach my $xdr (@xdrs) {
if ( FS::cdr->row_exists('uniqueid = ?', $xdr->{'i_xdr'}) ) {
$skipped += 1;
next;
}
my $desc = $xdr->{'country'};
if ($xdr->{'subdivision'}) {
$desc = ', ' . $desc if $desc;
$desc = $xdr->{'subdivision'} . $desc;
}
if ($xdr->{'description'}) {
$desc = ' (' . $desc . ')' if $desc;
$desc = $xdr->{'description'} . $desc;
}
my $cdr = FS::cdr->new ({
'cdrbatchnum' => $cdr_batch->cdrbatchnum,
'uniqueid' => $xdr->{'i_xdr'},
'src' => $xdr->{'CLI'},
'dst' => $xdr->{'CLD'},
'upstream_price' => $xdr->{'charged_amount'},
'startdate' => $xdr->{'unix_connect_time'},
'enddate' => $xdr->{'unix_disconnect_time'},
'accountcode' => $xdr->{'account_id'},
'billsec' => $xdr->{'charged_quantity'},
'upstream_dst_regionname' => $desc,
});
$error = $cdr->insert;
if ($error) {
dbh->rollback;
die "Error inserting cdr: $error";
}
} #foreach $xdr
print "Skipped $skipped duplicate records\n" if $verbose && $skipped;
$totalcount += @xdrs - $skipped;
$lastcount = @xdrs;
$offset += $step;
} #while $lastcount == $step
print scalar($totalcount) . " records inserted\n" if $verbose;
} #foreach $custnum
call_api('Session','logout',$auth_info);
### Full list of fields returned by API:
#i_xdr int The unique ID of the xdr record
#account_id int The unique ID of the account database record
#CLI string Calling Line Identification
#CLD string Called Line Identification
#charged_amount float Amount charged
#charged_quantity int Units charged
#country string Country
#subdivision string Country subdivision
#description string Destination description
#disconnect_cause string The code of disconnect cause
#bill_status string Call bill status
#disconnect_reason string Call disconnect reason
#connect_time dateTime Call connect time
#unix_connect_time int Call connect time (expressed in Unix time format - seconds since epoch)
#disconnect_time dateTime Call disconnect time
#unix_disconnect_time int Call disconnect time (expressed in Unix time format - seconds since epoch)
#bill_time dateTime Call bill time
#bit_flags int Extended information how the service was used; the integer field that should be treated as a bit-map. Each currently used bit is listed in the Transaction_Flag_Types table (bit_offset indicates position)
#call_recording_url string Path to recorded .wav files
#call_recording_server_url string URL to the recording server
dbh->commit;
exit;
sub call_api {
my ($service,$method,$params) = @_;
my %auth_info = $auth_info ? ('auth_info' => encode_json($auth_info)) : ();
$params ||= {};
print "Calling $service/$method\n" if $verbose;
my ( $page, $response, %reply_headers ) = https_post(
'host' => $host,
'port' => $port,
'path' => '/rest/'.$service.'/'.$method.'/',
'args' => [ %auth_info, 'params' => encode_json($params) ],
);
return decode_json($page) if $response eq '200 OK';
dbh->rollback;
if ($response =~ /^500/) {
my $error = decode_json($page);
die "Server returned error during $service/$method: ".$error->{'faultstring'}
if $error->{'faultcode'};
}
die "Bad response from server during $service/$method: $response";
}
|