Merge branch '16824'
[freeside.git] / FS / FS / cdr.pm
index cb866ca..3a6b01b 100644 (file)
@@ -5,10 +5,12 @@ use vars qw( @ISA @EXPORT_OK $DEBUG $me
              $conf $cdr_prerate %cdr_prerate_cdrtypenums
            );
 use Exporter;
+use List::Util qw(first min);
 use Tie::IxHash;
 use Date::Parse;
 use Date::Format;
 use Time::Local;
+use List::Util qw( first min );
 use FS::UID qw( dbh );
 use FS::Conf;
 use FS::Record qw( qsearch qsearchs );
@@ -85,6 +87,10 @@ following fields are currently supported:
 
 =item lastdata - Last application data
 
+=item src_ip_addr - Source IP address (dotted quad, zero-filled)
+
+=item dst_ip_addr - Destination IP address (same)
+
 =item startdate - Start of call (UNIX-style integer timestamp)
 
 =item answerdate - Answer time of call (UNIX-style integer timestamp)
@@ -146,7 +152,7 @@ following fields are currently supported:
 
 =item svcnum - Link to customer service (see L<FS::cust_svc>)
 
-=item freesidestatus - NULL, processing-tiered, rated, done
+=item freesidestatus - NULL, processing-tiered, rated, done, skipped, no-charge, failed
 
 =item freesiderewritestatus - NULL, done, skipped
 
