From 392d0c1164bf5d222826164195502906252ba2dd Mon Sep 17 00:00:00 2001 From: mark Date: Tue, 13 Dec 2011 20:40:44 +0000 Subject: [PATCH] CDR type separation and summary formats, #15535 --- FS/FS/cdr.pm | 23 ++++++++ FS/FS/cust_bill_pkg.pm | 4 +- FS/FS/part_pkg.pm | 3 + FS/FS/part_pkg/voip_cdr.pm | 139 ++++++++++++++++++++++++++++++--------------- FS/FS/svc_pbx.pm | 10 +++- FS/FS/svc_phone.pm | 8 +++ 6 files changed, 139 insertions(+), 48 deletions(-) diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index 1507dde95..190ffe2b5 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -573,6 +573,14 @@ my %export_names = ( '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 = (); @@ -622,6 +630,19 @@ sub export_formats { #sub { sprintf('%.3f', shift->upstream_price ) }, #PRICE sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, #PRICE ], + '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' }, + sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, + ], + 'sum_count' => [ + 'src', + sub { my($cdr, %opt) = @_; $opt{count} }, + sub { my($cdr, %opt) = @_; $opt{money_char}. $opt{charge}; }, + ], 'basic' => [ sub { time2str('%d %b - %I:%M %p', shift->calldate_unix) }, 'dst', @@ -672,6 +693,8 @@ sub export_formats { =item downstream_csv OPTION => VALUE ... +Returns a string of formatted call details for display on an invoice. + Options: format diff --git a/FS/FS/cust_bill_pkg.pm b/FS/FS/cust_bill_pkg.pm index 267804b5c..8d79ed587 100644 --- a/FS/FS/cust_bill_pkg.pm +++ b/FS/FS/cust_bill_pkg.pm @@ -916,9 +916,9 @@ sub usage_classes { my %seen = (); foreach my $detail ( grep { ref($_) } @{$self->get('details')} ) { - $seen{ ref($detail) eq 'HASH' + $seen{ (ref($detail) eq 'HASH' ? $detail->{'classnum'} - : $detail->[3] + : $detail->[3]) || '' } = 1; } keys %seen; diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 70b896c34..1db5a708c 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -1296,6 +1296,9 @@ sub calc_units { 0; } #fallback for everything except bulk.pm sub hide_svc_detail { 0; } +#fallback for packages that can't/won't summarize usage +sub sum_usage { 0; } + =item recur_cost_permonth CUST_PKG recur_cost divided by freq (only supported for monthly and longer frequencies) diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm index 02992277d..747965b69 100644 --- a/FS/FS/part_pkg/voip_cdr.pm +++ b/FS/FS/part_pkg/voip_cdr.pm @@ -403,6 +403,7 @@ sub calc_usage { my %options = ( 'disable_src' => $self->option('disable_src'), 'default_prefix' => $self->option('default_prefix'), + 'cdrtypenum' => $self->option('use_cdrtypenum'), 'status' => '', 'for_update' => 1, ); # $last_bill, $$sdate ) @@ -434,6 +435,8 @@ sub calc_usage { if ( $rating_method eq 'prefix' ) { my $da_rewrote = 0; + # this will result in those CDRs being marked as done... is that + # what we want? if ( length($cdr->dst) && grep { $cdr->dst eq $_ } @dirass ){ $cdr->dst('411'); $da_rewrote = 1; @@ -448,6 +451,8 @@ sub calc_usage { warn "not charging for CDR ($reason)\n" if $DEBUG; $charge = 0; + # this will result in those CDRs being marked as done... is that + # what we want? } else { @@ -767,17 +772,19 @@ sub calc_usage { warn "Incrementing \$charges by $charge. Now $charges\n" if $DEBUG; $charges += $charge; - @call_details = ( - $cdr->downstream_csv( 'format' => $output_format, - 'granularity' => $rate_detail->sec_granularity, - 'seconds' => ($use_duration ? - $cdr->duration : - $cdr->billsec), - 'charge' => $charge, - 'pretty_dst' => $pretty_destnum, - 'dst_regionname' => $regionname, - ) - ); + if ( !$self->sum_usage ) { + @call_details = ( + $cdr->downstream_csv( 'format' => $output_format, + 'granularity' => $rate_detail->sec_granularity, + 'seconds' => ($use_duration ? + $cdr->duration : + $cdr->billsec), + 'charge' => $charge, + 'pretty_dst' => $pretty_destnum, + 'dst_regionname' => $regionname, + ) + ); + } } #if(there is a rate_detail) @@ -788,33 +795,31 @@ sub calc_usage { if ( scalar(@call_details) == 1 ) { $call_details = - [ 'C', - $call_details[0], - $charge, - $classnum, - $phonenum, - $cdr->accountcode, - $cdr->startdate, - $seconds, - $regionname, - ]; + { format => 'C', + detail => $call_details[0], + amount => $charge, + classnum => $classnum, + phonenum => $phonenum, + accountcode => $cdr->accountcode, + startdate => $cdr->startdate, + duration => $seconds, + regionname => $regionname, + }; } else { #only used for $rating_method eq 'upstream' now + # and for sum_ formats $csv->combine(@call_details); $call_details = - [ 'C', - $csv->string, - $charge, - $classnum, - $phonenum, - $cdr->accountcode, - $cdr->startdate, - $seconds, - $regionname, - ]; + { format => 'C', + detail => $csv->string, + amount => $charge, + classnum => $classnum, + phonenum => $phonenum, + accountcode => $cdr->accountcode, + startdate => $cdr->startdate, + duration => $seconds, + regionname => $regionname, + }; } - warn " adding details on charge to invoice: [ ". - join(', ', @{$call_details} ). " ]" - if ( $DEBUG && ref($call_details) ); push @invoice_details_sort, [ $call_details, $cdr->calldate_unix ]; } @@ -833,22 +838,50 @@ sub calc_usage { } } # $cdr - - my @sorted_invoice_details = sort { @{$a}[1] <=> @{$b}[1] } @invoice_details_sort; - foreach my $sorted_call_detail ( @sorted_invoice_details ) { + + if ( !$self->sum_usage ) { + #sort them + my @sorted_invoice_details = + sort { @{$a}[1] <=> @{$b}[1] } @invoice_details_sort; + foreach my $sorted_call_detail ( @sorted_invoice_details ) { push @$details, @{$sorted_call_detail}[0]; + } } + else { #$self->sum_usage + my $sum_detail = { + amount => 0, + format => 'C', + classnum => '', #XXX + duration => 0, + phonenum => $svc_x->phonenum, + accountcode => '', #XXX + startdate => '', #XXX + regionnam => '', + }; + # combine the entire set of CDRs + foreach ( @invoice_details_sort ) { + $sum_detail->{amount} += $_->[0]{amount}; + $sum_detail->{duration} += $_->[0]{duration}; + } + my $total_cdr = FS::cdr->new({ + 'billsec' => $sum_detail->{duration}, + 'src' => $sum_detail->{phonenum}, + }); + $sum_detail->{detail} = $total_cdr->downstream_csv( + format => $output_format, + seconds => $sum_detail->{duration}, + charge => sprintf('%.2f',$sum_detail->{amount}), + phonenum => $sum_detail->{phonenum}, + count => scalar(@invoice_details_sort), + ); + push @$details, $sum_detail; + } #if $self->sum_usage } # $cust_svc - unshift @$details, [ 'C', - FS::cdr::invoice_header($output_format), - '', - '', - '', - '', - '', - ] + unshift @$details, { format => 'C', + detail => FS::cdr::invoice_header($output_format), + } if @$details && $rating_method ne 'upstream'; # if ( $spool_cdr && length($downstream_cdr) ) { @@ -929,6 +962,7 @@ sub check_chargable { && $cdr->carrierid ne $opt{'use_carrierid'} #ne otherwise 0 matches '' && ! $flags{'da_rewrote'}; + # unlike everything else, use_cdrtypenum is applied in FS::svc_x::get_cdrs. return "cdrtypenum != $opt{'use_cdrtypenum'}" if length($opt{'use_cdrtypenum'}) && $cdr->cdrtypenum ne $opt{'use_cdrtypenum'}; #ne otherwise 0 matches '' @@ -1005,5 +1039,20 @@ sub calc_units { $count; } +# tells whether cust_bill_pkg_detail should return a single line for +# each phonenum +sub sum_usage { + my $self = shift; + $self->option('output_format') =~ /^sum_/; +} + +# and whether cust_bill should show a detail line for the service label +# (separate from usage details) +sub hide_svc_detail { + my $self = shift; + $self->option('output_format') =~ /^sum_/; +} + + 1; diff --git a/FS/FS/svc_pbx.pm b/FS/FS/svc_pbx.pm index 093eacd54..37ab174d2 100644 --- a/FS/FS/svc_pbx.pm +++ b/FS/FS/svc_pbx.pm @@ -283,6 +283,10 @@ with the chosen prefix. =item by_svcnum => 1: Select CDRs where the svcnum field matches, instead of title/charged_party. Normally this field is set after processing. +=item begin, end: Start and end of date range, as unix timestamp. + +=item cdrtypenum: Only return CDRs with this type number. + =back =cut @@ -295,7 +299,11 @@ sub get_cdrs { my @fields = ( 'charged_party' ); $hash{'freesidestatus'} = $options{'status'} if exists($options{'status'}); - + + if ($options{'cdrtypenum'}) { + $hash{'cdrtypenum'} = $options{'cdrtypenum'}; + } + my $for_update = $options{'for_update'} ? 'FOR UPDATE' : ''; if ( $options{'by_svcnum'} ) { diff --git a/FS/FS/svc_phone.pm b/FS/FS/svc_phone.pm index e3d18e092..e8b0d0a71 100644 --- a/FS/FS/svc_phone.pm +++ b/FS/FS/svc_phone.pm @@ -658,6 +658,10 @@ on inbound processing status. =item default_prefix => "XXX": Also accept the phone number of the service prepended with the chosen prefix. +=item begin, end: Start and end of a date range, as unix timestamp. + +=item cdrtypenum: Only return CDRs with this type number. + =item disable_src => 1: Only match on "charged_party", not "src". =item by_svcnum: not supported for svc_phone @@ -696,6 +700,10 @@ sub get_cdrs { $hash{'freesidestatus'} = $options{'status'} if exists($options{'status'}); } + + if ($options{'cdrtypenum'}) { + $hash{'cdrtypenum'} = $options{'cdrtypenum'}; + } my $for_update = $options{'for_update'} ? 'FOR UPDATE' : ''; -- 2.11.0