localize CDR column headings, #27276
[freeside.git] / FS / FS / part_pkg / voip_cdr.pm
index b92cb47..1370c92 100644 (file)
@@ -1,10 +1,12 @@
 package FS::part_pkg::voip_cdr;
+use base qw( FS::part_pkg::recur_Common );
 
 use strict;
-use base qw( FS::part_pkg::recur_Common );
 use vars qw( $DEBUG %info );
-use Date::Format;
+use List::Util qw(first min);
 use Tie::IxHash;
+use Date::Format;
+use Text::CSV_XS;
 use FS::Conf;
 use FS::Record qw(qsearchs qsearch);
 use FS::cdr;
@@ -12,9 +14,6 @@ use FS::rate;
 use FS::rate_prefix;
 use FS::rate_detail;
 
-use List::Util qw(first min);
-
-
 $DEBUG = 0;
 
 tie my %cdr_svc_method, 'Tie::IxHash',
@@ -50,6 +49,11 @@ tie my %unrateable_opts, 'Tie::IxHash',
   2  => 'Flag for later review',
 ;
 
+tie my %detail_formats, 'Tie::IxHash',
+  '' => '',
+  FS::cdr::invoice_formats()
+;
+
 %info = (
   'name' => 'VoIP rating by plan of CDR records in an internal (or external) SQL table',
   'shortname' => 'VoIP/telco CDR rating (standard)',
@@ -145,7 +149,7 @@ tie my %unrateable_opts, 'Tie::IxHash',
                         'type' => 'checkbox',
                       },
 
