use strict;
use vars qw( $DEBUG %info );
use Tie::IxHash;
+use Date::Parse;
use Date::Format;
use Text::CSV_XS;
use FS::Conf;
use FS::Record qw(qsearchs qsearch);
use FS::cdr;
-#use FS::rate;
-#use FS::rate_prefix;
-#use FS::rate_detail;
+use FS::detail_format;
$DEBUG = 0;
'svc_phone.phonenum' => 'Phone numbers (svc_phone.phonenum)',
'svc_pbx.title' => 'PBX name (svc_pbx.title)',
'svc_pbx.svcnum' => 'Freeside service # (svc_pbx.svcnum)',
+ 'svc_pbx.ip.src' => 'PBX name to source IP address',
+ 'svc_pbx.ip.dst' => 'PBX name to destination IP address',
+ 'svc_acct.username' => 'Username (svc_acct.username)',
;
tie my %rating_method, 'Tie::IxHash',
'prefix' => 'Rate calls by using destination prefix to look up a region and rate according to the internal prefix and rate tables',
# 'upstream' => 'Rate calls based on upstream data: If the call type is "1", map the upstream rate ID directly to an internal rate (rate_detail), otherwise, pass the upstream price through directly.',
- 'upstream_simple' => 'Simply pass through and charge the "upstream_price" amount.',
- 'single_price' => 'A single price per minute for all calls.',
+ 'upstream_simple' => 'Simply pass through and charge the "upstream_price" amount (ignoring all options to skip calls).',
+ 'single_price' => 'A single price per minute for all calls (ignoring all options to skip calls).',
+;
+
+tie my %rounding, 'Tie::IxHash',
+ '2' => 'Two decimal places (cent)',
+ '4' => 'Four decimal places (100th of a cent)',
;
#tie my %cdr_location, 'Tie::IxHash',
2 => 'Flag for later review',
;
+tie my %detail_formats, 'Tie::IxHash',
+ '' => '',
+ FS::cdr::invoice_formats()
+;
+
+tie my %accountcode_tollfree_field, 'Tie::IxHash',
+ 'dst' => 'Destination (dst)',
+ 'src' => 'Source (src)',
+;
+
%info = (
- 'name' => 'VoIP rating by plan of CDR records in an internal (or external) SQL table',
+ 'name' => 'VoIP rating by plan of CDR records',
'shortname' => 'VoIP/telco CDR rating (standard)',
'inherit_fields' => [ 'prorate_Mixin', 'global_Mixin' ],
'fields' => {
},
'cdr_svc_method' => { 'name' => 'CDR service matching method',
- 'type' => 'radio',
- 'options' => \%cdr_svc_method,
+# 'type' => 'radio',
+ 'type' => 'select',
+ 'select_options' => \%cdr_svc_method,
},
'rating_method' => { 'name' => 'Rating method',
'options' => \%rating_method,
},
+ 'rounding' => { 'name' => 'Rounding for destination prefix rating',
+ 'type' => 'select',
+ 'select_options' => \%rounding,
+ },
+
'ratenum' => { 'name' => 'Rate plan',
- 'type' => 'select',
- 'select_table' => 'rate',
- 'select_key' => 'ratenum',
- 'select_label' => 'ratename',
+ 'type' => 'select-rate',
},
'intrastate_ratenum' => { 'name' => 'Optional alternate intrastate rate plan',
- 'type' => 'select',
- 'select_table' => 'rate',
- 'select_key' => 'ratenum',
- 'select_label' => 'ratename',
+ 'type' => 'select-rate',
'disable_empty' => 0,
- 'empty_label' => '',
+ 'empty_label' => ' ',
},
- 'min_included' => { 'name' => 'Minutes included when using the "single price per minute" rating method or when using the "prefix" rating method ("region group" billing)',
+ 'calls_included' => { 'name' => 'Number of calls included at no usage charge', },
+
+ 'min_included' => { 'name' => 'Minutes included when using the "single price per minute" or "prefix" rating method',
},
'min_charge' => { 'name' => 'Charge per minute when using "single price per minute" rating method',
# 'type' => 'checkbox',
# },
- 'international_prefix' => { 'name' => 'Destination prefix for international CDR records',
+ 'international_prefix' => { 'name' => 'Destination prefix for international CDR records (or "none" for no prefix)',
'default' => '011',
},
'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 this cdrtypenum: ',
+ 'validate' => \&FS::part_pkg::validate_number_blank,
+ 'js_validate' => 'digits: true',
+ },
+
+ 'ignore_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is set to this cdrtypenum: ',
+ 'validate' => \&FS::part_pkg::validate_number_blank,
+ 'js_validate' => 'digits: true',
},
- 'use_cdrtypenum' => { 'name' => 'Only charge for CDRs where the CDR Type is set to: ',
+ 'use_calltypenum' => { 'name' => 'Only charge for CDRs where the CDR Call Type is set to this calltypenum: ',
},
- 'ignore_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is set to: ',
+ 'ignore_calltypenum' => { 'name' => 'Do not charge for CDRs where the CDR Call Type is set to this calltypenum: ',
},
'ignore_disposition' => { 'name' => 'Do not charge for CDRs where the Disposition is set to any of these (comma-separated) values: ',
'disposition_in' => { 'name' => 'Only charge for CDRs where the Disposition is set to any of these (comma-separated) values: ',
},
- 'skip_dst_prefix' => { 'name' => 'Do not charge for CDRs where the destination number starts with any of these values: ',
+ 'disposition_prefix' => { 'name' => 'Only charge for CDRs where the Disposition starts with: ',
+ },
+
+ 'skip_dst_prefix' => { 'name' => 'Do not charge for CDRs where the destination number starts with any of these (comma-separated) values: ',
},
- 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values: ',
+ 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where dcontext is set to any of these (comma-separated) values: ',
+ },
+
+ 'skip_dcontext_prefix' => { 'name' => 'Do not charge for CDRs where dcontext starts with: ',
+ },
+
+ 'skip_dcontext_suffix' => { 'name' => 'Do not charge for CDRs where dcontext ends with: ',
},
'skip_dstchannel_prefix' => { 'name' => 'Do not charge for CDRs where the dstchannel starts with:',
'empty_label' => '',
},
+ 'accountcode_tollfree_field' => {
+ 'name' => 'When using an alternate rate plan for toll-free accountcodes, the CDR field to use in rating calculations',
+ 'type' => 'select',
+ 'select_options' => \%accountcode_tollfree_field,
+ },
+
'skip_dst_length_less' => { 'name' => 'Do not charge for CDRs where the destination is less than this many digits:',
},
'skip_max_callers' => { 'name' => 'Do not charge for CDRs where max_callers is less than or equal to this value: ',
},
+ 'skip_same_customer' => {
+ 'name' => 'Do not charge for calls between numbers belonging to the same customer',
+ 'type' => 'checkbox',
+ },
+
'use_duration' => { 'name' => 'Calculate usage based on the duration field instead of the billsec field',
'type' => 'checkbox',
},
},
#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): ',
},
'type' => 'checkbox',
},
- 'usage_mandate' => { 'name' => 'Always put usage details in separate section',
+ 'usage_mandate' => { 'name' => 'Always put usage details in separate section. The section is defined in the next option.',
'type' => 'checkbox',
},
#eofalse
+ 'usage_showzero' => { 'name' => 'Show details for included / no-charge calls.',
+ 'type' => 'checkbox',
+ },
+
'bill_every_call' => { 'name' => 'Generate an invoice immediately for every call (as well any setup fee, upon first payment). Useful for prepaid.',
'type' => 'checkbox',
},
'bill_inactive_svcs' => { 'name' => 'Bill for all phone numbers that were active during the billing period',
'type' => 'checkbox',
},
-
- 'count_available_phones' => { 'name' => 'Consider for tax purposes the number of lines to be svc_phones that may be provisioned rather than those that actually are.',
- 'type' => 'checkbox',
- },
+ 'bill_only_pkg_dates' => { 'name' => 'Only bill CDRs with a date during the package billing period',
+ 'type' => 'checkbox',
+ },
+ 'skip_old' => { 'name' => 'Do not charge for CDRs older than: ',
+ 'type' => 'date',
+ },
#XXX also have option for an external db? these days we suck them into ours
# 'cdr_location' => { 'name' => 'CDR database location'
FS::part_pkg::prorate_Mixin::fieldorder,
qw(
cdr_svc_method
- rating_method ratenum intrastate_ratenum
- min_charge min_included sec_granularity
+ rating_method rounding ratenum intrastate_ratenum
+ calls_included
+ min_charge min_included
+ sec_granularity
ignore_unrateable
default_prefix
disable_src
use_amaflags
use_carrierid
use_cdrtypenum ignore_cdrtypenum
- ignore_disposition disposition_in
- skip_dcontext skip_dst_prefix
+ use_calltypenum ignore_calltypenum
+ ignore_disposition disposition_in disposition_prefix
+ skip_dcontext skip_dcontext_prefix skip_dcontext_suffix
+ skip_dst_prefix
skip_dstchannel_prefix skip_src_length_more
noskip_src_length_accountcode_tollfree
- accountcode_tollfree_ratenum
+ accountcode_tollfree_ratenum accountcode_tollfree_field
skip_dst_length_less
noskip_dst_length_accountcode_tollfree
skip_lastapp
skip_max_callers
+ skip_same_customer
use_duration
411_rewrite
- output_format usage_mandate summarize_usage usage_section
- bill_every_call bill_inactive_svcs
- count_available_phones suspend_bill
+ output_format
+ selfservice_format selfservice_inbound_format
+ usage_mandate usage_section summarize_usage
+ usage_showzero bill_every_call bill_inactive_svcs
+ bill_only_pkg_dates skip_old
+ suspend_bill
)
],
- 'weight' => 40,
+ 'weight' => 41,
+ 'validate' => sub {
+ # Validation function for FS::part_pkg::check_options()
+ my $options = shift;
+ return "Please choose a Rate Plan for use with selected Rating Method"
+ if $options->{rating_method} eq 'prefix' && !$options->{ratenum};
+ return;
+ },
);
sub price_info {
my $self = shift;
- my $str = $self->SUPER::price_info;
+ my $str = $self->SUPER::price_info(@_);
$str .= " plus usage" if $str;
$str;
}
my $charges = 0;
$charges += $self->calc_usage(@_);
- $charges += $self->calc_recur_Common(@_);
+ $charges += ($cust_pkg->quantity || 1) * $self->calc_recur_Common(@_);
$charges;
}
-sub calc_cancel {
- my $self = shift;
- my($cust_pkg, $sdate, $details, $param ) = @_;
-
- $self->calc_usage(@_);
-}
+# use the default
+#sub calc_cancel {
+# my $self = shift;
+# my($cust_pkg, $sdate, $details, $param ) = @_;
+#
+# $self->calc_usage(@_);
+#}
#false laziness w/voip_sqlradacct calc_recur resolve it if that one ever gets used again
my $charges = 0;
- my $included_min = $self->option('min_included', 1) || 0; #single price rating
+ my $included_min_total = ($self->option('min_included', 1) || 0)
+ * ($cust_pkg->quantity || 1);
+ #single price rating
+ #or region group
+ my $included_min_left = $included_min_total;
+
+ my $included_calls = $self->option('calls_included', 1) || 0;
+ $included_calls *= ($cust_pkg->quantity || 1);
my $cdr_svc_method = $self->option('cdr_svc_method',1)||'svc_phone.phonenum';
my $rating_method = $self->option('rating_method') || 'prefix';
- my $region_group_included_min = $self->option('min_included',1) || 0;
- my %region_group_included_min = ();
+ my %detail_included_min = ();
my $output_format = $self->option('output_format', 'Hush!')
|| ( $rating_method eq 'upstream_simple'
: 'default'
);
- my $use_duration = $self->option('use_duration');
+ my $usage_showzero = $self->option('usage_showzero', 1);
+
+ my $formatter = FS::detail_format->new($output_format,
+ buffer => $details,
+ locale => $cust_pkg->cust_main->locale,
+ rounding => ($self->option_cacheable('rounding') || 2),
+ );
- my $csv = new Text::CSV_XS;
+ my $use_duration = $self->option('use_duration');
- my($svc_table, $svc_field) = split('\.', $cdr_svc_method);
+ my($svc_table, $svc_field, $by_ip_addr) = split('\.', $cdr_svc_method);
my @cust_svc;
if( $self->option('bill_inactive_svcs',1) ) {
$svc_x = $cust_svc->svc_x;
}
+ unless ( $svc_x ) {
+ my $h = $self->option('bill_inactive_svcs',1) ? 'h_' : '';
+ warn "WARNING: no $h$svc_table for svcnum ". $cust_svc->svcnum. "\n";
+ }
+
my %options = (
- 'disable_src' => $self->option('disable_src'),
- 'default_prefix' => $self->option('default_prefix'),
- 'cdrtypenum' => $self->option('use_cdrtypenum'),
+ 'disable_src' => $self->option('disable_src',1),
+ 'default_prefix' => $self->option('default_prefix',1),
+ 'cdrtypenum' => $self->option('use_cdrtypenum',1),
+ 'calltypenum' => $self->option('use_calltypenum',1),
'status' => '',
'for_update' => 1,
- ); # $last_bill, $$sdate )
- $options{'by_svcnum'} = 1 if $svc_field eq 'svcnum';
+ );
+ if ( $self->option('bill_only_pkg_dates') ) {
+ $options{'begin'} = $last_bill;
+ $options{'end'} = $$sdate;
+ }
+ if ( $svc_field eq 'svcnum' ) {
+ $options{'by_svcnum'} = 1;
+ } elsif ($svc_table eq 'svc_pbx' and $svc_field eq 'ip') {
+ $options{'by_ip_addr'} = $by_ip_addr;
+ }
- my @invoice_details_sort;
+ #my @invoice_details_sort;
+
+ # for tagging invoice details
+ # (unfortunate; should be a svc_x class method or table_info item or
+ # something)
+ my $phonenum;
+ if ( $svc_table eq 'svc_phone' ) {
+ $phonenum = $svc_x->phonenum;
+ } elsif ( $svc_table eq 'svc_pbx' ) {
+ $phonenum = $svc_x->title;
+ } elsif ( $svc_table eq 'svc_acct' ) {
+ $phonenum = $svc_x->username;
+ }
+ $formatter->phonenum($phonenum);
#first rate any outstanding CDRs not yet rated
- foreach my $cdr (
- $svc_x->get_cdrs( %options )
- ) {
+ # use FS::Cursor for this starting in 4.x
+ my $cdr_search = $svc_x->psearch_cdrs(%options);
+ $cdr_search->limit(1000);
+ $cdr_search->increment(0); # because we're changing their status as we go
+ while ( my $cdr = $cdr_search->fetch ) {
my $error = $cdr->rate(
'part_pkg' => $self,
+ 'cust_pkg' => $cust_pkg,
'svcnum' => $svc_x->svcnum,
- 'single_price_included_min' => \$included_min,
- 'region_group_included_min' => \$region_group_included_min,
- 'region_group_included_min_hashref' => \%region_group_included_min,
+ 'plan_included_min' => \$included_min_left,
+ 'detail_included_min_hashref' => \%detail_included_min,
);
die $error if $error; #??
+ $cdr_search->adjust(1) if $cdr->freesidestatus eq '';
+ # it was skipped without changing status, so increment the
+ # offset so that we don't re-fetch it on refill
+
} # $cdr
#then add details to invoices & get a total
$options{'status'} = 'rated';
- foreach my $cdr (
- $svc_x->get_cdrs( %options )
- ) {
-
- my $classnum = '';
- my @call_details = ();
-
- if ( $rating_method eq 'prefix' ) {
-
- $classnum = $cdr->rated_classnum;
-
- unless ( $self->sum_usage ) {
- @call_details = ($cdr->downstream_csv(
- 'format' => $output_format,
- 'granularity' => $cdr->rated_granularity,
- 'seconds' =>($use_duration ? $cdr->duration : $cdr->billsec),
- 'charge' => $cdr->rated_price,
- 'pretty_dst' => $cdr->rated_pretty_dst,
- 'dst_regionname' => $cdr->rated_regionname,
- ));
- }
-
-
- } elsif ( $rating_method eq 'upstream_simple' ) {
-
- $classnum = $cdr->calltypenum; #? meaningful these days?
-
- @call_details = ($cdr->downstream_csv(
- 'format' => $output_format,
- 'charge' => $cdr->rated_price,
- ));
-
- } elsif ( $rating_method eq 'single_price' ) {
-
- my $granularity = length($self->option_cacheable('sec_granularity'))
- ? $self->option_cacheable('sec_granularity')
- : 60;
-
- @call_details = ($cdr->downstream_csv(
- 'format' => $output_format,
- 'charge' => $cdr->rated_price,
- 'seconds' => ($use_duration ? $cdr->duration : $cdr->billsec),
- 'granularity' => $granularity,
- ));
- } else {
- die "don't know how to rate CDRs using method: $rating_method\n";
+ $cdr_search = $svc_x->psearch_cdrs(%options);
+ $cdr_search->limit(1000);
+ $cdr_search->increment(0);
+ while ( my $cdr = $cdr_search->fetch ) {
+ my $error;
+ # at this point we officially Do Not Care about the rating method
+ if ( $included_calls > 0 ) {
+ $included_calls--;
+ #$charges += 0, obviously
+ #but don't set the rated price to zero--there should be a record
+ $error = $cdr->set_status('no-charge');
}
-
- $charges += $cdr->rated_price;
-
- #if ( $cdr->rated_price > 0 ) {
- # generate a detail record for every call; filter out
- # $cdr->rated_price == 0 # later.
- my $call_details;
- my $phonenum = $svc_x->phonenum;
-
- if ( scalar(@call_details) == 1 ) {
- $call_details =
- { format => 'C',
- detail => $call_details[0],
- amount => $cdr->rated_price,
- classnum => $classnum,
- phonenum => $phonenum,
- accountcode => $cdr->accountcode,
- startdate => $cdr->startdate,
- duration => $cdr->rated_seconds,
- regionname => $cdr->rated_regionname,
- };
- } else { #only used for $rating_method eq 'upstream' now
- # and for sum_ formats
- $csv->combine(@call_details);
- $call_details =
- { format => 'C',
- detail => $csv->string,
- amount => $cdr->rated_price,
- classnum => $classnum,
- phonenum => $phonenum,
- accountcode => $cdr->accountcode,
- startdate => $cdr->startdate,
- duration => $cdr->rated_seconds,
- regionname => $cdr->rated_regionname,
- };
+ else {
+ $charges += $cdr->rated_price;
+ $error = $cdr->set_status('done');
}
- $call_details->{'ratename'} = $cdr->rated_ratename;
+ die $error if $error;
+ $formatter->append($cdr)
+ unless $cdr->rated_price == 0 and not $usage_showzero;
- push @invoice_details_sort, [ $call_details, $cdr->calldate_unix ];
- #} $charge > 0
+ $cdr_search->adjust(1) if $cdr->freesidestatus eq 'rated';
+ } #$cdr
+ }
- my $error = $cdr->set_status('done');
- die $error if $error; #??
+ $formatter->finish; #writes into $details
+ unshift @$details, $formatter->header if @$details;
- }
+ if ( $included_min_total > 0 ) {
- 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 ) {
- my $d = $sorted_call_detail->[0];
- push @$details, $d if $d->{amount} > 0;
- }
- }
- else { #$self->sum_usage
- push @$details, $self->sum_detail($svc_x, \@invoice_details_sort);
- }
- } # $cust_svc
-
- unshift @$details, { format => 'C',
- detail => FS::cdr::invoice_header($output_format),
- }
- if @$details && $rating_method ne 'upstream';
+ my $min_detail = sprintf('%d / %d ',
+ $included_min_total - $included_min_left,
+ $included_min_total
+ ) . $cust_pkg->mt('included minutes used');
+ unshift @$details, $min_detail;
+ }
$charges;
}
#returns a reason why not to rate this CDR, or false if the CDR is chargeable
+# lots of false laziness w/voip_inbound
sub check_chargable {
my( $self, $cdr, %flags ) = @_;
return 'amaflags != 2'
if $self->option_cacheable('use_amaflags') && $cdr->amaflags != 2;
- return "disposition NOT IN ( $self->option_cacheable('disposition_in') )"
+ return "disposition NOT IN ( ". $self->option_cacheable('disposition_in')." )"
if $self->option_cacheable('disposition_in') =~ /\S/
&& !grep { $cdr->disposition eq $_ } split(/\s*,\s*/, $self->option_cacheable('disposition_in'));
-
- return "disposition IN ( $self->option_cacheable('ignore_disposition') )"
+
+ my $disposition_prefix = $self->option_cacheable('disposition_prefix');
+ my $len_dis_prefix = length($disposition_prefix);
+ return "disposition does not start with $disposition_prefix"
+ if $len_dis_prefix
+ && substr($cdr->disposition, 0, $len_dis_prefix) ne $disposition_prefix;
+
+ return "disposition IN ( ". $self->option_cacheable('ignore_disposition')." )"
if $self->option_cacheable('ignore_disposition') =~ /\S/
&& grep { $cdr->disposition eq $_ } split(/\s*,\s*/, $self->option_cacheable('ignore_disposition'));
if length($_) && substr($cdr->dst,0,length($_)) eq $_;
}
- return "carrierid != $self->option_cacheable('use_carrierid')"
- if length($self->option_cacheable('use_carrierid'))
- && $cdr->carrierid ne $self->option_cacheable('use_carrierid') #ne otherwise 0 matches ''
- && ! $flags{'da_rewrote'};
+ return "carrierid NOT IN ( ". $self->option_cacheable('use_carrierid'). " )"
+ if $self->option_cacheable('use_carrierid') =~ /\S/
+ && ! $flags{'da_rewrote'} #why?
+ && !grep { $cdr->carrierid eq $_ } split(/\s*,\s*/, $self->option_cacheable('use_carrierid')); #eq otherwise 0 matches ''
# unlike everything else, use_cdrtypenum is applied in FS::svc_x::get_cdrs.
- return "cdrtypenum != $self->option_cacheable('use_cdrtypenum')"
+ return "cdrtypenum != ". $self->option_cacheable('use_cdrtypenum')
if length($self->option_cacheable('use_cdrtypenum'))
&& $cdr->cdrtypenum ne $self->option_cacheable('use_cdrtypenum'); #ne otherwise 0 matches ''
- return "cdrtypenum == $self->option_cacheable('ignore_cdrtypenum')"
+ return "cdrtypenum == ". $self->option_cacheable('ignore_cdrtypenum')
if length($self->option_cacheable('ignore_cdrtypenum'))
&& $cdr->cdrtypenum eq $self->option_cacheable('ignore_cdrtypenum'); #eq otherwise 0 matches ''
- return "dcontext IN ( $self->option_cacheable('skip_dcontext') )"
+ # unlike everything else, use_calltypenum is applied in FS::svc_x::get_cdrs.
+ return "calltypenum != ". $self->option_cacheable('use_calltypenum')
+ if length($self->option_cacheable('use_calltypenum'))
+ && $cdr->calltypenum ne $self->option_cacheable('use_calltypenum'); #ne otherwise 0 matches ''
+
+ return "calltypenum == ". $self->option_cacheable('ignore_calltypenum')
+ if length($self->option_cacheable('ignore_calltypenum'))
+ && $cdr->calltypenum eq $self->option_cacheable('ignore_calltypenum'); #eq otherwise 0 matches ''
+
+ return "dcontext IN ( ". $self->option_cacheable('skip_dcontext'). " )"
if $self->option_cacheable('skip_dcontext') =~ /\S/
&& grep { $cdr->dcontext eq $_ } split(/\s*,\s*/, $self->option_cacheable('skip_dcontext'));
+ my $len_dcontext_prefix =
+ length($self->option_cacheable('skip_dcontext_prefix'));
+ return "dcontext starts with ". $self->option_cacheable('skip_dcontext_prefix')
+ if $len_dcontext_prefix
+ && substr($cdr->dcontext,0,$len_dcontext_prefix) eq $self->option_cacheable('skip_dcontext_prefix');
+
+ my $len_suffix = length($self->option_cacheable('skip_dcontext_suffix'));
+ return "dcontext ends with ". $self->option_cacheable('skip_dcontext_suffix')
+ if $len_suffix
+ && substr($cdr->dcontext,-$len_suffix,$len_suffix) eq $self->option_cacheable('skip_dcontext_suffix');
+
my $len_prefix = length($self->option_cacheable('skip_dstchannel_prefix'));
- return "dstchannel starts with $self->option_cacheable('skip_dstchannel_prefix')"
+ return "dstchannel starts with ". $self->option_cacheable('skip_dstchannel_prefix')
if $len_prefix
&& substr($cdr->dstchannel,0,$len_prefix) eq $self->option_cacheable('skip_dstchannel_prefix');
&& $cdr->is_tollfree('accountcode')
);
- return "lastapp is $self->option_cacheable('skip_lastapp')"
+ return "lastapp is ". $self->option_cacheable('skip_lastapp')
if length($self->option_cacheable('skip_lastapp')) && $cdr->lastapp eq $self->option_cacheable('skip_lastapp');
my $src_length = $self->option_cacheable('skip_src_length_more');
and length($cdr->max_callers)
and $cdr->max_callers <= $self->option_cacheable('skip_max_callers');
+ return "calldate < ". $self->option_cacheable('skip_old')
+ if length($self->option_cacheable('skip_old'))
+ && $cdr->calldate_unix < str2time($self->option_cacheable('skip_old'));
+
#all right then, rate it
'';
}
0;
}
-# This equates svc_phone records; perhaps svc_phone should have a field
-# to indicate it represents a line
-sub calc_units {
- my($self, $cust_pkg ) = @_;
- my $count = 0;
- if ( $self->option('count_available_phones', 1)) {
- map { $count += ( $_->quantity || 0 ) }
- grep { $_->part_svc->svcdb eq 'svc_phone' }
- $cust_pkg->part_pkg->pkg_svc;
- } else {
- $count =
- scalar(grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc);
- }
- $count;
+sub reset_usage {
+ my ($self, $cust_pkg, %opt) = @_;
+ my @part_pkg_usage = $self->part_pkg_usage or return '';
+ warn " resetting usage minutes\n" if $opt{debug};
+ my %cust_pkg_usage = map { $_->pkgusagepart, $_ } $cust_pkg->cust_pkg_usage;
+ foreach my $part_pkg_usage (@part_pkg_usage) {
+ my $part = $part_pkg_usage->pkgusagepart;
+ my $usage = $cust_pkg_usage{$part} ||
+ FS::cust_pkg_usage->new({
+ 'pkgnum' => $cust_pkg->pkgnum,
+ 'pkgusagepart' => $part,
+ 'minutes' => $part_pkg_usage->minutes *
+ ($cust_pkg->quantity || 1),
+ });
+ foreach my $cdr_usage (
+ qsearch('cdr_cust_pkg_usage', {'cdrusagenum' => $usage->cdrusagenum})
+ ) {
+ my $error = $cdr_usage->delete;
+ warn " error resetting CDR usage: $error\n";
+ }
+
+ if ( $usage->pkgusagenum ) {
+ if ( $part_pkg_usage->rollover ) {
+ $usage->set('minutes', $part_pkg_usage->minutes + $usage->minutes);
+ } else {
+ $usage->set('minutes', $part_pkg_usage->minutes);
+ }
+ my $error = $usage->replace;
+ warn " error resetting usage minutes: $error\n" if $error;
+ } else {
+ my $error = $usage->insert;
+ warn " error resetting usage minutes: $error\n" if $error;
+ }
+ } #foreach $part_pkg_usage
}
# tells whether cust_bill_pkg_detail should return a single line for
# each phonenum
+# i think this is currently unused?
sub sum_usage {
my $self = shift;
$self->option('output_format') =~ /^sum_/;
}
-sub sum_detail {
- my $self = shift;
- my $svc_x = shift;
- my $invoice_details = shift || [];
- return () if !@$invoice_details;
- my $details_by_rate = {};
- # combine the entire set of CDRs
- foreach ( @$invoice_details ) {
- 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 @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
# (separate from usage details)
sub hide_svc_detail {
1;
-