$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 );
=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)
=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
'dstchannel' => 'Destination channel',
#'lastapp' => '',
#'lastdata' => '',
+ 'src_ip_addr' => 'Source IP',
+ 'dst_ip_addr' => 'Dest. IP',
'startdate' => 'Start date',
'answerdate' => 'Answer date',
'enddate' => 'End date',
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.
);
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'},
);
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"
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 {
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,
+ );
}
'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",
'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 = ();
#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
=cut
+# in the future, load this dynamically from detail_format classes
+
sub invoice_formats {
map { ($_ => $export_names{$_}->{'name'}) }
grep { $export_names{$_}->{'invoice_header'} }
sub clear_status {
my $self = shift;
+ my %opt = @_;
local $SIG{HUP} = 'IGNORE';
local $SIG{INT} = 'IGNORE';
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');
}
+=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