use detail formats to show CDRs in selfservice, #14923
[freeside.git] / FS / FS / cdr.pm
index 002e2d2..1e40881 100644 (file)
@@ -130,9 +130,9 @@ following fields are currently supported:
 
 =item svcnum - Link to customer service (see L<FS::cust_svc>)
 
-=item freesidestatus - NULL, done (or something)
+=item freesidestatus - NULL, processing-tiered, done
 
-=item freesiderewritestatus - NULL, done (or something)
+=item freesiderewritestatus - NULL, done, skipped
 
 =item cdrbatch
 
@@ -285,7 +285,7 @@ sub check {
 #  ;
 #  return $error if $error;
 
-  for my $f ( grep { $self->$_ =~ /[a-z ]/i } qw( startdate enddate ) ) {
+  for my $f ( grep { $self->$_ =~ /\D/ } qw(startdate answerdate enddate)){
     $self->$f( str2time($self->$f) );
   }
 
@@ -401,13 +401,12 @@ error, otherwise returns false.
 
 sub set_status_and_rated_price {
   my($self, $status, $rated_price, $svcnum, %opt) = @_;
-  if($opt{'inbound'}) {
-    my $term = qsearchs('cdr_termination', {
-        acctid   => $self->acctid, 
-        termpart => 1 # inbound
-    });
+
+  if ($opt{'inbound'}) {
+
+    my $term = $self->cdr_termination( 1 ); #1: inbound
     my $error;
-    if($term) {
+    if ( $term ) {
       warn "replacing existing cdr status (".$self->acctid.")\n" if $term;
       $error = $term->delete;
       return $error if $error;
@@ -417,18 +416,47 @@ sub set_status_and_rated_price {
         termpart    => 1,
         rated_price => $rated_price,
         status      => $status,
-        svcnum      => $svcnum,
     });
+    $term->rated_seconds($opt{rated_seconds}) if exists($opt{rated_seconds});
+    $term->rated_minutes($opt{rated_minutes}) if exists($opt{rated_minutes});
+    $term->svcnum($svcnum) if $svcnum;
     return $term->insert;
-  }
-  else {
+
+  } else {
+
     $self->freesidestatus($status);
     $self->rated_price($rated_price);
+    $self->rated_seconds($opt{rated_seconds}) if exists($opt{rated_seconds});
+    $self->rated_minutes($opt{rated_minutes}) if exists($opt{rated_minutes});
     $self->svcnum($svcnum) if $svcnum;
     return $self->replace();
+
   }
 }
 
+=item cdr_termination [ TERMPART ]
+
+=cut
+
+sub cdr_termination {
+  my $self = shift;
+
+  if ( scalar(@_) && $_[0] ) {
+    my $termpart = shift;
+
+    qsearchs('cdr_termination', { acctid   => $self->acctid,
+                                  termpart => $termpart,
+                                }
+            );
+
+  } else {
+
+    qsearch('cdr_termination', { acctid => $self->acctid, } );
+
+  }
+
+}
+
 =item calldate_unix 
 
 Parses the calldate in SQL string format and returns a UNIX timestamp.
@@ -525,6 +553,10 @@ my %export_names = (
     'invoice_header' => "Date,Time,Called From,Destination,Duration,Price",
                        #"Date,Time,Name,Called From,Destination,Duration,Price",
   },
+  'basic' => {
+    'name'           => 'Basic',
+    'invoice_header' => "Date/Time,Called Number,Min/Sec,Price",
+  },
   'default' => {
     'name'           => 'Default',
     'invoice_header' => 'Date,Time,Number,Destination,Duration,Price',
@@ -537,6 +569,18 @@ my %export_names = (
     'name'           => 'Default plus accountcode',
     'invoice_header' => 'Date,Time,Account,Number,Destination,Duration,Price',
   },
+  'description_default' => {
+    'name'           => 'Default with description field as destination',
+    'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price',
+  },
+  'sum_duration' => {
+    'name'           => 'Summary (one line per service, with duration)',
+    'invoice_header' => 'Caller,Calls,Minutes,Price',
+  },
+  'sum_count' => {
+    'name'           => 'Summary (one line per service, with count)',
+    'invoice_header' => 'Caller,Messages,Price',
+  },
 );
 
 my %export_formats = ();
@@ -548,23 +592,40 @@ sub export_formats {
   my $conf = new FS::Conf;
   my $date_format = $conf->config('date_format') || '%m/%d/%Y';
 
-  # This is now smarter, and shows the call duration in the 
-  # largest units that accurately reflect the granularity.
+  # call duration in the largest units that accurately reflect the  granularity
   my $duration_sub = sub {
     my($cdr, %opt) = @_;
     my $sec = $opt{seconds} || $cdr->billsec;
-    if ( length($opt{granularity}) && 
+    if ( defined $opt{granularity} && 
          $opt{granularity} == 0 ) { #per call
       return '1 call';
     }
-    elsif ( $opt{granularity} == 60 ) {#full minutes
-      return sprintf("%.0fm",$sec/60);
+    elsif ( defined $opt{granularity} && $opt{granularity} == 60 ) {#full minutes
+      my $min = int($sec/60);
+      $min++ if $sec%60;
+      return $min.'m';
     }
     else { #anything else
       return sprintf("%dm %ds", $sec/60, $sec%60);
     }
   };
 
+  my $price_sub = sub {
+    my ($cdr, %opt) = @_;
+    my $price;
+    if ( defined($opt{charge}) ) {
+      $price = $opt{charge};
+    }
+    elsif ( $opt{inbound} ) {
+      my $term = $cdr->cdr_termination(1); # 1 = inbound
+      $price = $term->rated_price if defined $term;
+    }
+    else {
+      $price = $cdr->rated_price;
+    }
+    length($price) ? ($opt{money_char} . $price) : '';
+  };
+
   %export_formats = (
     'simple' => [
       sub { time2str($date_format, shift->calldate_unix ) },   #DATE
@@ -573,7 +634,7 @@ sub export_formats {
       'dst',                                           #NUMBER_DIALED
       $duration_sub,                                   #DURATION
       #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
-      sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE
+      $price_sub,
     ],
     'simple2' => [
       sub { time2str($date_format, shift->calldate_unix ) },   #DATE
@@ -583,7 +644,26 @@ sub export_formats {
       'dst',                                           #NUMBER_DIALED
       $duration_sub,                                   #DURATION
       #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
-      sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE
+      $price_sub,
+    ],
+    'sum_duration' => [ 
+      # for summary formats, the CDR is a fictitious object containing the 
+      # total billsec and the phone number of the service
+      'src',
+      sub { my($cdr, %opt) = @_; $opt{count} },
+      sub { my($cdr, %opt) = @_; int($opt{seconds}/60).'m' },
+      $price_sub,
+    ],
+    'sum_count' => [
+      'src',
+      sub { my($cdr, %opt) = @_; $opt{count} },
+      $price_sub,
+    ],
+    'basic' => [
+      sub { time2str('%d %b - %I:%M %p', shift->calldate_unix) },
+      'dst',
+      $duration_sub,
+      $price_sub,
     ],
     'default' => [
 
@@ -605,8 +685,7 @@ sub export_formats {
       $duration_sub,
 
       #PRICE
-      sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; },
-
+      $price_sub,
     ],
   );
   $export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ];
@@ -615,10 +694,31 @@ sub export_formats {
       'accountcode',
       @{ $export_formats{'default'} }[2..5],
     ];
+  my @default = @{ $export_formats{'default'} };
+  $export_formats{'description_default'} = 
+    [ 'src', @default[0..2], 
+      sub { my($cdr, %opt) = @_; $cdr->description },
+      @default[4,5] ];
 
-  %export_formats
+  return %export_formats;
 }
 
+=item downstream_csv OPTION => VALUE ...
+
+Returns a string of formatted call details for display on an invoice.
+
+Options:
+
+format
+
+charge
+
+seconds
+
+granularity
+
+=cut
+
 sub downstream_csv {
   my( $self, %opt ) = @_;
 
@@ -640,6 +740,8 @@ sub downstream_csv {
         }
     @{ $formats{$format} };
 
+  return @columns if defined $opt{'keeparray'};
+
   my $status = $csv->combine(@columns);
   die "FS::CDR: error combining ". $csv->error_input(). "into downstream CSV"
     unless $status;
@@ -678,6 +780,49 @@ sub invoice_header {
   $export_names{$format}->{'invoice_header'};
 }
 
+=item clear_status 
+
+Clears cdr and any associated cdr_termination statuses - used for 
+CDR reprocessing.
+
+=cut
+
+sub clear_status {
+  my $self = shift;
+
+  local $SIG{HUP} = 'IGNORE';
+  local $SIG{INT} = 'IGNORE';
+  local $SIG{QUIT} = 'IGNORE';
+  local $SIG{TERM} = 'IGNORE';
+  local $SIG{TSTP} = 'IGNORE';
+  local $SIG{PIPE} = 'IGNORE';
+
+  my $oldAutoCommit = $FS::UID::AutoCommit;
+  local $FS::UID::AutoCommit = 0;
+  my $dbh = dbh;
+
+  $self->freesidestatus('');
+  my $error = $self->replace;
+  if ( $error ) {
+    $dbh->rollback if $oldAutoCommit;
+    return $error;
+  } 
+
+  foreach my $cdr_termination ( $self->cdr_termination ) {
+      #$cdr_termination->status('');
+      #$error = $cdr_termination->replace;
+      $error = $cdr_termination->delete;
+      if ( $error ) {
+        $dbh->rollback if $oldAutoCommit;
+        return $error;
+      } 
+  }
+  
+  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+
+  '';
+}
+
 =item import_formats
 
 Returns an ordered list of key value pairs containing import format names
@@ -788,6 +933,10 @@ sub _cdr_date_parse {
   } elsif ( $date  =~ /^\s*(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/ ) {
     # WIP: 20100329121420
     ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+  } elsif ( $date =~ /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/) {
+    # Telos
+    ($year, $mon, $day, $hour, $min, $sec) = ( $1, $2, $3, $4, $5, $6 );
+    $options{gmt} = 1;
   } else {
      die "unparsable date: $date"; #maybe we shouldn't die...
   }
@@ -838,7 +987,7 @@ my %import_options = (
                },
 
                           #drop the || 'csv' to allow auto xls for csv types?
-  'format_types' => { map { $_ => ( lc($cdr_info{$_}->{'type'}) || 'csv' ); }
+  'format_types' => { map { $_ => lc($cdr_info{$_}->{'type'} || 'csv'); }
                           keys %cdr_info
                     },
 
@@ -855,6 +1004,11 @@ my %import_options = (
           keys %cdr_info
     },
 
+  'format_xml_formats' =>
+    { map { $_ => $cdr_info{$_}->{'xml_format'}; }
+          keys %cdr_info
+    },
+
   'format_row_callbacks' => { map { $_ => $cdr_info{$_}->{'row_callback'}; }
                                   keys %cdr_info
                             },
@@ -870,6 +1024,14 @@ sub batch_import {
   my $iopt = _import_options;
   $opt->{$_} = $iopt->{$_} foreach keys %$iopt;
 
+  if ( defined $opt->{'cdrtypenum'} ) {
+        $opt->{'preinsert_callback'} = sub {
+                my($record,$param) = (shift,shift);
+                $record->cdrtypenum($opt->{'cdrtypenum'});
+                '';
+        };
+  }
+
   FS::Record::batch_import( $opt );
 
 }