+################
+# CALL DETAILS #
+################
+
+=item import_cdrs START, END
+
+Retrieves CDRs for calls in the date range from START to END and inserts them
+as a 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;
+ $end ||= time;
+
+ 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');
+ $end->subtract(seconds => 1); # filter by >= and <= only, so...
+
+ # a little different from the usual: we have to fetch these subscriber by
+ # subscriber, not all at once.
+ my @svcs = qsearch({
+ 'table' => 'svc_acct',
+ 'addl_from' => ' JOIN cust_svc USING (svcnum)' .
+ ' JOIN export_svc USING (svcpart)',
+ 'extra_sql' => ' WHERE export_svc.role = \'subscriber\''.
+ ' AND export_svc.exportnum = '.$self->exportnum
+ });
+ my $cdr_batch;
+ my @args = ( 'start_ge' => $start->iso8601,
+ 'start_le' => $end->iso8601,
+ );
+
+ my $error;
+ SVC: foreach my $svc (@svcs) {
+ my $subscriber = $self->get_subscriber($svc);
+ if (!$subscriber) {
+ warn "$me user ".$svc->label." is not configured on the SIP server.\n";
+ next;
+ }
+ my $id = $subscriber->{id};
+ my @calls;
+ try {
+ # alias_field tells "calllists" which field from the source and
+ # destination to use as the "own_cli" and "other_cli" of the call.
+ # "user" = username@domain.
+ @calls = $self->api_query('calllists', [
+ 'subscriber_id' => $id,
+ 'alias_field' => 'user',
+ @args
+ ]);
+ } catch {
+ $error = "$me $_ (retrieving records for ".$svc->label.")";
+ };
+
+ if (@calls and !$cdr_batch) {
+ # create a cdr_batch if needed
+ my $cdrbatchname = 'sipwise-' . $self->exportnum . '-' . $end->epoch;
+ $cdr_batch = FS::cdr_batch->new({ cdrbatch => $cdrbatchname });
+ $error = $cdr_batch->insert;
+ }
+
+ last SVC if $error;
+
+ foreach my $c (@calls) {
+ # avoid double-importing
+ my $uniqueid = $c->{call_id};
+ if ( FS::cdr->row_exists("uniqueid = ?", $uniqueid) ) {
+ warn "skipped call with uniqueid = '$uniqueid' (already imported)\n"
+ if $DEBUG;
+ next;
+ }
+ my $src = $c->{own_cli};
+ my $dst = $c->{other_cli};
+ if ( $c->{direction} eq 'in' ) { # then reverse them
+ ($src, $dst) = ($dst, $src);
+ }
+ # parse duration from H:MM:SS format
+ my $duration;
+ if ( $c->{duration} =~ /^(\d+):(\d+):(\d+)$/ ) {
+ $duration = $3 + (60 * $2) + (3600 * $1);
+ } else {
+ $error = "call $uniqueid: unparseable duration '".$c->{duration}."'";
+ }
+
+ # use the username@domain label for src and/or dst if possible
+ my $cdr = FS::cdr->new({
+ uniqueid => $uniqueid,
+ upstream_price => $c->{customer_cost},
+ startdate => parse_datetime($c->{start_time}),
+ disposition => $c->{status},
+ duration => $duration,
+ billsec => $duration,
+ src => $src,
+ dst => $dst,
+ });
+ $error ||= $cdr->insert;
+ last SVC if $error;
+ }
+ } # foreach $svc
+
+ if ( $error ) {
+ dbh->rollback if $oldAutoCommit;
+ return $error;
+ } elsif ( $cdr_batch ) {
+ dbh->commit if $oldAutoCommit;
+ return $cdr_batch;
+ } else { # no CDRs
+ return;
+ }
+}
+