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