rewrite CDRs for forwarded Asterisk calls to be billable, RT#3196
[freeside.git] / FS / FS / cdr.pm
index 79a687e..8307b28 100644 (file)
@@ -8,6 +8,7 @@ use Date::Parse;
 use Date::Format;
 use Time::Local;
 use FS::UID qw( dbh );
+use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
 use FS::cdr_type;
 use FS::cdr_calltype;
@@ -129,6 +130,8 @@ following fields are currently supported:
 
 =item freesidestatus - NULL, done (or something)
 
+=item freesiderewritestatus - NULL, done (or something)
+
 =item cdrbatch
 
 =back
@@ -227,18 +230,40 @@ sub check {
 #    || $self->ut_numbern('upstream_rateid')
 #    || $self->ut_numbern('svcnum')
 #    || $self->ut_textn('freesidestatus')
+#    || $self->ut_textn('freesiderewritestatus')
 #  ;
 #  return $error if $error;
 
   $self->calldate( $self->startdate_sql )
     if !$self->calldate && $self->startdate;
 
+  #was just for $format eq 'taqua' but can't see the harm... add something to
+  #disable if it becomes a problem
+  if ( $self->duration eq '' && $self->enddate && $self->startdate ) {
+    $self->duration( $self->enddate - $self->startdate  );
+  }
+  if ( $self->billsec eq '' && $self->enddate && $self->answerdate ) {
+    $self->billsec(  $self->enddate - $self->answerdate );
+  } 
+
+  my $conf = new FS::Conf;
+
   unless ( $self->charged_party ) {
-    if ( $self->dst =~ /^(\+?1)?8[02-8]{2}/ ) {
-      $self->charged_party($self->dst);
+
+    if ( $conf->exists('cdr-charged_party-accountcode') && $self->accountcode ){
+
+      $self->charged_party( $self->accountcode );
+
     } else {
-      $self->charged_party($self->src);
+
+      if ( $self->dst =~ /^(\+?1)?8[02-8]{2}/ ) {
+        $self->charged_party($self->dst);
+      } else {
+        $self->charged_party($self->src);
+      }
+
     }
+
   }
 
   #check the foreign keys even?
@@ -403,15 +428,23 @@ sub _convergent_format {
 
 my %export_names = (
   'convergent'      => {},
-  'simple'  => { 'name'           => 'Simple',
-                 'invoice_header' =>
-                     "Date,Time,Name,Destination,Duration,Price",
-               },
-  'simple2' => { 'name'           => 'Simple with source',
-                 'invoice_header' =>
-                     #"Date,Time,Name,Called From,Destination,Duration,Price",
-                     "Date,Time,Called From,Destination,Duration,Price",
-               },
+  'simple'  => {
+    'name'           => 'Simple',
+    'invoice_header' => "Date,Time,Name,Destination,Duration,Price",
+  },
+  'simple2' => {
+    'name'           => 'Simple with source',
+    'invoice_header' => "Date,Time,Called From,Destination,Duration,Price",
+                       #"Date,Time,Name,Called From,Destination,Duration,Price",
+  },
+  'default' => {
+    'name'           => 'Default',
+    'invoice_header' => 'Date,Time,Number,Destination,Duration,Price',
+  },
+  'source_default' => {
+    'name'           => 'Default with source',
+    'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price',
+  },
 );
 
 my %export_formats = (
@@ -436,7 +469,8 @@ my %export_formats = (
     'userfield',                                     #USER
     'dst',                                           #NUMBER_DIALED
     sub { sprintf('%.2fm', shift->billsec / 60 ) },  #DURATION
-    sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
+    #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
+    sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE
   ],
   'simple2' => [
     sub { time2str('%D', shift->calldate_unix ) },   #DATE
@@ -445,9 +479,36 @@ my %export_formats = (
     'dst',                                           #NUMBER_DIALED
     'src',                                           #called from
     sub { sprintf('%.2fm', shift->billsec / 60 ) },  #DURATION
-    sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
+    #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
+    sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE
+  ],
+  'default' => [
+
+    #DATE
+    sub { time2str('%D', shift->calldate_unix ) },
+          # #time2str("%Y %b %d - %r", $cdr->calldate_unix ),
+
+    #TIME
+    sub { time2str('%r', shift->calldate_unix ) },
+          # time2str("%c", $cdr->calldate_unix),  #XXX this should probably be a config option dropdown so they can select US vs- rest of world dates or whatnot
+
+    #DEST ("Number")
+    sub { my($cdr, %opt) = @_; $opt{pretty_dst} || $cdr->dst; },
+
+    #REGIONNAME ("Destination")
+    sub { my($cdr, %opt) = @_; $opt{dst_regionname}; },
+
+    #DURATION
+    sub { my($cdr, %opt) = @_;
+          $opt{minutes}. ( $opt{granularity} ? 'm' : ' call' );
+        },
+
+    #PRICE
+    sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; },
+
   ],
 );
+$export_formats{'source_default'} = [ 'src', @{ $export_formats{'default'} }, ];
 
 sub downstream_csv {
   my( $self, %opt ) = @_;
@@ -455,13 +516,17 @@ sub downstream_csv {
   my $format = $opt{'format'}; # 'convergent';
   return "Unknown format $format" unless exists $export_formats{$format};
 
+  #my $conf = new FS::Conf;
+  #$opt{'money_char'} ||= $conf->config('money_char') || '$';
+  $opt{'money_char'} ||= FS::Conf->new->config('money_char') || '$';
+
   eval "use Text::CSV_XS;";
   die $@ if $@;
   my $csv = new Text::CSV_XS;
 
   my @columns =
     map {
-          ref($_) ? &{$_}($self) : $self->$_();
+          ref($_) ? &{$_}($self, %opt) : $self->$_();
         }
     @{ $export_formats{$format} };
 
@@ -554,7 +619,7 @@ sub import_formats {
 sub _cdr_min_parser_maker {
   my $field = shift;
   my @fields = ref($field) ? @$field : ($field);
-  @fields = qw( billsec duration ) unless scalar(@fields);
+  @fields = qw( billsec duration ) unless scalar(@fields) && $fields[0];
   return sub {
     my( $cdr, $min ) = @_;
     my $sec = eval { _cdr_min_parse($min) };
@@ -570,11 +635,12 @@ sub _cdr_min_parse {
 
 sub _cdr_date_parser_maker {
   my $field = shift;
+  my @fields = ref($field) ? @$field : ($field);
   return sub {
-    my( $cdr, $date ) = @_;
-    #$cdr->$field( _cdr_date_parse($date) );
-    eval { $cdr->$field( _cdr_date_parse($date) ); };
-    die "error parsing date for $field from $date: $@\n" if $@;
+    my( $cdr, $datestring ) = @_;
+    my $unixdate = eval { _cdr_date_parse($datestring) };
+    die "error parsing date for @fields from $datestring: $@\n" if $@;
+    $cdr->$_($unixdate) foreach @fields;
   };
 }
 
@@ -608,146 +674,80 @@ Imports CDR records.  Available options are:
 
 =over 4
 
-=item filehandle
-
-=item format
-
-=back
-
-=cut
-
-sub batch_import {
-  my $param = shift;
-
-  my $fh = $param->{filehandle};
-  my $format = $param->{format};
-  my $cdrbatch = $param->{cdrbatch};
-
-  return "Unknown format $format"
-    unless exists( $cdr_info{$format} )
-        && exists( $cdr_info{$format}->{'import_fields'} );
-
-  my $info = $cdr_info{$format};
+=item file
 
-  my $type = exists($info->{'type'}) ? lc($info->{'type'}) : 'csv';
-
-  my $parser;
-  if ( $type eq 'csv' ) {
-    eval "use Text::CSV_XS;";
-    die $@ if $@;
-    my %attr = ();
-    foreach ( grep exists($info->{$_}), qw( sep_char ) ) {
-      $attr{$_} = $info->{$_};
-    }
-    $parser = new Text::CSV_XS \%attr;
-  } elsif ( $type eq 'fixedlength' ) {
-    eval "use Parse::FixedLength;";
-    die $@ if $@;
-    $parser = new Parse::FixedLength $info->{'fixedlength_format'};
-  } else {
-    die "Unknown CDR format type $type for format $format\n";
-  }
+Filename
 
-  my $imported = 0;
-  #my $columns;
-
-  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;
-
-  my $header_lines = exists($info->{'header'}) ? $info->{'header'} : 0;
+=item format
 
-  my $line;
-  while ( defined($line=<$fh>) ) {
+=item params
 
-    next if $header_lines-- > 0; #&& $line =~ /^[\w, "]+$/ 
+Hash reference of preset fields, typically cdrbatch
 
-    my @columns = ();
-    if ( $type eq 'csv' ) {
+=item empty_ok
 
-      $parser->parse($line) or do {
-        $dbh->rollback if $oldAutoCommit;
-        return "can't parse: ". $parser->error_input();
-      };
+Set true to prevent throwing an error on empty imports
 
-      @columns = $parser->fields();
+=back
 
-    } elsif ( $type eq 'fixedlength' ) {
+=cut
 
-      @columns = $parser->parse($line);
+my %import_options = (
+  'table'   => 'cdr',
 
-    } else {
-      die "Unknown CDR format type $type for format $format\n";
-    }
+  'formats' => { map { $_ => $cdr_info{$_}->{'import_fields'}; }
+                     keys %cdr_info
+               },
 
-    #warn join('-',@columns);
+                          #drop the || 'csv' to allow auto xls for csv types?
+  'format_types' => { map { $_ => ( lc($cdr_info{$_}->{'type'}) || 'csv' ); }
+                          keys %cdr_info
+                    },
 
-    if ( $format eq 'simple' ) { #should be a callback or opt in FS::cdr::simple
-      @columns = map { s/^ +//; $_; } @columns;
-    }
+  'format_headers' => { map { $_ => ( $cdr_info{$_}->{'header'} || 0 ); }
+                            keys %cdr_info
+                      },
 
-    my @later = ();
-    my %cdr =
-      map {
+  'format_sep_chars' => { map { $_ => $cdr_info{$_}->{'sep_char'}; }
+                              keys %cdr_info
+                        },
 
-        my $field_or_sub = $_;
-        if ( ref($field_or_sub) ) {
-          push @later, $field_or_sub, shift(@columns);
-          ();
-        } else {
-          ( $field_or_sub => shift @columns );
-        }
+  'format_fixedlength_formats' =>
+    { map { $_ => $cdr_info{$_}->{'fixedlength_format'}; }
+          keys %cdr_info
+    },
+);
 
-      }
-      @{ $info->{'import_fields'} }
-    ;
-    $cdr{cdrbatch} = $cdrbatch;
+sub _import_options {
+  \%import_options;
+}
 
-    my $cdr = new FS::cdr ( \%cdr );
+sub batch_import {
+  my $opt = shift;
 
-    while ( scalar(@later) ) {
-      my $sub = shift @later;
-      my $data = shift @later;
-      &{$sub}($cdr, $data);  # $cdr->&{$sub}($data); 
-    }
+  my $iopt = _import_options;
+  $opt->{$_} = $iopt->{$_} foreach keys %$iopt;
 
-    if ( $format eq 'taqua' ) { #should be a callback or opt in FS::cdr::taqua
-      if ( $cdr->enddate && $cdr->startdate  ) { #a bit more?
-        $cdr->duration( $cdr->enddate - $cdr->startdate  );
-      }
-      if ( $cdr->enddate && $cdr->answerdate ) { #a bit more?
-        $cdr->billsec(  $cdr->enddate - $cdr->answerdate );
-      } 
-    }
+  FS::Record::batch_import( $opt );
 
-    my $error = $cdr->insert;
-    if ( $error ) {
-      $dbh->rollback if $oldAutoCommit;
-      return $error;
+}
 
-      #or just skip?
-      #next;
-    }
+=item process_batch_import
 
-    $imported++;
-  }
+=cut
 
-  $dbh->commit or die $dbh->errstr if $oldAutoCommit;
+sub process_batch_import {
+  my $job = shift;
 
-  #might want to disable this if we skip records for any reason...
-  return "Empty file!" unless $imported || $param->{empty_ok};
+  my $opt = _import_options;
+  $opt->{'params'} = [ 'format', 'cdrbatch' ];
 
-  '';
+  FS::Record::process_batch_import( $job, $opt, @_ );
 
 }
+#  if ( $format eq 'simple' ) { #should be a callback or opt in FS::cdr::simple
+#    @columns = map { s/^ +//; $_; } @columns;
+#  }
 
 =back