@@ -185,6 +191,8 @@ sub table_info {
         'dstchannel'            => 'Destination channel',
         #'lastapp'               => '',
         #'lastdata'              => '',
+        'src_ip_addr'           => 'Source IP',
+        'dst_ip_addr'           => 'Dest. IP',
         'startdate'             => 'Start date',
         'answerdate'            => 'Answer date',
         'enddate'               => 'End date',
@@ -425,7 +433,9 @@ sub set_status {
 
 Sets the status and rated price.
 
-Available options are: inbound, rated_seconds, rated_minutes, rated_classnum, rated_ratename
+Available options are: inbound, rated_pretty_dst, rated_regionname,
+rated_seconds, rated_minutes, rated_granularity, rated_ratedetailnum,
+rated_classnum, rated_ratename.
 
 If there is an error, returns the error, otherwise returns false.
 
@@ -535,7 +545,7 @@ sub rate_prefix {
                                          );
   if ( $reason ) {
     warn "not charging for CDR ($reason)\n" if $DEBUG;
-    return $self->set_status_and_rated_price( 'rated',
+    return $self->set_status_and_rated_price( 'skipped',
                                               0,
                                               $opt{'svcnum'},
                                             );
@@ -688,8 +698,11 @@ sub rate_prefix {
   if ( !exists($interval_cache{$regionnum}) ) {
     my @intervals = (
       sort { $a->stime <=> $b->stime }
-      map { my $r = $_->rate_time; $r ? $r->intervals : () }
-      $rate->rate_detail
+        map { $_->rate_time->intervals }
+          qsearch({ 'table'     => 'rate_detail',
+                    'hashref'   => { 'ratenum' => $rate->ratenum },
+                    'extra_sql' => 'AND ratetimenum IS NOT NULL',
+                 })
     );
     $interval_cache{$regionnum} = \@intervals;
     warn "  cached ".scalar(@intervals)." interval(s)\n"
@@ -832,10 +845,12 @@ sub rate_prefix {
 sub rate_upstream_simple {
   my( $self, %opt ) = @_;
 
-  $self->set_status_and_rated_price( 'rated',
-                                     sprintf('%.3f', $self->upstream_price),
-                                     $opt{'svcnum'},
-                                   );
+  $self->set_status_and_rated_price(
+    'rated',
+    sprintf('%.3f', $self->upstream_price),
+    $opt{'svcnum'},
+    'rated_classnum' => $self->calltypenum,
+  );
 }
 
 sub rate_single_price {
@@ -873,10 +888,13 @@ sub rate_single_price {
     sprintf('%.4f', ( $part_pkg->option_cacheable('min_charge') * $charge_min )
                     + 0.0000000001 ); #so 1.00005 rounds to 1.0001
 
-  $self->set_status_and_rated_price( 'rated',
-                                     $charge,
-                                     $opt{'svcnum'},
-                                   );
+  $self->set_status_and_rated_price(
+    'rated',
+    $charge,
+    $opt{'svcnum'},
+    'rated_granularity' => $granularity,
+    'rated_seconds'     => $seconds,
+  );
 
 }
 
@@ -999,6 +1017,10 @@ my %export_names = (
     'invoice_header' => "Date,Time,Called From,Destination,Duration,Price",
                        #"Date,Time,Name,Called From,Destination,Duration,Price",
   },
+  'accountcode_simple' => {
+    'name'           => 'Simple with accountcode',
+    'invoice_header' => "Date,Time,Called From,Account,Duration,Price",
+  },
   'basic' => {
     'name'           => 'Basic',
     'invoice_header' => "Date/Time,Called Number,Min/Sec,Price",
@@ -1020,13 +1042,17 @@ my %export_names = (
     'invoice_header' => 'Caller,Date,Time,Number,Destination,Duration,Price',
   },
   'sum_duration' => {
-    'name'           => 'Summary (one line per service, with duration)',
+    'name'           => 'Summary, one line per service',
     'invoice_header' => 'Caller,Rate,Calls,Minutes,Price',
   },
   'sum_count' => {
-    'name'           => 'Summary (one line per service, with count)',
+    'name'           => 'Number of calls, one line per service',
     'invoice_header' => 'Caller,Rate,Messages,Price',
   },
+  'sum_duration_prefix' => {
+    'name'           => 'Summary, one line per destination prefix',
+    'invoice_header' => 'Caller,Rate,Calls,Minutes,Price',
+  },
 );
 
 my %export_formats = ();
@@ -1092,6 +1118,14 @@ sub export_formats {
       #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE
       $price_sub,
     ],
+    'accountcode_simple' => [
+      sub { time2str($date_format, shift->calldate_unix ) },   #DATE
+      sub { time2str('%r', shift->calldate_unix ) },   #TIME
+      'src',                                           #called from
+      'accountcode',                                   #NUMBER_DIALED
+      $duration_sub,                                   #DURATION
+      $price_sub,
+    ],
     'sum_duration' => [ 
       # for summary formats, the CDR is a fictitious object containing the 
       # total billsec and the phone number of the service
@@ -1215,6 +1249,8 @@ as keys (for use with part_pkg::voip_cdr) and "pretty" format names as values.
 
 =cut
 
+# in the future, load this dynamically from detail_format classes
+
 sub invoice_formats {
   map { ($_ => $export_names{$_}->{'name'}) }
     grep { $export_names{$_}->{'invoice_header'} }
@@ -1241,6 +1277,7 @@ CDR reprocessing.
 
 sub clear_status {
   my $self = shift;
+  my %opt = @_;
 
   local $SIG{HUP} = 'IGNORE';
   local $SIG{INT} = 'IGNORE';
@@ -1254,7 +1291,9 @@ sub clear_status {
   my $dbh = dbh;
 
   if ( $cdr_prerate && $cdr_prerate_cdrtypenums{$self->cdrtypenum}
+       && $self->rated_ratedetailnum #avoid putting old CDRs back in "rated"
        && $self->freesidestatus eq 'done'
+       && ! $opt{'rerate'}
      )
   { #special case
     $self->freesidestatus('rated');
@@ -1549,6 +1588,31 @@ sub _upgrade_data {
 
 }
 
+=item ip_addr_sql FIELD RANGE
+
+Returns an SQL condition to search for CDRs with an IP address 
+within RANGE.  FIELD is either 'src_ip_addr' or 'dst_ip_addr'.  RANGE 
+should be in the form "a.b.c.d-e.f.g.h' (dotted quads), where any of 
+the leftmost octets of the second address can be omitted if they're 
+the same as the first address.
+
+=cut
+
+sub ip_addr_sql {
+  my $class = shift;
+  my ($field, $range) = @_;
+  $range =~ /^[\d\.-]+$/ or die "bad ip address range '$range'";
+  my @r = split('-', $range);
+  my @saddr = split('\.', $r[0] || '');
+  my @eaddr = split('\.', $r[1] || '');
+  unshift @eaddr, (undef) x (4 - scalar @eaddr);
+  for(0..3) {
+    $eaddr[$_] = $saddr[$_] if !defined $eaddr[$_];
+  }
+  "$field >= '".sprintf('%03d.%03d.%03d.%03d', @saddr) . "' AND ".
+  "$field <= '".sprintf('%03d.%03d.%03d.%03d', @eaddr) . "'";
+}
+
 =back
 
 =head1 BUGS