1 package FS::part_pkg::voip_tiered;
2 use base qw( FS::part_pkg::voip_cdr );
5 use vars qw( $DEBUG %info );
10 use FS::Record qw(qsearchs); # qsearch);
19 tie my %cdr_inout, 'Tie::IxHash',
20 'outbound' => 'Outbound',
21 'inbound' => 'Inbound',
22 'outbound_inbound' => 'Outbound and Inbound',
25 tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities();
28 'name' => 'VoIP tiered rate pricing of CDRs',
29 'shortname' => 'VoIP/telco CDR tiered rating',
30 'inherit_fields' => [ 'voip_cdr', 'prorate_Mixin', 'global_Mixin' ],
32 'tiernum' => { 'name' => 'Tier plan',
34 'select_table' => 'rate_tier',
35 'select_key' => 'tiernum',
36 'select_label' => 'tiername',
38 'cdr_inout' => { 'name'=> 'Call direction when using phone number matching',
40 'select_options' => \%cdr_inout,
42 'min_included' => { 'name' => 'Minutes included',
44 'sec_granularity' => { 'name' => 'Granularity',
46 'select_options' => \%granularity,
48 'rating_method' => { 'disabled' => 1 },
49 'ratenum' => { 'disabled' => 1 },
50 'intrastate_ratenum' => { 'disabled' => 1 },
51 'min_charge' => { 'disabled' => 1 },
52 'ignore_unrateable' => { 'disabled' => 1 },
53 'domestic_prefix' => { 'disabled' => 1 },
54 'international_prefix' => { 'disabled' => 1 },
55 'disable_tollfree' => { 'disabled' => 1 },
56 'noskip_src_length_accountcode_tollfree' => { 'disabled' => 1 },
57 'accountcode_tollfree_ratenum' => { 'disabled' => 1 },
58 'noskip_dst_length_accountcode_tollfree' => { 'disabled' => 1 },
62 recur_method cutoff_day ),
63 FS::part_pkg::prorate_Mixin::fieldorder,
65 cdr_svc_method cdr_inout
74 my($cust_pkg, $sdate, $details, $param ) = @_;
76 #my $last_bill = $cust_pkg->last_bill;
77 my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup
80 if $self->recur_temporality eq 'preceding'
81 && ( $last_bill eq '' || $last_bill == 0 );
83 my $included_min = $self->option('min_included', 1) || 0;
84 my $cdr_svc_method = $self->option('cdr_svc_method',1)||'svc_phone.phonenum';
85 my $cdr_inout = ($cdr_svc_method eq 'svc_phone.phonenum')
86 && $self->option('cdr_inout',1)
88 my $use_duration = $self->option('use_duration');
89 my $granularity = length($self->option('sec_granularity'))
90 ? $self->option('sec_granularity')
93 #for check_chargable, so we don't keep looking up options inside the loop
96 my($svc_table, $svc_field) = split('\.', $cdr_svc_method);
99 'disable_src' => $self->option('disable_src'),
100 'default_prefix' => $self->option('default_prefix'),
103 ); # $last_bill, $$sdate )
104 $options{'by_svcnum'} = 1 if $svc_field eq 'svcnum';
107 # pass one: find the total minutes/calls and store the CDRs
112 if( $self->option('bill_inactive_svcs',1) ) {
113 #XXX in this mode do we need to restrict the set of CDRs by date also?
114 @cust_svc = $cust_pkg->h_cust_svc($$sdate, $last_bill);
116 @cust_svc = $cust_pkg->cust_svc;
118 @cust_svc = grep { $_->part_svc->svcdb eq $svc_table } @cust_svc;
120 foreach my $cust_svc (@cust_svc) {
123 if( $self->option('bill_inactive_svcs',1) ) {
124 $svc_x = $cust_svc->h_svc_x($$sdate, $last_bill);
127 $svc_x = $cust_svc->svc_x;
130 foreach my $pass (split('_', $cdr_inout)) {
132 $options{'inbound'} = ( $pass eq 'inbound' );
135 $svc_x->get_cdrs( %options )
138 warn "rating CDR $cdr\n".
139 join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr );
145 $seconds = $use_duration ? $cdr->duration : $cdr->billsec;
147 $seconds += $granularity - ( $seconds % $granularity )
148 if $seconds # don't granular-ize 0 billsec calls (bills them)
149 && $granularity # 0 is per call
150 && $seconds % $granularity;
151 my $minutes = $granularity ? ($seconds / 60) : 1;
153 my $charge_min = $minutes;
155 $included_min -= $minutes;
156 if ( $included_min > 0 ) {
159 $charge_min = 0 - $included_min;
163 my $error = $cdr->set_status_and_rated_price(
167 'inbound' => ($pass eq 'inbound'),
168 'rated_minutes' => $charge_min,
169 'rated_seconds' => $seconds,
171 die $error if $error;
173 $total += $charge_min;
182 # pass two: find a tiered rate and do the rest
185 my $rate_tier = qsearchs('rate_tier', { tiernum=>$self->option('tiernum') } )
186 or die "unknown tiernum ". $self->option('tiernum');
187 my $rate_tier_detail = $rate_tier->rate_tier_detail( $total )
188 or die "no base rate for tier? ($total)";
189 my $min_charge = $rate_tier_detail->min_charge;
191 my $output_format = $self->option('output_format', 'Hush!') || 'default';
193 my $csv = new Text::CSV_XS;
196 my @invoice_details_sort;
198 $options{'status'} = 'processing-tiered';
200 foreach my $cust_svc (@cust_svc) {
203 if( $self->option('bill_inactive_svcs',1) ) {
204 $svc_x = $cust_svc->h_svc_x($$sdate, $last_bill);
207 $svc_x = $cust_svc->svc_x;
210 foreach my $pass (split('_', $cdr_inout)) {
212 $options{'inbound'} = ( $pass eq 'inbound' );
215 $svc_x->get_cdrs( %options )
218 my $object = $options{'inbound'}
219 ? $cdr->cdr_termination( 1 ) #1: inbound
222 my $charge_min = $object->rated_minutes;
224 my $charge = sprintf('%.4f', ( $min_charge * $charge_min )
225 + 0.0000000001 ); #so 1.00005 rounds to 1.0001
230 my $detail = $self->sum_usage ? '' :
231 $cdr->downstream_csv( 'format' => $output_format,
233 'seconds' => ($use_duration ?
236 'granularity' => $granularity,
243 #classnum => $cdr->calltypenum, #classnum
244 #phonenum => $phonenum, #XXX need this to sort on them
245 accountcode => $cdr->accountcode,
246 startdate => $cdr->startdate,
247 duration => $object->rated_seconds,
250 #warn " adding details on charge to invoice: [ ".
251 # join(', ', @{$call_details} ). " ]"
252 # if ( $DEBUG && ref($call_details) );
253 push @invoice_details_sort, [ $call_details, $cdr->calldate_unix ];
256 my $error = $cdr->set_status_and_rated_price(
260 'inbound' => $options{'inbound'},
261 'rated_minutes' => $charge_min,
262 'rated_seconds' => $object->rated_seconds,
264 die $error if $error;
270 if ( $self->sum_usage ) {
271 # then summarize all accumulated details within this svc_x
272 # and then flush them
273 push @$details, $self->sum_detail($svc_x, \@invoice_details_sort);
274 @invoice_details_sort = ();
279 if ( !$self->sum_usage ) {
281 my @sorted_invoice_details =
282 sort { ${$a}[1] <=> ${$b}[1] } @invoice_details_sort;
283 foreach my $sorted_call_detail ( @sorted_invoice_details ) {
284 push @$details, ${$sorted_call_detail}[0];
288 unshift @$details, { format => 'C',
289 detail => FS::cdr::invoice_header($output_format),