-    'use_carrierid' => { 'name' => 'Only charge for CDRs where the Carrier ID is set to: ',
+    'use_carrierid' => { 'name' => 'Only charge for CDRs where the Carrier ID is set to any of these (comma-separated) values: ',
                          },
 
     'use_cdrtypenum' => { 'name' => 'Only charge for CDRs where the CDR Type is set to: ',
@@ -207,12 +211,25 @@ tie my %unrateable_opts, 'Tie::IxHash',
                       },
 
     #false laziness w/cdr_termination.pm
-    'output_format' => { 'name' => 'CDR invoice display format',
+    'output_format' => { 'name' => 'CDR display format for invoices',
                          'type' => 'select',
-                         'select_options' => { FS::cdr::invoice_formats() },
+                         'select_options' => \%detail_formats,
                          'default'        => 'default', #XXX test
                        },
 
+    'selfservice_format' => 
+      { 'name' => 'CDR display format for selfservice',
+        'type' => 'select',
+        'select_options' => \%detail_formats,
+        'default' => 'default'
+      },
+    'selfservice_inbound_format' =>
+      { 'name' => 'Inbound CDR display format for selfservice',
+        'type' => 'select',
+        'select_options' => \%detail_formats,
+        'default' => ''
+      },
+
     'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not): ',
                        },
 
@@ -268,8 +285,7 @@ tie my %unrateable_opts, 'Tie::IxHash',
                     qw(
                        cdr_svc_method
                        rating_method ratenum intrastate_ratenum 
-                       min_charge min_included
-                              sec_granularity
+                       min_charge min_included sec_granularity
                        ignore_unrateable
                        default_prefix
                        disable_src
@@ -289,7 +305,9 @@ tie my %unrateable_opts, 'Tie::IxHash',
                        skip_max_callers
                        use_duration
                        411_rewrite
-                       output_format usage_mandate summarize_usage usage_section
+                       output_format 
+                       selfservice_format selfservice_inbound_format
+                       usage_mandate summarize_usage usage_section
                        bill_every_call bill_inactive_svcs
                        count_available_phones suspend_bill 
                      )
@@ -341,7 +359,9 @@ sub calc_usage {
 
   my $spool_cdr = $cust_pkg->cust_main->spool_cdr;
 
-  my %included_min = ();
+  my %included_min = (); #region groups w/prefix rating
+
+  my $included_min = $self->option('min_included', 1) || 0; #single price rating
 
   my $charges = 0;
 
@@ -375,8 +395,6 @@ sub calc_usage {
   #for check_chargable, so we don't keep looking up options inside the loop
   my %opt_cache = ();
 
-  eval "use Text::CSV_XS;";
-  die $@ if $@;
   my $csv = new Text::CSV_XS;
 
   my($svc_table, $svc_field) = split('\.', $cdr_svc_method);
@@ -427,6 +445,7 @@ sub calc_usage {
       my $seconds = '';
       my $weektime = '';
       my $regionname = '';
+      my $ratename = '';
       my $classnum = '';
       my $countrycode;
       my $number;
@@ -514,8 +533,9 @@ sub calc_usage {
             ? $cust_pkg->part_pkg->option('accountcode_tollfree_ratenum')
             : '';
 
-          my $intrastate_ratenum = $cust_pkg->part_pkg->option('accountcode_tollfree_ratenum');
+          my $intrastate_ratenum = $cust_pkg->part_pkg->option('intrastate_ratenum');
           if ( $intrastate_ratenum && !$cdr->is_tollfree ) {
+            $ratename = 'Interstate'; #until proven otherwise
             # this is relatively easy only because:
             # -assume all numbers are valid NANP numbers NOT in a fully-qualified format
             # -disregard toll-free
@@ -532,9 +552,12 @@ sub calc_usage {
             $srcprefix = qsearchs('rate_prefix', {   'countrycode' => '1',
                                                      'npa' => $1, 
                                                  }) || '';
-            $eff_ratenum = $intrastate_ratenum if ($srcprefix && $dstprefix
+            if ($srcprefix && $dstprefix
                 && $srcprefix->state && $dstprefix->state
-                && $srcprefix->state eq $dstprefix->state);
+                && $srcprefix->state eq $dstprefix->state) {
+              $eff_ratenum = $intrastate_ratenum;
+              $ratename = 'Intrastate'; # XXX possibly just use the ratename?
+            }
           }
 
           $eff_ratenum ||= $ratenum;
@@ -567,8 +590,11 @@ sub calc_usage {
             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"
@@ -620,7 +646,18 @@ sub calc_usage {
           && $granularity  # 0 is per call
           && $seconds % $granularity;
         my $minutes = $granularity ? ($seconds / 60) : 1;
-        $charge = sprintf('%.4f', ( $self->option('min_charge') * $minutes )
+
+        my $charge_min = $minutes;
+
+        $included_min -= $minutes;
+        if ( $included_min > 0 ) {
+          $charge_min = 0;
+        } else {
+           $charge_min = 0 - $included_min;
+           $included_min = 0;
+        }
+
+        $charge = sprintf('%.4f', ( $self->option('min_charge') * $charge_min )
                                   + 0.0000000001 ); #so 1.00005 rounds to 1.0001
 
         warn "Incrementing \$charges by $charge.  Now $charges\n" if $DEBUG;
@@ -786,7 +823,6 @@ sub calc_usage {
             );
           }
         } #if(there is a rate_detail)
 
         #if ( $charge > 0 ) {
         # generate a detail record for every call; filter out $charge = 0 
@@ -821,6 +857,8 @@ sub calc_usage {
             regionname  => $regionname,
           };
         }
+        $call_details->{'ratename'} = $ratename;
+
         push @invoice_details_sort, [ $call_details, $cdr->calldate_unix ];
         #} $charge > 0
 
@@ -855,7 +893,8 @@ sub calc_usage {
   } # $cust_svc
 
   unshift @$details, { format => 'C',
-                       detail => FS::cdr::invoice_header($output_format),
+                       detail => 
+                         $cust_pkg->mt(FS::cdr::invoice_header($output_format))
                      }
     if @$details && $rating_method ne 'upstream';
 
@@ -905,10 +944,10 @@ sub check_chargable {
     if length($_) && substr($cdr->dst,0,length($_)) eq $_;
   }
 
-  return "carrierid != $opt{'use_carrierid'}"
-    if length($opt{'use_carrierid'})
-    && $cdr->carrierid ne $opt{'use_carrierid'} #ne otherwise 0 matches ''
-    && ! $flags{'da_rewrote'};
+  return "carrierid NOT IN ( $opt{'use_carrierid'} )"
+    if $opt{'use_carrierid'} =~ /\S/
+    && ! $flags{'da_rewrote'} #why?
+    && !grep { $cdr->carrierid eq $_ } split(/\s*,\s*/, $opt{'use_carrierid'}); #eq otherwise 0 matches ''
 
   # unlike everything else, use_cdrtypenum is applied in FS::svc_x::get_cdrs.
   return "cdrtypenum != $opt{'use_cdrtypenum'}"
@@ -998,35 +1037,45 @@ sub sum_detail {
   my $self = shift;
   my $svc_x = shift;
   my $invoice_details = shift || [];
-  my $count = scalar(@$invoice_details);
-  return () if !$count;
-  my $sum_detail = {
-    amount    => 0,
-    format    => 'C',
-    classnum  => '', #XXX
-    duration  => 0,
-    phonenum  => $svc_x->phonenum,
-    accountcode => '', #XXX
-    startdate => '', #XXX
-    regionnam => '',
-  };
+  return () if !@$invoice_details;
+  my $details_by_rate = {};
   # combine the entire set of CDRs
   foreach ( @$invoice_details ) {
-    $sum_detail->{amount} += $_->[0]{amount};
-    $sum_detail->{duration} += $_->[0]{duration};
+    my $d = $_->[0];
+    my $sum = $details_by_rate->{ $d->{ratename} } ||= {
+      amount      => 0,
+      format      => 'C',
+      classnum    => '', #XXX
+      duration    => 0,
+      phonenum    => $svc_x->phonenum,
+      accountcode => '', #XXX
+      startdate   => '', #XXX
+      regionname  => '',
+      count       => 0,
+    };
+    $sum->{amount} += $d->{amount};
+    $sum->{duration} += $d->{duration};
+    $sum->{count}++;
   }
-  my $total_cdr = FS::cdr->new({
-      'billsec' => $sum_detail->{duration},
-      'src'     => $sum_detail->{phonenum},
-    });
-  $sum_detail->{detail} = $total_cdr->downstream_csv(
-    format    => $self->option('output_format'),
-    seconds   => $sum_detail->{duration},
-    charge    => sprintf('%.2f',$sum_detail->{amount}),
-    phonenum  => $sum_detail->{phonenum},
-    count     => $count,
-  );
-  return $sum_detail;
+  my @details;
+  foreach my $ratename ( sort keys(%$details_by_rate) ) {
+    my $sum = $details_by_rate->{$ratename};
+    next if $sum->{count} == 0;
+    my $total_cdr = FS::cdr->new({
+        'billsec' => $sum->{duration},
+        'src'     => $sum->{phonenum},
+      });
+    $sum->{detail} = $total_cdr->downstream_csv(
+      format    => $self->option('output_format'),
+      seconds   => $sum->{duration},
+      charge    => sprintf('%.2f',$sum->{amount}),
+      ratename  => $ratename,
+      phonenum  => $sum->{phonenum},
+      count     => $sum->{count},
+    );
+    push @details, $sum;
+  }
+  @details;
 }
 
 # and whether cust_bill should show a detail line for the service label