X-Git-Url: http://git.freeside.biz/gitweb/?a=blobdiff_plain;f=FS%2FFS%2Fpart_export%2Fvoip_ms.pm;h=1eedd66acf9901fa1361698debf0049576e0a752;hb=ffa18709ee8a4d05e18d2d406cf73afe79e52524;hp=44ce908eb7a2a486a0126d0b0dedc8b6b528da2d;hpb=e3503e19a5a6c876f410903a3946dd9f1597aa46;p=freeside.git diff --git a/FS/FS/part_export/voip_ms.pm b/FS/FS/part_export/voip_ms.pm index 44ce908eb..1eedd66ac 100644 --- a/FS/FS/part_export/voip_ms.pm +++ b/FS/FS/part_export/voip_ms.pm @@ -7,12 +7,17 @@ use Tie::IxHash; use LWP::UserAgent; use URI; use URI::Escape; -use JSON; +use Cpanel::JSON::XS; use HTTP::Request::Common; use Cache::FileCache; +use FS::Record qw(dbh); +use FS::Misc::DateTime qw(parse_datetime); +use DateTime; our $me = '[voip.ms]'; -our $DEBUG = 2; +our $DEBUG = 0; +# our $DEBUG = 1; # log requests +# our $DEBUG = 2; # log requests and content of replies our $base_url = 'https://voip.ms/api/v1/rest.php'; # cache cities and provinces @@ -128,7 +133,7 @@ our %info = ( END ); -sub export_insert { +sub _export_insert { my($self, $svc_x) = (shift, shift); my $role = $self->svc_role($svc_x); @@ -157,7 +162,7 @@ sub export_insert { ''; } -sub export_replace { +sub _export_replace { my ($self, $svc_new, $svc_old) = @_; my $role = $self->svc_role($svc_new); my $error; @@ -170,7 +175,7 @@ sub export_replace { ''; } -sub export_delete { +sub _export_delete { my ($self, $svc_x) = (shift, shift); my $role = $self->svc_role($svc_x); if ( $role eq 'subacct' ) { @@ -199,7 +204,7 @@ sub export_delete { ''; } -sub export_suspend { +sub _export_suspend { my $self = shift; my $svc_x = shift; my $role = $self->svc_role($svc_x); @@ -210,7 +215,7 @@ sub export_suspend { ''; } -sub export_unsuspend { +sub _export_unsuspend { my $self = shift; my $svc_x = shift; my $role = $self->svc_role($svc_x); @@ -222,6 +227,9 @@ sub export_unsuspend { ''; } +################ +# PROVISIONING # +################ sub insert_subacct { my ($self, $svc_acct) = @_; @@ -400,6 +408,7 @@ sub subacct_content { } return { username => $svc_acct->username, + protocol => $self->option('protocol'), description => $desc, %auth, device_type => $self->option('device_type'), @@ -503,7 +512,7 @@ sub cache { my $province = shift; $CACHE ||= Cache::FileCache->new({ - 'cache_root' => $FS::UID::cache_dir.'/cache'.$FS::UID::datasrc, + 'cache_root' => $FS::UID::cache_dir.'/cache.'.$FS::UID::datasrc, 'namespace' => __PACKAGE__, 'default_expires_in' => $cache_timeout, }); @@ -587,6 +596,142 @@ sub reload_cache { } } +################ +# CALL DETAILS # +################ + +=item import_cdrs START, END + +Retrieves CDRs for calls in the date range from START to END and inserts them +as a new CDR batch. On success, returns a new cdr_batch object. On failure, +returns an error message. If there are no new CDRs, returns nothing. + +=cut + +sub import_cdrs { + my ($self, $start, $end) = @_; + $start ||= 0; # all CDRs ever + $end ||= time; + $DEBUG ||= $self->option('debug'); + + my $oldAutoCommit = $FS::UID::AutoCommit; + local $FS::UID::AutoCommit = 0; + + ($start, $end) = ($end, $start) if $end < $start; + $start = DateTime->from_epoch(epoch => $start, time_zone => 'local'); + $end = DateTime->from_epoch(epoch => $end, time_zone => 'local'); + my $accountnum = $self->option('account'); + my $cdr_batch; + # can't retrieve more than 92 days at a time + # actually, it's even less than that; on large batches their server + # sometimes cuts off in mid-sentence. so set the chunk size smaller. + while ( $start < $end ) { + + my $this_end = $start->clone; + $this_end->add(days => 14); + if ($this_end > $end) { + $this_end = $end; + } + + my $date_from = $start->strftime('%F'); + my $date_to = $this_end->strftime('%F'); + warn "retrieving CDRs from $date_from to $date_to\n" if $DEBUG; + my $timezone = $start->strftime('%z') / 100; # integer number of hours + my $result = $self->api_request('getCDR', { + date_from => $date_from, + date_to => $date_to, + answered => 1, + noanswer => 1, + busy => 1, + failed => 1, + timezone => $timezone, + }); + if ( $result->{status} eq 'success' ) { + if (!$cdr_batch) { + # then create one + my $cdrbatchname = 'voip_ms-' . $self->exportnum . '-' . $end->epoch; + $cdr_batch = FS::cdr_batch->new({ cdrbatch => $cdrbatchname }); + my $error = $cdr_batch->insert; + if ( $error ) { + dbh->rollback if $oldAutoCommit; + return $error; + } + } + + foreach ( @{ $result->{cdr} } ) { + my $uniqueid = $_->{uniqueid}; + # download ranges may overlap; avoid double-importing CDRs + if ( FS::cdr->row_exists("uniqueid = ?", $uniqueid) ) { + warn "skipped call with uniqueid = '$uniqueid' (already imported)\n" + if $DEBUG; + next; + } + # in this case, and probably in other cases in the near future, + # easier to do this than to create a FS::cdr::* format module + my $hash = { + disposition => $_->{disposition}, + calldate => $_->{date}, + dst => $_->{destination}, + uniqueid => $_->{uniqueid}, + upstream_price => $_->{total}, + upstream_dst_regionname => $_->{description}, + clid => $_->{callerid}, + duration => $_->{seconds}, + billsec => $_->{seconds}, + cdrbatchnum => $cdr_batch->cdrbatchnum, + }; + if ( $_->{date} ) { + $hash->{startdate} = parse_datetime($_->{date}); + } + if ( $_->{account} eq $accountnum ) { + # calls made from the master account, not a subaccount + # charged_party will be set to the source number + $hash->{charged_party} = ''; + } elsif ( $_->{account} =~ /^${accountnum}_(\w+)$/ ) { + $hash->{charged_party} = $1; + } else { + warn "skipped call with account = '$_->{account}'\n"; + next; + } + if ( $_->{callerid} =~ /<(\w+)>$/ ) { + $hash->{src} = $1; + } elsif ( $_->{callerid} =~ /^(\w+)$/ ) { + $hash->{src} = $1; + } else { + # else what? they don't have a source number anywhere else + warn "skipped call with unparseable callerid '$_->{callerid}'\n"; + next; + } + + my $cdr = FS::cdr->new($hash); + my $error = $cdr->insert; + if ( $error ) { + dbh->rollback if $oldAutoCommit; + return "$error (uniqueid $_->{uniqueid})"; + } + } # foreach @{ $result->{cdr} } + + } elsif ( $result->{status} eq 'no_cdr' ) { + # normal result if there are no CDRs, duh + next; # there may still be more CDRs later + } else { + dbh->rollback if $oldAutoCommit; + return "$me error retrieving CDRs: $result->{status}"; + } + + # we've retrieved and inserted this sub-batch of CDRs + $start->add(days => 15); + } # while ( $start < $end ) + + if ( $cdr_batch ) { + dbh->commit if $oldAutoCommit; + return $cdr_batch; + } else { + # no CDRs were ever found + return; + } +} + ############## # API ACCESS # ############## @@ -614,15 +759,21 @@ sub api_request { 'Accept' => 'text/json', ); - warn "$me $method\n" . $request->as_string ."\n" if $DEBUG; + warn "$me $method\n" if $DEBUG; + warn $request->as_string ."\n" if $DEBUG > 1; my $ua = LWP::UserAgent->new; my $response = $ua->request($request); - warn "$me received\n" . $response->as_string ."\n" if $DEBUG; + warn "$me received\n" . $response->as_string ."\n" if $DEBUG > 1; if ( !$response->is_success ) { return { status => $response->content }; } - return decode_json($response->content); + local $@; + my $decoded_response = eval { decode_json($response->content) }; + if ( $@ ) { + die "Error parsing response:\n" . $response->content . "\n\n"; + } + return $decoded_response; } =item api_insist METHOD, CONTENT