diff options
Diffstat (limited to 'FS/FS/part_pkg')
30 files changed, 0 insertions, 3781 deletions
diff --git a/FS/FS/part_pkg/agent.pm b/FS/FS/part_pkg/agent.pm deleted file mode 100644 index 6ab21d6..0000000 --- a/FS/FS/part_pkg/agent.pm +++ /dev/null @@ -1,172 +0,0 @@ -package FS::part_pkg::agent; - -use strict; -use vars qw(@ISA $DEBUG $me %info); -use Date::Format; -use FS::Record qw( qsearch ); -use FS::agent; -use FS::cust_main; - -#use FS::part_pkg::recur_Common;; -#@ISA = qw(FS::part_pkg::recur_Common); -use FS::part_pkg::prorate; -@ISA = qw(FS::part_pkg::prorate); - -$DEBUG = 0; - -$me = '[FS::part_pkg::agent]'; - -%info = ( - 'name' => 'Wholesale bulk billing, for master customers of an agent.', - 'shortname' => 'Wholesale bulk billing for agent.', - 'inherit_fields' => [qw( prorate global_Mixin)], - 'fields' => { - #'recur_method' => { 'name' => 'Recurring fee method', - # #'type' => 'radio', - # #'options' => \%recur_method, - # 'type' => 'select', - # 'select_options' => \%recur_Common::recur_method, - # }, - 'cutoff_day' => { 'name' => 'Billing Day (1 - 28)', - 'default' => '1', - }, - 'add_full_period'=> { 'name' => 'When prorating first month, also bill '. - 'for one full period after that', - 'type' => 'checkbox', - }, - - 'no_pkg_prorate' => { 'name' => 'Disable prorating bulk packages (charge full price for packages active only a portion of the month)', - 'type' => 'checkbox', - }, - - }, - - 'fieldorder' => [qw( cutoff_day add_full_period no_pkg_prorate ) ], - - 'weight' => 51, - -); - -#some false laziness-ish w/bulk.pm... not a lot -sub calc_recur { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - my $last_bill = $cust_pkg->last_bill; - - return sprintf("%.2f", $self->SUPER::calc_recur(@_) ) - unless $$sdate > $last_bill; - - my $conf = new FS::Conf; - my $money_char = $conf->config('money_char') || '$'; - - my $total_agent_charge = 0; - - warn "$me billing for agent packages from ". time2str('%x', $last_bill). - " to ". time2str('%x', $$sdate). "\n" - if $DEBUG; - - my $prorate_ratio = ( $$sdate - $last_bill ) - / ( $self->add_freq($last_bill) - $last_bill ); - - #almost always just one, - #unless you have multiple agents with same master customer0 - my @agents = qsearch('agent', { 'agent_custnum' => $cust_pkg->custnum } ); - - foreach my $agent (@agents) { - - warn "$me billing for agent ". $agent->agent. "\n" - if $DEBUG; - - #not the most efficient to load them all into memory, - #but good enough for our current needs - my @cust_main = qsearch('cust_main', { 'agentnum' => $agent->agentnum } ); - - foreach my $cust_main (@cust_main) { - - warn "$me billing agent charges for ". $cust_main->name_short. "\n" - if $DEBUG; - - #make sure setup dates are filled in - my $error = $cust_main->bill; #options don't propogate from freeside-daily - die "Error pre-billing agent customer: $error" if $error; - - my @cust_pkg = grep { my $setup = $_->get('setup'); - my $cancel = $_->get('cancel'); - - $setup < $$sdate # END - && ( ! $cancel || $cancel > $last_bill ) #START - } - $cust_main->all_pkgs; - - foreach my $cust_pkg ( @cust_pkg ) { - - warn "$me billing agent charges for pkgnum ". $cust_pkg->pkgnum. "\n" - if $DEBUG; - - my $pkg_details = $cust_main->name_short. ': '; #name? - # + something to identify package... primary service probably - - my $pkg_charge = 0; - - my $part_pkg = $cust_pkg->part_pkg; - #option to not fallback? via options above - my $pkg_setup_fee = - $part_pkg->setup_cost || $part_pkg->option('setup_fee'); - my $pkg_base_recur = - $part_pkg->recur_cost || $part_pkg->base_recur_permonth($cust_pkg); - - my $pkg_start = $cust_pkg->get('setup'); - if ( $pkg_start < $last_bill ) { - $pkg_start = $last_bill; - } elsif ( $pkg_setup_fee ) { - $pkg_charge += $pkg_setup_fee; - $pkg_details .= $money_char. sprintf('%.2f setup, ', $pkg_setup_fee ); - } - - my $pkg_end = $cust_pkg->get('cancel'); - $pkg_end = ( !$pkg_end || $pkg_end > $$sdate ) ? $$sdate : $pkg_end; - - - my $pkg_recur_charge = $prorate_ratio * $pkg_base_recur; - $pkg_recur_charge *= ( $pkg_end - $pkg_start ) - / ( $$sdate - $last_bill ) - unless $self->option('no_pkg_prorate'); - - my $recur_charge += $pkg_recur_charge; - - $pkg_details .= $money_char. sprintf('%.2f', $recur_charge ). - ' ('. time2str('%x', $pkg_start). - ' - '. time2str('%x', $pkg_end ). ')' - if $recur_charge; - - $pkg_charge += $recur_charge; - - push @$details, $pkg_details - if $pkg_charge; - $total_agent_charge += $pkg_charge; - - } #foreach $cust_pkg - - } #foreach $cust_main - - } #foreach $agent; - - my $charges = $total_agent_charge + $self->SUPER::calc_recur(@_); #prorate - - sprintf('%.2f', $charges ); - -} - -sub can_discount { 0; } - -sub hide_svc_detail { - 1; -} - -sub is_free { - 0; -} - -1; - diff --git a/FS/FS/part_pkg/base_delayed.pm b/FS/FS/part_pkg/base_delayed.pm deleted file mode 100644 index c6864a6..0000000 --- a/FS/FS/part_pkg/base_delayed.pm +++ /dev/null @@ -1,42 +0,0 @@ -package FS::part_pkg::base_delayed; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::base_rate; - -@ISA = qw(FS::part_pkg::base_rate); - -%info = ( - 'name' => 'Free (or setup fee) for X days, then base rate'. - ' (anniversary billing)', - 'shortname' => 'Bulk (manual from "units" option), w/intro period', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'free_days' => { 'name' => 'Initial free days', - 'default' => 0, - }, - 'recur_notify' => { 'name' => 'Number of days before recurring billing'. - ' commences to notify customer. (0 means'. - ' no warning)', - 'default' => 0, - }, - }, - 'fieldorder' => [ 'free_days', 'recur_notify', - ], - #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value', - #'recur' => 'what.recur_fee.value', - 'weight' => 54, #&g! -); - -sub calc_setup { - my($self, $cust_pkg, $time ) = @_; - - my $d = $cust_pkg->bill || $time; - $d += 86400 * $self->option('free_days'); - $cust_pkg->bill($d); - - $self->option('setup_fee'); -} - -1; diff --git a/FS/FS/part_pkg/base_rate.pm b/FS/FS/part_pkg/base_rate.pm deleted file mode 100644 index 6781977..0000000 --- a/FS/FS/part_pkg/base_rate.pm +++ /dev/null @@ -1,83 +0,0 @@ -package FS::part_pkg::base_rate; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch); -use FS::part_pkg; - -@ISA = qw(FS::part_pkg); - -%info = ( - 'name' => 'Base rate (anniversary billing, Times units ordered)', - # XXX it multiplies recurring fee by cust_pkg option "units", how to - # express that - 'shortname' => 'Bulk (manual from "units" option)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'externalid' => { 'name' => 'Optional External ID', - 'default' => '', - }, - }, - 'fieldorder' => [ qw( externalid ) ], - 'weight' => 52, -); - -sub calc_setup { - my($self, $cust_pkg, $sdate, $details ) = @_; - - my $i = 0; - my $count = $self->option( 'additional_count', 'quiet' ) || 0; - while ($i < $count) { - push @$details, $self->option( 'additional_info' . $i++ ); - } - - $self->option('setup_fee'); -} - -sub calc_recur { - my($self, $cust_pkg) = @_; - $self->base_recur($cust_pkg); -} - -sub base_recur { - my($self, $cust_pkg) = @_; - my $units = $cust_pkg->option('units') ? $cust_pkg->option('units') : 1 ; - # default to 1 if not found - sprintf("%.2f", - ($self->option('recur_fee') * $units ) - ); -} - -sub calc_remain { - my ($self, $cust_pkg, %options) = @_; - my $time = $options{'time'} || time; - my $next_bill = $cust_pkg->getfield('bill') || 0; - return 0 if ! $self->base_recur($cust_pkg) - || ! $next_bill - || $next_bill < $time; - - my %sec = ( - 'h' => 3600, # 60 * 60 - 'd' => 86400, # 60 * 60 * 24 - 'w' => 604800, # 60 * 60 * 24 * 7 - 'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12 - ); - - $self->freq =~ /^(\d+)([hdwm]?)$/ - or die 'unparsable frequency: '. $self->freq; - my $freq_sec = $1 * $sec{$2||'m'}; - return 0 unless $freq_sec; - - sprintf("%.2f", $self->base_recur($cust_pkg) * ( $next_bill - $time ) / $freq_sec ); - -} - -sub is_free_options { - qw( setup_fee recur_fee ); -} - -sub is_prepaid { - 0; #no, we're postpaid -} - -1; diff --git a/FS/FS/part_pkg/bulk.pm b/FS/FS/part_pkg/bulk.pm deleted file mode 100644 index 0df929e..0000000 --- a/FS/FS/part_pkg/bulk.pm +++ /dev/null @@ -1,130 +0,0 @@ -package FS::part_pkg::bulk; - -use strict; -use vars qw(@ISA $DEBUG $me %info); -use Date::Format; -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -$DEBUG = 0; -$me = '[FS::part_pkg::bulk]'; - -%info = ( - 'name' => 'Bulk billing based on number of active services', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'svc_setup_fee' => { 'name' => 'Setup fee for each new service', - 'default' => 0, - }, - 'svc_recur_fee' => { 'name' => 'Recurring fee for each service', - 'default' => 0, - }, - 'summarize_svcs'=> { 'name' => 'Show a count of services on the invoice, '. - 'instead of a detailed list', - 'type' => 'checkbox', - }, - 'no_prorate' => { 'name' => 'Don\'t prorate recurring fees on services '. - 'active for a partial month', - 'type' => 'checkbox', - }, - }, - 'fieldorder' => [ 'svc_setup_fee', 'svc_recur_fee', - 'summarize_svcs', 'no_prorate' ], - 'weight' => 50, -); - -#some false laziness-ish w/agent.pm... not a lot -sub calc_recur { - my($self, $cust_pkg, $sdate, $details ) = @_; - - my $conf = new FS::Conf; - my $money_char = $conf->config('money_char') || '$'; - - my $svc_setup_fee = $self->option('svc_setup_fee'); - - my $last_bill = $cust_pkg->last_bill; - - return sprintf("%.2f", $self->base_recur($cust_pkg) ) - unless $$sdate > $last_bill; - - my $total_svc_charge = 0; - my %n_setup = (); - my %n_recur = (); - my %part_svc_label = (); - - my $summarize = $self->option('summarize_svcs',1); - - warn "$me billing for bulk services from ". time2str('%x', $last_bill). - " to ". time2str('%x', $$sdate). "\n" - if $DEBUG; - - # END START - foreach my $h_cust_svc ( $cust_pkg->h_cust_svc( $$sdate, $last_bill ) ) { - - my @label = $h_cust_svc->label_long( $$sdate, $last_bill ); - die "fatal: no historical label found, wtf?" unless scalar(@label); #? - my $svc_details = $label[0]. ': '. $label[1]. ': '; - $part_svc_label{$h_cust_svc->svcpart} ||= $label[0]; - - my $svc_charge = 0; - - my $svc_start = $h_cust_svc->date_inserted; - if ( $svc_start < $last_bill ) { - $svc_start = $last_bill; - } elsif ( $svc_setup_fee ) { - $svc_charge += $svc_setup_fee; - $svc_details .= $money_char. sprintf('%.2f setup, ', $svc_setup_fee); - $n_setup{$h_cust_svc->svcpart}++; - } - - my $svc_end = $h_cust_svc->date_deleted; - $svc_end = ( !$svc_end || $svc_end > $$sdate ) ? $$sdate : $svc_end; - - my $recur_charge; - if ( $self->option('no_prorate',1) ) { - $recur_charge = $self->option('svc_recur_fee'); - } - else { - $recur_charge = $self->option('svc_recur_fee') - * ( $svc_end - $svc_start ) - / ( $$sdate - $last_bill ); - } - - $svc_details .= $money_char. sprintf('%.2f', $recur_charge ). - ' ('. time2str('%x', $svc_start). - ' - '. time2str('%x', $svc_end ). ')' - if $recur_charge; - - $svc_charge += $recur_charge; - $n_recur{$h_cust_svc->svcpart}++; - push @$details, $svc_details if !$summarize; - $total_svc_charge += $svc_charge; - - } - if ( $summarize ) { - foreach my $svcpart (keys %part_svc_label) { - push @$details, sprintf('Setup fee: %d @ '.$money_char.'%.2f', - $n_setup{$svcpart}, $svc_setup_fee ) - if $svc_setup_fee and $n_setup{$svcpart}; - push @$details, sprintf('%d services @ '.$money_char.'%.2f', - $n_recur{$svcpart}, $self->option('svc_recur_fee') ) - if $n_recur{$svcpart}; - } - } - - sprintf('%.2f', $self->base_recur($cust_pkg) + $total_svc_charge ); -} - -sub can_discount { 0; } - -sub hide_svc_detail { - 1; -} - -sub is_free_options { - qw( setup_fee recur_fee svc_setup_fee svc_recur_fee ); -} - -1; - diff --git a/FS/FS/part_pkg/cdr_termination.pm b/FS/FS/part_pkg/cdr_termination.pm deleted file mode 100644 index 840da82..0000000 --- a/FS/FS/part_pkg/cdr_termination.pm +++ /dev/null @@ -1,204 +0,0 @@ -package FS::part_pkg::cdr_termination; - -use strict; -use base qw( FS::part_pkg::recur_Common ); -use vars qw( $DEBUG %info ); -use Tie::IxHash; -use FS::Record qw( qsearch ); #qsearchs ); -use FS::cdr; -use FS::cdr_termination; - -tie my %temporalities, 'Tie::IxHash', - 'upcoming' => "Upcoming (future)", - 'preceding' => "Preceding (past)", -; - -%info = ( - 'name' => 'VoIP rating of CDR records for termination partners.', - 'shortname' => 'VoIP/telco CDR termination', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - #'cdr_column' => { 'name' => 'Column from CDR records', - # 'type' => 'select', - # 'select_enum' => [qw( - # dcontext - # channel - # dstchannel - # lastapp - # lastdata - # accountcode - # userfield - # cdrtypenum - # calltypenum - # description - # carrierid - # upstream_rateid - # )], - # }, - - #false laziness w/flat.pm - 'recur_temporality' => { 'name' => 'Charge recurring fee for period', - 'type' => 'select', - 'select_options' => \%temporalities, - }, - - 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '. - 'subscription', - 'default' => '1', - }, - 'add_full_period'=> { 'name' => 'When prorating first month, also bill '. - 'for one full period after that', - 'type' => 'checkbox', - }, - - 'recur_method' => { 'name' => 'Recurring fee method', - #'type' => 'radio', - #'options' => \%recur_method, - 'type' => 'select', - 'select_options' => \%FS::part_pkg::recur_Common::recur_method, - }, - - #false laziness w/voip_cdr.pm - 'output_format' => { 'name' => 'CDR invoice display format', - 'type' => 'select', - 'select_options' => { FS::cdr::invoice_formats() }, - 'default' => 'simple2', #XXX test - }, - - 'usage_section' => { 'name' => 'Section in which to place separate usage charges', - }, - - 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section', - 'type' => 'checkbox', - }, - - 'usage_mandate' => { 'name' => 'Always put usage details in separate section', - 'type' => 'checkbox', - }, - #eofalse - - }, - #cdr_column - 'fieldorder' => [qw( - recur_temporality recur_method cutoff_day - add_full_period - output_format usage_section summarize_usage usage_mandate - ) - ], - - 'weight' => 48, - -); - -sub calc_setup { - my($self, $cust_pkg ) = @_; - $self->option('setup_fee'); -} - -sub calc_recur { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - #my $last_bill = $cust_pkg->last_bill; - my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup - - return 0 - if $self->option('recur_temporality', 1) eq 'preceding' - && ( $last_bill eq '' || $last_bill == 0 ); - - # termination calculations - - my $term_percent = $cust_pkg->cust_main->cdr_termination_percentage; - die "no customer termination percentage" unless $term_percent; - - my $output_format = $self->option('output_format', 'Hush!') || 'simple2'; - - my $charges = 0; - - #find an svc_external record - my @svc_external = map { $_->svc_x } - grep { $_->part_svc->svcdb eq 'svc_external' } - $cust_pkg->cust_svc; - - die "cdr_termination package has no svc_external service" - unless @svc_external; - die "cdr_termination package has multiple svc_external services" - if scalar(@svc_external) > 1; - - my $svc_external = $svc_external[0]; - - # find CDRs: - # - matching our customer via svc_external.id/title? (and via what field?) - - #let's try carrierid for now, can always make it configurable or rewrite - my $cdr_column = 'carrierid'; - - my %hashref = ( 'freesidestatus' => 'done' ); - - # try matching on svc_external.id for now... (or title? if ints don't cut it) - $hashref{$cdr_column} = $svc_external[0]->id; - - # - with no cdr_termination.status - - my $termpart = 1; #or from an option - - #false lazienss w/search/cdr.html (i should be a part_termination method) - my $where_term = - "( cdr.acctid = cdr_termination.acctid AND termpart = $termpart ) "; - #my $join_term = "LEFT JOIN cdr_termination ON ( $where_term )"; - my $extra_sql = - "AND NOT EXISTS ( SELECT 1 FROM cdr_termination WHERE $where_term )"; - - #may need to process in batches if there's waaay too many - my @cdrs = qsearch({ - 'table' => 'cdr', - #'addl_from' => $join_term, - 'hashref' => \%hashref, - 'extra_sql' => "$extra_sql FOR UPDATE", - }); - - foreach my $cdr (@cdrs) { - - #add a cdr_termination record and the charges - - # XXX config? - #my $term_price = sprintf('%.2f', $cdr->rated_price * $term_percent / 100 ); - my $term_price = sprintf('%.4f', $cdr->rated_price * $term_percent / 100 ); - - my $cdr_termination = new FS::cdr_termination { - 'acctid' => $cdr->acctid, - 'termpart' => $termpart, - 'rated_price' => $term_price, - 'status' => 'done', - }; - - my $error = $cdr_termination->insert; - die $error if $error; #next if $error; #or just skip this one??? why? - - $charges += $term_price; - - # and add a line to the invoice - - my $call_details = $cdr->downstream_csv( 'format' => $output_format, - 'charge' => $term_price, - ); - - my $classnum = ''; #usage class? - - #option to turn off? or just use squelch_cdr for the customer probably - push @$details, [ 'C', $call_details, $term_price, $classnum ]; - - } - - # eotermiation calculation - - $charges += $self->calc_recur_Common(@_); - - $charges; -} - -sub is_free { - 0; -} - -1; diff --git a/FS/FS/part_pkg/discount_Mixin.pm b/FS/FS/part_pkg/discount_Mixin.pm deleted file mode 100644 index df65e97..0000000 --- a/FS/FS/part_pkg/discount_Mixin.pm +++ /dev/null @@ -1,128 +0,0 @@ -package FS::part_pkg::discount_Mixin; - -use strict; -use vars qw(@ISA %info); -use FS::part_pkg; -use FS::cust_pkg; -use FS::cust_bill_pkg_discount; -use Time::Local qw(timelocal); -use List::Util 'min'; - -@ISA = qw(FS::part_pkg); -%info = ( 'disabled' => 1 ); - -=head1 NAME - -FS::part_pkg::discount_Mixin - Mixin class for part_pkg:: classes that -can be discounted. - -=head1 SYNOPSIS - -package FS::part_pkg::...; -use base qw( FS::part_pkg::discount_Mixin ); - -sub calc_recur { - ... - my $discount = $self->calc_discount($cust_pkg, $$sdate, $details, $param); - $charge -= $discount; - ... -} - -=head METHODS - -=item calc_discount - -Takes all the arguments of calc_recur. Calculates and returns the amount -by which to reduce the recurring fee; also increments months used on the -discount and generates an invoice detail describing it. - -=cut - -sub calc_discount { - my($self, $cust_pkg, $sdate, $details, $param ) = @_; - - my $br = $self->base_recur($cust_pkg); - - my $tot_discount = 0; - #UI enforces just 1 for now, will need ordering when they can be stacked - - if ( $param->{freq_override} ) { - # When a customer pays for more than one month at a time to receive a - # term discount, freq_override is set to the number of months. - my $real_part_pkg = new FS::part_pkg { $self->hash }; - $real_part_pkg->pkgpart($param->{real_pkgpart} || $self->pkgpart); - # Find a discount with that duration... - my @discount = grep { $_->months == $param->{freq_override} } - map { $_->discount } $real_part_pkg->part_pkg_discount; - my $discount = shift @discount; - # and default to bill that many months at once. - $param->{months} = $param->{freq_override} unless $param->{months}; - my $error; - if ($discount) { - # Then set the cust_pkg discount. - if ($discount->months == $param->{months}) { - $cust_pkg->discountnum($discount->discountnum); - $error = $cust_pkg->insert_discount; - } else { - $cust_pkg->discountnum(-1); - foreach ( qw( amount percent months ) ) { - my $method = "discountnum_$_"; - $cust_pkg->$method($discount->$_); - } - $error = $cust_pkg->insert_discount; - } - die "error discounting using part_pkg_discount: $error" if $error; - } - } - - my @cust_pkg_discount = $cust_pkg->cust_pkg_discount_active; - foreach my $cust_pkg_discount ( @cust_pkg_discount ) { - my $discount = $cust_pkg_discount->discount; - #UI enforces one or the other (for now? probably for good) - my $amount = 0; - $amount += $discount->amount - if $cust_pkg->pkgpart == $param->{real_pkgpart}; - $amount += sprintf('%.2f', $discount->percent * $br / 100 ); - my $chg_months = $param->{'months'} || $cust_pkg->part_pkg->freq; - - my $months = $discount->months - ? min( $chg_months, - $discount->months - $cust_pkg_discount->months_used ) - : $chg_months; - - my $error = $cust_pkg_discount->increment_months_used($months) - if $cust_pkg->pkgpart == $param->{real_pkgpart}; - die "error discounting: $error" if $error; - - $amount *= $months; - $amount = sprintf('%.2f', $amount); - - next unless $amount > 0; - - #record details in cust_bill_pkg_discount - my $cust_bill_pkg_discount = new FS::cust_bill_pkg_discount { - 'pkgdiscountnum' => $cust_pkg_discount->pkgdiscountnum, - 'amount' => $amount, - 'months' => $months, - }; - push @{ $param->{'discounts'} }, $cust_bill_pkg_discount; - - #add details on discount to invoice - my $conf = new FS::Conf; - my $money_char = $conf->config('money_char') || '$'; - $months = sprintf('%.2f', $months) if $months =~ /\./; - - my $d = 'Includes '; - $d .= $discount->name. ' ' if $discount->name; - $d .= 'discount of '. $discount->description_short; - $d .= " for $months month". ( $months!=1 ? 's' : '' ); - $d .= ": $money_char$amount" if $months != 1 || $discount->percent; - push @$details, $d; - - $tot_discount += $amount; - } - - sprintf('%.2f', $tot_discount); -} - -1; diff --git a/FS/FS/part_pkg/flat.pm b/FS/FS/part_pkg/flat.pm deleted file mode 100644 index b5e0fa0..0000000 --- a/FS/FS/part_pkg/flat.pm +++ /dev/null @@ -1,204 +0,0 @@ -package FS::part_pkg::flat; - -use strict; -use vars qw( @ISA %info - %usage_recharge_fields @usage_recharge_fieldorder - ); -use Tie::IxHash; -use List::Util qw(min); # max); -#use FS::Record qw(qsearch); -use FS::UI::bytecount; -use FS::Conf; -use FS::part_pkg; - -@ISA = qw(FS::part_pkg - FS::part_pkg::prorate_Mixin - FS::part_pkg::discount_Mixin); - -tie my %temporalities, 'Tie::IxHash', - 'upcoming' => "Upcoming (future)", - 'preceding' => "Preceding (past)", -; - -tie my %contract_years, 'Tie::IxHash', ( - '' => '(none)', - map { $_*12 => $_ } (1..5), -); - -%info = ( - 'name' => 'Flat rate (anniversary billing)', - 'shortname' => 'Anniversary', - 'inherit_fields' => [ 'usage_Mixin', 'global_Mixin' ], - 'fields' => { - #false laziness w/voip_cdr.pm - 'recur_temporality' => { 'name' => 'Charge recurring fee for period', - 'type' => 'select', - 'select_options' => \%temporalities, - }, - - #used in cust_pkg.pm so could add to any price plan - 'expire_months' => { 'name' => 'Auto-add an expiration date this number of months out', - }, - 'adjourn_months'=> { 'name' => 'Auto-add a suspension date this number of months out', - }, - 'contract_end_months'=> { - 'name' => 'Auto-add a contract end date this number of years out', - 'type' => 'select', - 'select_options' => \%contract_years, - }, - #used in cust_pkg.pm so could add to any price plan where it made sense - 'start_1st' => { 'name' => 'Auto-add a start date to the 1st, ignoring the current month.', - 'type' => 'checkbox', - }, - 'sync_bill_date' => { 'name' => 'Prorate first month to synchronize '. - 'with the customer\'s other packages', - 'type' => 'checkbox', - }, - 'suspend_bill' => { 'name' => 'Continue recurring billing while suspended', - 'type' => 'checkbox', - }, - 'unsuspend_adjust_bill' => - { 'name' => 'Adjust next bill date forward when '. - 'unsuspending', - 'type' => 'checkbox', - }, - - 'externalid' => { 'name' => 'Optional External ID', - 'default' => '', - }, - }, - 'fieldorder' => [ qw( recur_temporality - expire_months adjourn_months - contract_end_months - start_1st sync_bill_date - suspend_bill unsuspend_adjust_bill - externalid ), - ], - 'weight' => 10, -); - -sub calc_setup { - my($self, $cust_pkg, $sdate, $details ) = @_; - - my $i = 0; - my $count = $self->option( 'additional_count', 'quiet' ) || 0; - while ($i < $count) { - push @$details, $self->option( 'additional_info' . $i++ ); - } - - my $quantity = $cust_pkg->quantity || 1; - - sprintf("%.2f", $quantity * $self->unit_setup($cust_pkg, $sdate, $details) ); -} - -sub unit_setup { - my($self, $cust_pkg, $sdate, $details ) = @_; - - $self->option('setup_fee') || 0; -} - -sub calc_recur { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - #my $last_bill = $cust_pkg->last_bill; - my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup - - return 0 - if $self->option('recur_temporality', 1) eq 'preceding' && $last_bill == 0; - - my $charge = $self->base_recur($cust_pkg); - if ( $self->option('sync_bill_date',1) ) { - my $next_bill = $cust_pkg->cust_main->next_bill_date; - if ( defined($next_bill) ) { - my $cutoff_day = (localtime($next_bill))[3]; - $charge = $self->calc_prorate(@_, $cutoff_day); - } - } - elsif ( $param->{freq_override} ) { - # XXX not sure if this should be mutually exclusive with sync_bill_date. - # Given the very specific problem that freq_override is meant to 'solve', - # it probably should. - $charge *= $param->{freq_override} if $param->{freq_override}; - } - - my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); - return sprintf('%.2f', $charge - $discount); -} - -sub base_recur { - my($self, $cust_pkg) = @_; - $self->option('recur_fee', 1) || 0; -} - -sub base_recur_permonth { - my($self, $cust_pkg) = @_; - - return 0 unless $self->freq =~ /^\d+$/ && $self->freq > 0; - - sprintf('%.2f', $self->base_recur($cust_pkg) / $self->freq ); -} - -sub calc_remain { - my ($self, $cust_pkg, %options) = @_; - - my $time; - if ($options{'time'}) { - $time = $options{'time'}; - } else { - $time = time; - } - - my $next_bill = $cust_pkg->getfield('bill') || 0; - - return 0 if ! $self->base_recur($cust_pkg) - || ! $next_bill - || $next_bill < $time; - - my %sec = ( - 'h' => 3600, # 60 * 60 - 'd' => 86400, # 60 * 60 * 24 - 'w' => 604800, # 60 * 60 * 24 * 7 - 'm' => 2629744, # 60 * 60 * 24 * 365.2422 / 12 - ); - - $self->freq =~ /^(\d+)([hdwm]?)$/ - or die 'unparsable frequency: '. $self->freq; - my $freq_sec = $1 * $sec{$2||'m'}; - return 0 unless $freq_sec; - - sprintf("%.2f", $self->base_recur($cust_pkg) * ( $next_bill - $time ) / $freq_sec ); - -} - -sub is_free_options { - qw( setup_fee recur_fee ); -} - -sub is_prepaid { 0; } #no, we're postpaid - -#XXX discounts only on recurring fees for now (no setup/one-time or usage) -sub can_discount { - my $self = shift; - $self->freq =~ /^\d+$/ && $self->freq > 0; -} - -sub usage_valuehash { - my $self = shift; - map { $_, $self->option($_) } - grep { $self->option($_, 'hush') } - qw(seconds upbytes downbytes totalbytes); -} - -sub reset_usage { - my($self, $cust_pkg, %opt) = @_; - warn " resetting usage counters" if defined($opt{debug}) && $opt{debug} > 1; - my %values = $self->usage_valuehash; - if ($self->option('usage_rollover', 1)) { - $cust_pkg->recharge(\%values); - }else{ - $cust_pkg->set_usage(\%values, %opt); - } -} - -1; diff --git a/FS/FS/part_pkg/flat_comission.pm b/FS/FS/part_pkg/flat_comission.pm deleted file mode 100644 index ec8c8eb..0000000 --- a/FS/FS/part_pkg/flat_comission.pm +++ /dev/null @@ -1,60 +0,0 @@ -package FS::part_pkg::flat_comission; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Flat rate with recurring commission per (any) active package', - 'shortname' => 'Commission per (any) active package', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'comission_amount' => { 'name' => 'Commission amount per month (per active package)', - 'default' => 0, - }, - 'comission_depth' => { 'name' => 'Number of layers', - 'default' => 1, - }, - 'reason_type' => { 'name' => 'Reason type for commission credits', - 'type' => 'select', - 'select_table' => 'reason_type', - 'select_hash' => { 'class' => 'R' }, - 'select_key' => 'typenum', - 'select_label' => 'type', - }, - }, - 'fieldorder' => [ 'comission_depth', 'comission_amount', 'reason_type' ], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'', - 'weight' => 62, -); - -sub calc_recur { - my($self, $cust_pkg ) = @_; - - my $amount = $self->option('comission_amount'); - my $num_active = scalar( - $cust_pkg->cust_main->referral_cust_pkg( $self->option('comission_depth') ) - ); - - my $commission = sprintf('%.2f', $amount*$num_active); - - if ( $commission > 0 ) { - - my $error = - $cust_pkg->cust_main->credit( $commission, "commission", - 'reason_type'=>$self->option('reason_type'), - ); - die $error if $error; - - } - - $self->option('recur_fee'); -} - -sub can_discount { 0; } - -1; diff --git a/FS/FS/part_pkg/flat_comission_cust.pm b/FS/FS/part_pkg/flat_comission_cust.pm deleted file mode 100644 index 5acf73d..0000000 --- a/FS/FS/part_pkg/flat_comission_cust.pm +++ /dev/null @@ -1,44 +0,0 @@ -package FS::part_pkg::flat_comission_cust; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Flat rate with recurring commission per active customer', - 'shortname' => 'Commission per active customer', - 'inherit_fields' => [ 'flat_comission', 'global_Mixin' ], - 'fields' => { }, - 'fieldorder' => [ ], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar($cust_pkg->cust_main->referral_cust_main_ncancelled(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'', - 'weight' => '60', -); - -sub calc_recur { - my($self, $cust_pkg ) = @_; - - my $amount = $self->option('comission_amount'); - my $num_active = scalar( - $cust_pkg->cust_main->referral_cust_main_ncancelled( - $self->option('comission_depth') - ) - ); - - if ( $amount && $num_active ) { - my $error = - $cust_pkg->cust_main->credit( $amount*$num_active, "commission", - 'reason_type'=>$self->option('reason_type'), - ); - die $error if $error; - } - - $self->option('recur_fee'); -} - -sub can_discount { 0; } - -1; diff --git a/FS/FS/part_pkg/flat_comission_pkg.pm b/FS/FS/part_pkg/flat_comission_pkg.pm deleted file mode 100644 index 26dd4d2..0000000 --- a/FS/FS/part_pkg/flat_comission_pkg.pm +++ /dev/null @@ -1,38 +0,0 @@ -package FS::part_pkg::flat_comission_pkg; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Flat rate with recurring commission per (selected) active package', - 'shortname' => 'Commission per (selected) active package', - 'inherit_fields' => [ 'flat_comission', 'global_Mixin' ], - 'fields' => { - 'comission_pkgpart' => { 'name' => 'Applicable packages<BR><FONT SIZE="-1">(hold <b>ctrl</b> to select multiple packages)</FONT>', - 'type' => 'select_multiple', - 'select_table' => 'part_pkg', - 'select_hash' => { 'disabled' => '' } , - 'select_key' => 'pkgpart', - 'select_label' => 'pkg', - }, - }, - 'fieldorder' => [ 'comission_depth', 'comission_amount', 'comission_pkgpart', 'reason_type' ], - #'setup' => 'what.setup_fee.value', - #'recur' => '""; var pkgparts = ""; for ( var c=0; c < document.flat_comission_pkg.comission_pkgpart.options.length; c++ ) { if (document.flat_comission_pkg.comission_pkgpart.options[c].selected) { pkgparts = pkgparts + document.flat_comission_pkg.comission_pkgpart.options[c].value + \', \'; } } what.recur.value = \'my $error = $cust_pkg->cust_main->credit( \' + what.comission_amount.value + \' * scalar( grep { my $pkgpart = $_->pkgpart; grep { $_ == $pkgpart } ( \' + pkgparts + \' ) } $cust_pkg->cust_main->referral_cust_pkg(\' + what.comission_depth.value+ \')), "commission" ); die $error if $error; \' + what.recur_fee.value + \';\'', - #'disabled' => 1, - 'weight' => '64', -); - -# XXX this needs to be fixed!!! -sub calc_recur { - my($self, $cust_pkg ) = @_; - $self->option('recur_fee'); -} - -sub can_discount { 0; } - -1; diff --git a/FS/FS/part_pkg/flat_delayed.pm b/FS/FS/part_pkg/flat_delayed.pm deleted file mode 100644 index b4be72b..0000000 --- a/FS/FS/part_pkg/flat_delayed.pm +++ /dev/null @@ -1,54 +0,0 @@ -package FS::part_pkg::flat_delayed; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Free (or setup fee) for X days, then flat rate'. - ' (anniversary billing)', - 'shortname' => 'Anniversary, with intro period', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'free_days' => { 'name' => 'Initial free days', - 'default' => 0, - }, - 'recur_notify' => { 'name' => 'Number of days before recurring billing'. - ' commences to notify customer. (0 means'. - ' no warning)', - 'default' => 0, - }, - }, - 'fieldorder' => [ 'free_days', 'recur_notify', - ], - #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value', - #'recur' => 'what.recur_fee.value', - 'weight' => 12, -); - -sub calc_setup { - my($self, $cust_pkg, $time ) = @_; - - my $d = $cust_pkg->bill || $time; - $d += 86400 * $self->option('free_days'); - $cust_pkg->bill($d); - - $self->option('setup_fee'); -} - -sub calc_remain { - my ($self, $cust_pkg, %options) = @_; - my $next_bill = $cust_pkg->getfield('bill') || 0; - my $last_bill = $cust_pkg->last_bill || 0; - my $free_days = $self->option('free_days'); - - return 0 if $last_bill + (86400 * $free_days) == $next_bill - && $last_bill == $cust_pkg->setup; - - return $self->SUPER::calc_remain($cust_pkg, %options); -} - -1; diff --git a/FS/FS/part_pkg/flat_introrate.pm b/FS/FS/part_pkg/flat_introrate.pm deleted file mode 100644 index 1447730..0000000 --- a/FS/FS/part_pkg/flat_introrate.pm +++ /dev/null @@ -1,60 +0,0 @@ -package FS::part_pkg::flat_introrate; - -use strict; -use vars qw(@ISA %info $DEBUG $me); -use FS::part_pkg::flat; - -use Date::Manip qw(DateCalc UnixDate ParseDate); - -@ISA = qw(FS::part_pkg::flat); -$me = '[' . __PACKAGE__ . ']'; -$DEBUG = 0; - -%info = ( - 'name' => 'Introductory price for X months, then flat rate,'. - 'relative to setup date (anniversary billing)', - 'shortname' => 'Anniversary, with intro price', - 'inherit_fields' => [ 'flat', 'usage_Mixin', 'global_Mixin' ], - 'fields' => { - 'intro_fee' => { 'name' => 'Introductory recurring fee for this package', - 'default' => 0, - }, - 'intro_duration' => - { 'name' => 'Duration of the introductory period, in number of months', - 'default' => 0, - }, - }, - 'fieldorder' => [ qw(intro_duration intro_fee) ], - 'weight' => 14, -); - -sub base_recur { - my($self, $cust_pkg, $time ) = @_; - - my $now = $time ? $$time : time; - - my ($duration) = ($self->option('intro_duration') =~ /^(\d+)$/); - unless ($duration) { - die "Invalid intro_duration: " . $self->option('intro_duration'); - } - - my $setup = &ParseDate('epoch ' . $cust_pkg->getfield('setup')); - my $intro_end = &DateCalc($setup, "+${duration} month"); - my $recur; - - warn "$me: \$duration = ${duration}" if $DEBUG; - warn "$me: \$intro_end = ${intro_end}" if $DEBUG; - warn "$me: $now < " . &UnixDate($intro_end, '%s') if $DEBUG; - - if ($now < &UnixDate($intro_end, '%s')) { - $recur = $self->option('intro_fee'); - } else { - $recur = $self->option('recur_fee'); - } - - $recur; - -} - - -1; diff --git a/FS/FS/part_pkg/global_Mixin.pm b/FS/FS/part_pkg/global_Mixin.pm deleted file mode 100644 index 56f1602..0000000 --- a/FS/FS/part_pkg/global_Mixin.pm +++ /dev/null @@ -1,38 +0,0 @@ -package FS::part_pkg::global_Mixin; - -use strict; -use vars qw(@ISA %info); -use FS::part_pkg; -@ISA = qw(FS::part_pkg); - -%info = ( - 'disabled' => 1, - 'fields' => { - 'setup_fee' => { - 'name' => 'Setup fee for this package', - 'default' => 0, - }, - 'recur_fee' => { - 'name' => 'Recurring fee for this package', - 'default' => 0, - }, - 'unused_credit_cancel' => { - 'name' => 'Credit the customer for the unused portion of service at '. - 'cancellation', - 'type' => 'checkbox', - }, - 'unused_credit_change' => { - 'name' => 'Credit the customer for the unused portion of service when '. - 'changing packages', - 'type' => 'checkbox', - }, - }, - 'fieldorder' => [ qw( - setup_fee - recur_fee - unused_credit_cancel - unused_credit_change - )], -); - -1; diff --git a/FS/FS/part_pkg/incomplete/billoneday.pm b/FS/FS/part_pkg/incomplete/billoneday.pm deleted file mode 100644 index 8740547..0000000 --- a/FS/FS/part_pkg/incomplete/billoneday.pm +++ /dev/null @@ -1,48 +0,0 @@ -package FS::part_pkg::billoneday; - -use strict; -use vars qw(@ISA %info); -use Time::Local qw(timelocal); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'charge a full month every (selectable) billing day', - 'fields' => { - 'setup_fee' => { 'name' => 'Setup fee for this package', - 'default' => 0, - }, - 'recur_fee' => { 'name' => 'Recurring fee for this package', - 'default' => 0, - }, - 'cutoff_day' => { 'name' => 'billing day', - 'default' => 1, - }, - - }, - 'fieldorder' => [ 'setup_fee', 'recur_fee','cutoff_day'], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $mnow = $sdate; my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($sdate) )[0,1,2,3,4,5]; $sdate = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); \' + what.recur_fee.value', - 'freq' => 'm', - 'weight' => 30, -); - -sub calc_recur { - my($self, $cust_pkg, $sdate ) = @_; - - my $mnow = $$sdate; - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - my $mstart = timelocal(0,0,0,$self->option('cutoff_day'),$mon,$year); - my $mend = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - - if($mday > $self->option('cutoff_date') and $mstart != $mnow ) { - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon == 11 ? 0 : $mon+1, $year+($mon==11)); - } - else{ - $$sdate = timelocal(0,0,0,$self->option('cutoff_day'), $mon, $year); - } - $self->option('recur_fee'); -} -1; diff --git a/FS/FS/part_pkg/prepaid.pm b/FS/FS/part_pkg/prepaid.pm deleted file mode 100644 index 407343b..0000000 --- a/FS/FS/part_pkg/prepaid.pm +++ /dev/null @@ -1,51 +0,0 @@ -package FS::part_pkg::prepaid; - -use strict; -use vars qw(@ISA %info %recur_action); -use Tie::IxHash; -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -tie %recur_action, 'Tie::IxHash', - 'suspend' => 'suspend', - 'cancel' => 'cancel', -; - -tie my %overlimit_action, 'Tie::IxHash', - 'overlimit' => 'Default overlimit processing', - 'cancel' => 'Cancel', -; - -%info = ( - 'name' => 'Prepaid, flat rate', - #'name' => 'Prepaid (no automatic recurring)', #maybe use it here too - 'shortname' => 'Prepaid, no automatic cycle', - 'inherit_fields' => [ 'usage_Mixin', 'global_Mixin' ], - 'fields' => { - 'recur_action' => { 'name' => 'Action to take upon reaching end of prepaid preiod', - 'type' => 'select', - 'select_options' => \%recur_action, - }, - 'overlimit_action' => { 'name' => 'Action to take upon reaching a usage limit.', - 'type' => 'select', - 'select_options' => \%overlimit_action, - }, - #XXX if you set overlimit_action to 'cancel', should also have the ability - # to select a reason - - # do we need to disable these? - map { $_ => { 'disabled' => 1 } } ( - qw(recharge_amount recharge_seconds recharge_upbytes recharge_downbytes - recharge_totalbytes usage_rollover recharge_reset) ), - }, - 'fieldorder' => [ qw( recur_action overlimit_action ) ], - 'weight' => 25, -); - -sub is_prepaid { - 1; -} - -1; - diff --git a/FS/FS/part_pkg/prorate.pm b/FS/FS/part_pkg/prorate.pm deleted file mode 100644 index 367f152..0000000 --- a/FS/FS/part_pkg/prorate.pm +++ /dev/null @@ -1,43 +0,0 @@ -package FS::part_pkg::prorate; - -use strict; -use vars qw(@ISA %info); -use Time::Local qw(timelocal); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'First partial month pro-rated, then flat-rate (selectable billing day)', - 'shortname' => 'Prorate (Nth of month billing)', - 'inherit_fields' => [ 'flat', 'usage_Mixin', 'global_Mixin' ], - 'fields' => { - 'recur_temporality' => {'disabled' => 1}, - 'sync_bill_date' => {'disabled' => 1}, - 'cutoff_day' => { 'name' => 'Billing Day (1 - 28)', - 'default' => 1, - }, - - 'add_full_period'=> { 'name' => 'When prorating first month, also bill '. - 'for one full period after that', - 'type' => 'checkbox', - }, - 'prorate_round_day'=> { - 'name' => 'When prorating first month, round to '. - 'the nearest full day', - 'type' => 'checkbox', - }, - }, - 'fieldorder' => [ 'cutoff_day', 'add_full_period', 'prorate_round_day' ], - 'freq' => 'm', - 'weight' => 20, -); - -sub calc_recur { - my $self = shift; - my $cutoff_day = $self->option('cutoff_day') || 1; - return $self->calc_prorate(@_, $cutoff_day) - $self->calc_discount(@_); -} - -1; diff --git a/FS/FS/part_pkg/prorate_Mixin.pm b/FS/FS/part_pkg/prorate_Mixin.pm deleted file mode 100644 index 3f3d86f..0000000 --- a/FS/FS/part_pkg/prorate_Mixin.pm +++ /dev/null @@ -1,105 +0,0 @@ -package FS::part_pkg::prorate_Mixin; - -use strict; -use vars qw(@ISA %info); -use Time::Local qw(timelocal); - -@ISA = qw(FS::part_pkg); -%info = ( - 'disabled' => 1, -); - -=head1 NAME - -FS::part_pkg::prorate_Mixin - Mixin class for part_pkg:: classes that -need to prorate partial months - -=head1 SYNOPSIS - -package FS::part_pkg::...; -use base qw( FS::part_pkg::prorate_Mixin ); - -sub calc_recur { - ... - if( conditions that trigger prorate ) { - # sets $$sdate and $param->{'months'}, returns the prorated charge - $charges = $self->calc_prorate($cust_pkg, $sdate, $param, $cutoff_day); - } - ... -} - -=head METHODS - -=item calc_prorate CUST_PKG - -Takes all the arguments of calc_recur, followed by a day of the month -to prorate to (which must be <= 28). Calculates a prorated charge from -the $sdate to that day, and sets the $sdate and $param->{months} accordingly. - -Options: -- recur_fee: The charge to use for a complete billing period. -- add_full_period: Bill for the time up to the prorate day plus one full -billing period after that. -- prorate_round_day: Round the current time to the nearest full day, -instead of using the exact time. - -=cut - -sub calc_prorate { - my $self = shift; - my ($cust_pkg, $sdate, $details, $param, $cutoff_day) = @_; - - my $charge = $self->option('recur_fee',1) || 0; - if($cutoff_day) { - # only works for freq >= 1 month; probably can't be fixed - my $mnow = $$sdate; - my ($sec, $min, $hour, $mday, $mon, $year) = (localtime($mnow))[0..5]; - if( $self->option('prorate_round_day',1) ) { - $mday++ if $hour >= 12; - $mnow = timelocal(0,0,0,$mday,$mon,$year); - } - my $mend; - my $mstart; - # if cutoff day > 28, force it to the 1st of next month - if ( $cutoff_day > 28 ) { - $cutoff_day = 1; - # and if we are currently after the 28th, roll the current day - # forward to that day - if ( $mday > 28 ) { - $mday = 1; - #set $mnow = $mend so the amount billed will be zero - $mnow = timelocal(0,0,0,1,$mon == 11 ? 0 : $mon + 1,$year+($mon==11)); - } - } - if ( $mday >= $cutoff_day ) { - $mend = - timelocal(0,0,0,$cutoff_day,$mon == 11 ? 0 : $mon + 1,$year+($mon==11)); - $mstart = - timelocal(0,0,0,$cutoff_day,$mon,$year); - } - else { - $mend = - timelocal(0,0,0,$cutoff_day,$mon,$year); - $mstart = - timelocal(0,0,0,$cutoff_day,$mon == 0 ? 11 : $mon - 1,$year-($mon==0)); - } - - # next bill date will be figured as $$sdate + one period - $$sdate = $mstart; - - my $permonth = $charge / $self->freq; - my $months = ( ( $self->freq - 1 ) + ($mend-$mnow) / ($mend-$mstart) ); - - if ( $self->option('add_full_period',1) ) { - # charge a full period in addition to the partial month - $months += $self->freq; - $$sdate = $self->add_freq($mstart); - } - - $param->{'months'} = $months; - $charge = sprintf('%.2f', $permonth * $months); - } - return $charge; -} - -1; diff --git a/FS/FS/part_pkg/prorate_delayed.pm b/FS/FS/part_pkg/prorate_delayed.pm deleted file mode 100644 index dd1b816..0000000 --- a/FS/FS/part_pkg/prorate_delayed.pm +++ /dev/null @@ -1,53 +0,0 @@ -package FS::part_pkg::prorate_delayed; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg; - -@ISA = qw(FS::part_pkg::prorate); - -%info = ( - 'name' => 'Free (or setup fee) for X days, then prorate, then flat-rate ' . - '(1st of month billing)', - 'shortname' => 'Prorate (Nth of month billing), with intro period', #?? - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'free_days' => { 'name' => 'Initial free days', - 'default' => 0, - }, - 'recur_notify' => { 'name' => 'Number of days before recurring billing'. - ' commences to notify customer. (0 means'. - ' no warning)', - 'default' => 0, - }, - }, - 'fieldorder' => [ 'free_days', 'recur_notify' ], - #'setup' => '\'my $d = $cust_pkg->bill || $time; $d += 86400 * \' + what.free_days.value + \'; $cust_pkg->bill($d); $cust_pkg_mod_flag=1; \' + what.setup_fee.value', - #'recur' => 'what.recur_fee.value', - 'weight' => 22, -); - -sub calc_setup { - my($self, $cust_pkg, $time ) = @_; - - my $d = $cust_pkg->bill || $time; - $d += 86400 * $self->option('free_days'); - $cust_pkg->bill($d); - - $self->option('setup_fee'); -} - -sub calc_remain { - my ($self, $cust_pkg, %options) = @_; - my $last_bill = $cust_pkg->last_bill || 0; - my $next_bill = $cust_pkg->getfield('bill') || 0; - my $free_days = $self->option('free_days'); - - return 0 if $last_bill + (86400 * $free_days) == $next_bill - && $last_bill == $cust_pkg->setup; - - return $self->SUPER::calc_remain($cust_pkg, %options); -} - -1; diff --git a/FS/FS/part_pkg/recur_Common.pm b/FS/FS/part_pkg/recur_Common.pm deleted file mode 100644 index 7614d7a..0000000 --- a/FS/FS/part_pkg/recur_Common.pm +++ /dev/null @@ -1,70 +0,0 @@ -package FS::part_pkg::recur_Common; - -use strict; -use vars qw( @ISA %info %recur_method ); -use Tie::IxHash; -use Time::Local; -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( 'disabled' => 1 ); #recur_Common not a usable price plan directly - -tie %recur_method, 'Tie::IxHash', - 'anniversary' => 'Charge the recurring fee at the frequency specified above', - 'prorate' => 'Charge a prorated fee the first time (selectable billing date)', - 'subscription' => 'Charge the full fee for the first partial period (selectable billing date)', -; - -sub base_recur { - my $self = shift; - $self->option('recur_fee', 1) || 0; -} - -sub calc_recur_Common { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; #only need $sdate & $param - - my $charges = 0; - - if ( $param->{'increment_next_bill'} ) { - - my $recur_method = $self->option('recur_method', 1) || 'anniversary'; - - $charges = $self->base_recur; - - if ( $recur_method eq 'prorate' ) { - my $cutoff_day = $self->option('cutoff_day') || 1; - $charges = $self->calc_prorate(@_, $cutoff_day); - } - elsif ( $recur_method eq 'anniversary' and - $self->option('sync_bill_date',1) ) { - my $next_bill = $cust_pkg->cust_main->next_bill_date; - if ( defined($next_bill) ) { - my $cutoff_day = (localtime($next_bill))[3]; - $charges = $self->calc_prorate(@_, $cutoff_day); - } - } - elsif ( $recur_method eq 'subscription' ) { - - my $cutoff_day = $self->option('cutoff_day', 1) || 1; - my ($day, $mon, $year) = ( localtime($$sdate) )[ 3..5 ]; - - if ( $day < $cutoff_day ) { - if ( $mon == 0 ) { $mon=11; $year--; } - else { $mon--; } - } - - $$sdate = timelocal(0, 0, 0, $cutoff_day, $mon, $year); - - }#$recur_method eq 'subscription' - - $charges -= $self->calc_discount( $cust_pkg, $sdate, $details, $param ); - - }#increment_next_bill - - return $charges; - -} - -1; diff --git a/FS/FS/part_pkg/rt_time.pm b/FS/FS/part_pkg/rt_time.pm deleted file mode 100644 index 03ed1cd..0000000 --- a/FS/FS/part_pkg/rt_time.pm +++ /dev/null @@ -1,73 +0,0 @@ -package FS::part_pkg::rt_time; - -use strict; -use FS::Conf; -use FS::Record qw(qsearchs qsearch); -use FS::part_pkg::recur_Common; -use Carp qw(cluck); - -our @ISA = qw(FS::part_pkg::recur_Common); - -our $DEBUG = 0; - -our %info = ( - 'name' => 'Bill from Time Worked on tickets in RT', - 'shortname' => 'Project Billing (RT)', - 'weight' => 55, - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'base_rate' => { 'name' => 'Rate (per minute)', - 'default' => 0, - }, - 'recur_fee' => {'disabled' => 1}, - }, - 'fieldorder' => [ 'base_rate' ], -); - -sub calc_setup { - my($self, $cust_pkg ) = @_; - $self->option('setup_fee'); -} - -sub calc_recur { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - my $charges = 0; - - $charges += $self->calc_usage(@_); - $charges += $self->calc_recur_Common(@_); - - $charges; - -} - -sub can_discount { 0; } - -sub calc_cancel { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - $self->calc_usage(@_); -} - -sub calc_usage { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - my $last_bill = $cust_pkg->get('last_bill') || $cust_pkg->get('setup'); - my @tickets = @{ FS::TicketSystem->comments_on_tickets( $cust_pkg->custnum, 100, $last_bill ) }; - - my $charges = 0; - - my $rate = $self->option('base_rate'); - - foreach my $ding ( @tickets) { - $charges += sprintf('%.2f', $ding->{'timetaken'} * $rate); - push @$details, join( ", ", ("($ding->{timetaken}) Minutes", substr($ding->{'content'},0,255))); - } - cluck $rate, $charges, @$details if $DEBUG > 0; - return $charges; -} - -1; diff --git a/FS/FS/part_pkg/sesmon_hour.pm b/FS/FS/part_pkg/sesmon_hour.pm deleted file mode 100644 index 97274d0..0000000 --- a/FS/FS/part_pkg/sesmon_hour.pm +++ /dev/null @@ -1,50 +0,0 @@ -package FS::part_pkg::sesmon_hour; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Base charge plus charge per-hour from the session monitor', - 'shortname' => 'Session monitor (per-hour)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'recur_included_hours' => { 'name' => 'Hours included', - 'default' => 0, - }, - 'recur_hourly_charge' => { 'name' => 'Additional charge per hour', - 'default' => 0, - }, - }, - 'fieldorder' => [ 'recur_included_hours', 'recur_hourly_charge' ], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $hours = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; \' + what.recur_fee.value + \' + \' + what.recur_hourly_charge.value + \' * $hours;\'', - 'weight' => 80, -); - -sub calc_recur { - my($self, $cust_pkg ) = @_; - - my $hours = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 3600; - $hours -= $self->option('recur_included_hours'); - $hours = 0 if $hours < 0; - - $self->option('recur_fee') + $hours * $self->option('recur_hourly_charge'); - -} - -sub can_discount { 0; } - -sub is_free_options { - qw( setup_fee recur_fee recur_hourly_charge ); -} - -sub base_recur { - my($self, $cust_pkg) = @_; - $self->option('recur_fee'); -} - -1; diff --git a/FS/FS/part_pkg/sesmon_minute.pm b/FS/FS/part_pkg/sesmon_minute.pm deleted file mode 100644 index 9c8dfd1..0000000 --- a/FS/FS/part_pkg/sesmon_minute.pm +++ /dev/null @@ -1,49 +0,0 @@ -package FS::part_pkg::sesmon_minute; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Base charge plus charge per-minute from the session monitor', - 'shortname' => 'Session monitor (per-minute)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'recur_included_min' => { 'name' => 'Minutes included', - 'default' => 0, - }, - 'recur_minly_charge' => { 'name' => 'Additional charge per minute', - 'default' => 0, - }, - }, - 'fieldorder' => [ 'recur_included_min', 'recur_minly_charge' ], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $min = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 60 - \' + what.recur_included_min.value + \'; $min = 0 if $min < 0; \' + what.recur_fee.value + \' + \' + what.recur_minly_charge.value + \' * $min;\'', - 'weight' => 80, -); - - -sub calc_recur { - my( $self, $cust_pkg ) = @); - my $min = $cust_pkg->seconds_since($cust_pkg->bill || 0) / 60; - $min -= $self->option('recur_included_min'); - $min = 0 if $min < 0; - - $self->option('recur_fee') + $min * $self->option('recur_minly_charge'); -} - -sub can_discount { 0; } - -sub is_free_options { - qw( setup_fee recur_fee recur_minly_charge ); -} - -sub base_recur { - my($self, $cust_pkg) = @_; - $self->option('recur_fee'); -} - -1; diff --git a/FS/FS/part_pkg/sql_external.pm b/FS/FS/part_pkg/sql_external.pm deleted file mode 100644 index 8d43086..0000000 --- a/FS/FS/part_pkg/sql_external.pm +++ /dev/null @@ -1,77 +0,0 @@ -package FS::part_pkg::sql_external; - -use strict; -use base qw( FS::part_pkg::recur_Common ); -use vars qw( %info ); -use DBI; -#use FS::Record qw(qsearch qsearchs); - -%info = ( - 'name' => 'Base charge plus additional fees for external services from a configurable SQL query', - 'shortname' => 'External SQL query', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '. - 'subscription', - 'default' => '1', - }, - 'add_full_period'=> { 'name' => 'When prorating first month, also bill '. - 'for one full period after that', - 'type' => 'checkbox', - }, - - 'recur_method' => { 'name' => 'Recurring fee method', - #'type' => 'radio', - #'options' => \%recur_method, - 'type' => 'select', - 'select_options' => \%FS::part_pkg::recur_Common::recur_method, - }, - 'datasrc' => { 'name' => 'DBI data source', - 'default' => '', - }, - 'db_username' => { 'name' => 'Database username', - 'default' => '', - }, - 'db_password' => { 'name' => 'Database password', - 'default' => '', - }, - 'query' => { 'name' => 'SQL query', - 'default' => '', - }, - }, - 'fieldorder' => [qw( recur_method cutoff_day - add_full_period datasrc db_username db_password query - )], - 'weight' => '58', -); - -sub calc_recur { - my $self = shift; - my($cust_pkg) = @_; #, $sdate, $details, $param ) = @_; - - my $price = $self->calc_recur_Common(@_); - - my $dbh = DBI->connect( map { $self->option($_) } - qw( datasrc db_username db_password ) - ) - or die $DBI::errstr; - - my $sth = $dbh->prepare( $self->option('query') ) - or die $dbh->errstr; - - foreach my $cust_svc ( - grep { $_->part_svc->svcdb eq "svc_external" } $cust_pkg->cust_svc - ) { - my $id = $cust_svc->svc_x->id; - $sth->execute($id) or die $sth->errstr; - $price += $sth->fetchrow_arrayref->[0]; - } - - $price; -} - -sub can_discount { 0; } - -sub is_free { 0; } - -1; diff --git a/FS/FS/part_pkg/sql_generic.pm b/FS/FS/part_pkg/sql_generic.pm deleted file mode 100644 index cf38257..0000000 --- a/FS/FS/part_pkg/sql_generic.pm +++ /dev/null @@ -1,81 +0,0 @@ -package FS::part_pkg::sql_generic; - -use strict; -use vars qw(@ISA %info); -use DBI; -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Base charge plus a per-domain metered rate from a configurable SQL query', - 'shortname' => 'Bulk (per-domain from SQL query)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'recur_included' => { 'name' => 'Units included', - 'default' => 0, - }, - 'recur_unit_charge' => { 'name' => 'Additional charge per unit', - 'default' => 0, - }, - 'datasrc' => { 'name' => 'DBI data source', - 'default' => '', - }, - 'db_username' => { 'name' => 'Database username', - 'default' => '', - }, - 'db_password' => { 'name' => 'Database username', - 'default' => '', - }, - 'query' => { 'name' => 'SQL query', - 'default' => '', - }, - }, - 'fieldorder' => [qw( recur_included recur_unit_charge datasrc db_username db_password query )], - # 'setup' => 'what.setup_fee.value', - # 'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\") or die $DBI::errstr; \'', - #'recur' => '\'my $dbh = DBI->connect(\"\' + what.datasrc.value + \'\", \"\' + what.db_username.value + \'\", \"\' + what.db_password.value + \'\" ) or die $DBI::errstr; my $sth = $dbh->prepare(\"\' + what.query.value + \'\") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq \"svc_domain\" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_fee.value + \' + $units * \' + what.recur_unit_charge.value + \';\'', - #'recur' => '\'my $dbh = DBI->connect("\' + what.datasrc.value + \'", "\' + what.db_username.value + \'", "\' what.db_password.value + \'" ) or die $DBI::errstr; my $sth = $dbh->prepare("\' + what.query.value + \'") or die $dbh->errstr; my $units = 0; foreach my $cust_svc ( grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc ) { my $domain = $cust_svc->svc_x->domain; $sth->execute($domain) or die $sth->errstr; $units += $sth->fetchrow_arrayref->[0]; } $units -= \' + what.recur_included.value + \'; $units = 0 if $units < 0; \' + what.recur_fee.value + \' + $units * \' + what.recur_unit_charge + \';\'', - 'weight' => '56', -); - -sub calc_recur { - my($self, $cust_pkg ) = @_; - - my $dbh = DBI->connect( map { $self->option($_) } - qw( datasrc db_username db_password ) - ) - or die $DBI::errstr; - - my $sth = $dbh->prepare( $self->option('query') ) - or die $dbh->errstr; - - my $units = 0; - foreach my $cust_svc ( - grep { $_->part_svc->svcdb eq "svc_domain" } $cust_pkg->cust_svc - ) { - my $domain = $cust_svc->svc_x->domain; - $sth->execute($domain) or die $sth->errstr; - - $units += $sth->fetchrow_arrayref->[0]; - } - - $units -= $self->option('recur_included'); - $units = 0 if $units < 0; - - $self->option('recur_fee') + $units * $self->option('recur_unit_charge'); -} - -sub can_discount { 0; } - -sub is_free_options { - qw( setup_fee recur_fee recur_unit_charge ); -} - -sub base_recur { - my($self, $cust_pkg) = @_; - $self->option('recur_fee'); -} - -1; diff --git a/FS/FS/part_pkg/sqlradacct_hour.pm b/FS/FS/part_pkg/sqlradacct_hour.pm deleted file mode 100644 index 3cc46ac..0000000 --- a/FS/FS/part_pkg/sqlradacct_hour.pm +++ /dev/null @@ -1,163 +0,0 @@ -package FS::part_pkg::sqlradacct_hour; - -use strict; -use vars qw(@ISA %info); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'Base charge plus per-hour (and for data) from an SQL RADIUS radacct table', - 'shortname' => 'Usage charges from RADIUS', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'recur_included_hours' => { 'name' => 'Hours included', - 'default' => 0, - }, - 'recur_hourly_charge' => { 'name' => 'Additional charge per hour', - 'default' => 0, - }, - 'recur_hourly_cap' => { 'name' => 'Maximum overage charge for hours'. - ' (0 means no cap)', - - 'default' => 0, - }, - - 'recur_included_input' => { 'name' => 'Upload megabytes included', - 'default' => 0, - }, - 'recur_input_charge' => { 'name' => - 'Additional charge per megabyte upload', - 'default' => 0, - }, - 'recur_input_cap' => { 'name' => 'Maximum overage charge for upload'. - ' (0 means no cap)', - 'default' => 0, - }, - - 'recur_included_output' => { 'name' => 'Download megabytes included', - 'default' => 0, - }, - 'recur_output_charge' => { 'name' => - 'Additional charge per megabyte download', - 'default' => 0, - }, - 'recur_output_cap' => { 'name' => 'Maximum overage charge for download'. - ' (0 means no cap)', - 'default' => 0, - }, - - 'recur_included_total' => { 'name' => - 'Total megabytes included', - 'default' => 0, - }, - 'recur_total_charge' => { 'name' => - 'Additional charge per megabyte total', - 'default' => 0, - }, - 'recur_total_cap' => { 'name' => 'Maximum overage charge for total'. - ' megabytes (0 means no cap)', - 'default' => 0, - }, - - 'global_cap' => { 'name' => 'Global cap on all overage charges'. - ' (0 means no cap)', - 'default' => 0, - }, - - }, - 'fieldorder' => [qw( recur_included_hours recur_hourly_charge recur_hourly_cap recur_included_input recur_input_charge recur_input_cap recur_included_output recur_output_charge recur_output_cap recur_included_total recur_total_charge recur_total_cap global_cap )], - #'setup' => 'what.setup_fee.value', - #'recur' => '\'my $last_bill = $cust_pkg->last_bill; my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $sdate ) / 3600 - \' + what.recur_included_hours.value + \'; $hours = 0 if $hours < 0; my $input = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctInputOctets\" ) / 1048576; my $output = $cust_pkg->attribute_since_sqlradacct($last_bill, $sdate, \"AcctOutputOctets\" ) / 1048576; my $total = $input + $output - \' + what.recur_included_total.value + \'; $total = 0 if $total < 0; my $input = $input - \' + what.recur_included_input.value + \'; $input = 0 if $input < 0; my $output = $output - \' + what.recur_included_output.value + \'; $output = 0 if $output < 0; my $totalcharge = sprintf(\"%.2f\", \' + what.recur_total_charge.value + \' * $total); my $inputcharge = sprintf(\"%.2f\", \' + what.recur_input_charge.value + \' * $input); my $outputcharge = sprintf(\"%.2f\", \' + what.recur_output_charge.value + \' * $output); my $hourscharge = sprintf(\"%.2f\", \' + what.recur_hourly_charge.value + \' * $hours); if ( \' + what.recur_total_charge.value + \' > 0 ) { push @details, \"Last month\\\'s data \". sprintf(\"%.1f\", $total). \" megs: \\\$$totalcharge\" } if ( \' + what.recur_input_charge.value + \' > 0 ) { push @details, \"Last month\\\'s download \". sprintf(\"%.1f\", $input). \" megs: \\\$$inputcharge\" } if ( \' + what.recur_output_charge.value + \' > 0 ) { push @details, \"Last month\\\'s upload \". sprintf(\"%.1f\", $output). \" megs: \\\$$outputcharge\" } if ( \' + what.recur_hourly_charge.value + \' > 0 ) { push @details, \"Last month\\\'s time \". sprintf(\"%.1f\", $hours). \" hours: \\\$$hourscharge\"; } \' + what.recur_fee.value + \' + $hourscharge + $inputcharge + $outputcharge + $totalcharge ;\'', - 'weight' => 40, -); - -sub calc_recur { - my($self, $cust_pkg, $sdate, $details ) = @_; - - my $last_bill = $cust_pkg->last_bill; - my $hours = $cust_pkg->seconds_since_sqlradacct($last_bill, $$sdate ) / 3600; - $hours -= $self->option('recur_included_hours'); - $hours = 0 if $hours < 0; - - my $input = $cust_pkg->attribute_since_sqlradacct( $last_bill, - $$sdate, - 'AcctInputOctets' ) - / 1048576; - - my $output = $cust_pkg->attribute_since_sqlradacct( $last_bill, - $$sdate, - 'AcctOutputOctets' ) - / 1048576; - - my $total = $input + $output - $self->option('recur_included_total'); - $total = 0 if $total < 0; - $input = $input - $self->option('recur_included_input'); - $input = 0 if $input < 0; - $output = $output - $self->option('recur_included_output'); - $output = 0 if $output < 0; - - my $totalcharge = - $total * sprintf('%.2f', $self->option('recur_total_charge')); - $totalcharge = $self->option('recur_total_cap') - if $self->option('recur_total_cap') - && $totalcharge > $self->option('recur_total_cap'); - - my $inputcharge = - $input * sprintf('%.2f', $self->option('recur_input_charge')); - $inputcharge = $self->option('recur_input_cap') - if $self->option('recur_input_cap') - && $inputcharge > $self->option('recur_input_cap'); - - my $outputcharge = - $output * sprintf('%.2f', $self->option('recur_output_charge')); - $outputcharge = $self->option('recur_output_cap') - if $self->option('recur_output_cap') - && $outputcharge > $self->option('recur_output_cap'); - - my $hourscharge = - $hours * sprintf('%.2f', $self->option('recur_hourly_charge')); - $hourscharge = $self->option('recur_hourly_cap') - if $self->option('recur_hourly_cap') - && $hourscharge > $self->option('recur_hourly_cap'); - - if ( $self->option('recur_total_charge') > 0 ) { - push @$details, "Last month's data ". - sprintf('%.1f', $total). " megs: $totalcharge"; - } - if ( $self->option('recur_input_charge') > 0 ) { - push @$details, "Last month's download ". - sprintf('%.1f', $input). " megs: $inputcharge"; - } - if ( $self->option('recur_output_charge') > 0 ) { - push @$details, "Last month's upload ". - sprintf('%.1f', $output). " megs: $outputcharge"; - } - if ( $self->option('recur_hourly_charge') > 0 ) { - push @$details, "Last month\'s time ". - sprintf('%.1f', $hours). " hours: $hourscharge"; - } - - my $charges = $hourscharge + $inputcharge + $outputcharge + $totalcharge; - if ( $self->option('global_cap') && $charges > $self->option('global_cap') ) { - $charges = $self->option('global_cap'); - push @$details, "Usage charges capped at: $charges"; - } - - $self->option('recur_fee') + $charges; -} - -sub can_discount { 0; } - -sub is_free_options { - qw( setup_fee recur_fee recur_hourly_charge - recur_input_charge recur_output_charge recur_total_charge ); -} - -sub base_recur { - my($self, $cust_pkg) = @_; - $self->option('recur_fee'); -} - -1; diff --git a/FS/FS/part_pkg/subscription.pm b/FS/FS/part_pkg/subscription.pm deleted file mode 100644 index 3c5f96b..0000000 --- a/FS/FS/part_pkg/subscription.pm +++ /dev/null @@ -1,108 +0,0 @@ -package FS::part_pkg::subscription; - -use strict; -use vars qw(@ISA %info); -use Time::Local qw(timelocal); -#use FS::Record qw(qsearch qsearchs); -use FS::part_pkg::flat; - -@ISA = qw(FS::part_pkg::flat); - -%info = ( - 'name' => 'First partial month full charge, then flat-rate (selectable billing day)', - 'shortname' => 'Subscription (Nth of month, full charge for first)', - 'inherit_fields' => [ 'usage_Mixin', 'global_Mixin' ], - 'fields' => { - 'cutoff_day' => { 'name' => 'Billing day', - 'default' => 1, - }, - 'seconds' => { 'name' => 'Time limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - }, - 'upbytes' => { 'name' => 'Upload limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'downbytes' => { 'name' => 'Download limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'totalbytes' => { 'name' => 'Transfer limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'recharge_amount' => { 'name' => 'Cost of recharge for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ }, - }, - 'recharge_seconds' => { 'name' => 'Recharge time for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - }, - 'recharge_upbytes' => { 'name' => 'Recharge upload for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'recharge_downbytes' => { 'name' => 'Recharge download for this package', 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '. - 'over into current period', - 'type' => 'checkbox', - }, - 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '. - 'package recharge', - 'type' => 'checkbox', - }, - - #it would be better if this had to be turned on, its confusing - 'externalid' => { 'name' => 'Optional External ID', - 'default' => '', - }, - }, - 'fieldorder' => [ 'cutoff_day', 'seconds', - 'upbytes', 'downbytes', 'totalbytes', - 'recharge_amount', 'recharge_seconds', 'recharge_upbytes', - 'recharge_downbytes', 'recharge_totalbytes', - 'usage_rollover', 'recharge_reset', 'externalid' ], - 'freq' => 'm', - 'weight' => 30, -); - -sub calc_recur { - my($self, $cust_pkg, $sdate, $details, $param ) = @_; - my $cutoff_day = $self->option('cutoff_day', 1) || 1; - my $mnow = $$sdate; - my ($sec,$min,$hour,$mday,$mon,$year) = (localtime($mnow) )[0,1,2,3,4,5]; - - if ( $mday < $cutoff_day ) { - if ($mon==0) {$mon=11;$year--;} - else {$mon--;} - } - - $$sdate = timelocal(0,0,0,$cutoff_day,$mon,$year); - - my $br = $self->base_recur($cust_pkg); - - my $discount = $self->calc_discount($cust_pkg, $sdate, $details, $param); - - sprintf('%.2f', $br - $discount); -} - -1; diff --git a/FS/FS/part_pkg/usage_Mixin.pm b/FS/FS/part_pkg/usage_Mixin.pm deleted file mode 100644 index 028fce7..0000000 --- a/FS/FS/part_pkg/usage_Mixin.pm +++ /dev/null @@ -1,77 +0,0 @@ -package FS::part_pkg::usage_Mixin; - -use strict; -use vars qw( @ISA %info ); -use FS::part_pkg; -use FS::UI::bytecount; -@ISA = qw(FS::part_pkg); - -# Field definitions for time and data usage, other than CDRs. - -%info = ( - 'disabled' => 1, - 'fields' => { - 'seconds' => { 'name' => 'Time limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - }, - 'upbytes' => { 'name' => 'Upload limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'downbytes' => { 'name' => 'Download limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'totalbytes' => { 'name' => 'Transfer limit for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'recharge_amount' => { 'name' => 'Cost of recharge for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*(\.\d{2})?$/ }, - }, - 'recharge_seconds' => { 'name' => 'Recharge time for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - }, - 'recharge_upbytes' => { 'name' => 'Recharge upload for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'recharge_downbytes' => { 'name' => 'Recharge download for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'recharge_totalbytes' => { 'name' => 'Recharge transfer for this package', - 'default' => '', - 'check' => sub { shift =~ /^\d*$/ }, - 'format' => \&FS::UI::bytecount::display_bytecount, - 'parse' => \&FS::UI::bytecount::parse_bytecount, - }, - 'usage_rollover' => { 'name' => 'Allow usage from previous period to roll '. - ' over into current period', - 'type' => 'checkbox', - }, - 'recharge_reset' => { 'name' => 'Reset usage to these values on manual '. - 'package recharge', - 'type' => 'checkbox', - }, - }, - 'fieldorder' => [ qw( seconds upbytes downbytes totalbytes - recharge_amount recharge_seconds recharge_upbytes - recharge_downbytes recharge_totalbytes - usage_rollover recharge_reset ) ], -); - -1; diff --git a/FS/FS/part_pkg/voip_cdr.pm b/FS/FS/part_pkg/voip_cdr.pm deleted file mode 100644 index 5dbd115..0000000 --- a/FS/FS/part_pkg/voip_cdr.pm +++ /dev/null @@ -1,925 +0,0 @@ -package FS::part_pkg::voip_cdr; - -use strict; -use vars qw(@ISA $DEBUG %info); -use Date::Format; -use Tie::IxHash; -use FS::Conf; -use FS::Record qw(qsearchs qsearch); -use FS::part_pkg::recur_Common; -use FS::cdr; -use FS::rate; -use FS::rate_prefix; -use FS::rate_detail; -use FS::part_pkg::recur_Common; - -use List::Util qw(first min); - -@ISA = qw(FS::part_pkg::recur_Common); - -$DEBUG = 0; - -tie my %cdr_svc_method, 'Tie::IxHash', - 'svc_phone.phonenum' => 'Phone numbers (svc_phone.phonenum)', - 'svc_pbx.title' => 'PBX name (svc_pbx.title)', - 'svc_pbx.svcnum' => 'Freeside service # (svc_pbx.svcnum)', -; - -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.', -; - -#tie my %cdr_location, 'Tie::IxHash', -# 'internal' => 'Internal: CDR records imported into the internal CDR table', -# 'external' => 'External: CDR records queried directly from an external '. -# 'Asterisk (or other?) CDR table', -#; - -tie my %temporalities, 'Tie::IxHash', - 'upcoming' => "Upcoming (future)", - 'preceding' => "Preceding (past)", -; - -tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); - -%info = ( - 'name' => 'VoIP rating by plan of CDR records in an internal (or external) SQL table', - 'shortname' => 'VoIP/telco CDR rating (standard)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - #false laziness w/flat.pm - 'recur_temporality' => { 'name' => 'Charge recurring fee for period', - 'type' => 'select', - 'select_options' => \%temporalities, - }, - - 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '. - 'subscription', - 'default' => '1', - }, - 'add_full_period'=> { 'name' => 'When prorating first month, also bill '. - 'for one full period after that', - 'type' => 'checkbox', - }, - 'recur_method' => { 'name' => 'Recurring fee method', - #'type' => 'radio', - #'options' => \%recur_method, - 'type' => 'select', - 'select_options' => \%FS::part_pkg::recur_Common::recur_method, - }, - - 'cdr_svc_method' => { 'name' => 'CDR service matching method', - 'type' => 'radio', - 'options' => \%cdr_svc_method, - }, - - 'rating_method' => { 'name' => 'Rating method', - 'type' => 'radio', - 'options' => \%rating_method, - }, - - 'ratenum' => { 'name' => 'Rate plan', - 'type' => 'select', - 'select_table' => 'rate', - 'select_key' => 'ratenum', - 'select_label' => 'ratename', - }, - - 'min_included' => { 'name' => 'Minutes included when using "single price per minute" rating method', - }, - - - 'min_charge' => { 'name' => 'Charge per minute when using "single price per minute" rating method', - }, - - 'sec_granularity' => { 'name' => 'Granularity when using "single price per minute" rating method', - 'type' => 'select', - 'select_options' => \%granularity, - }, - - 'ignore_unrateable' => { 'name' => 'Ignore calls without a rate in the rate tables. By default, the system will throw a fatal error upon encountering unrateable calls.', - 'type' => 'checkbox', - }, - - 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records', - 'default' => '+1', - }, - - 'disable_src' => { 'name' => 'Disable rating of CDR records based on the "src" field in addition to "charged_party"', - 'type' => 'checkbox' - }, - - 'domestic_prefix' => { 'name' => 'Destination prefix for domestic CDR records', - 'default' => '1', - }, - -# 'domestic_prefix_required' => { 'name' => 'Require explicit destination prefix for domestic CDR records', -# 'type' => 'checkbox', -# }, - - 'international_prefix' => { 'name' => 'Destination prefix for international CDR records', - 'default' => '011', - }, - - 'disable_tollfree' => { 'name' => 'Disable automatic toll-free processing', - 'type' => 'checkbox', - }, - - 'use_amaflags' => { 'name' => 'Do not charge for CDRs where the amaflags field is not set to "2" ("BILL"/"BILLING").', - 'type' => 'checkbox', - }, - - 'use_disposition' => { 'name' => 'Do not charge for CDRs where the disposition flag is not set to "ANSWERED".', - 'type' => 'checkbox', - }, - - 'use_disposition_taqua' => { 'name' => 'Do not charge for CDRs where the disposition is not set to "100" (Taqua).', - 'type' => 'checkbox', - }, - - 'use_carrierid' => { 'name' => 'Do not charge for CDRs where the Carrier ID is not set to: ', - }, - - 'use_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is not set to: ', - }, - - 'skip_dst_prefix' => { 'name' => 'Do not charge for CDRs where the destination number starts with any of these values: ', - }, - - 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values: ', - }, - - 'skip_dstchannel_prefix' => { 'name' => 'Do not charge for CDRs where the dstchannel starts with:', - }, - - 'skip_src_length_more' => { 'name' => 'Do not charge for CDRs where the source is more than this many digits:', - }, - - 'noskip_src_length_accountcode_tollfree' => { 'name' => 'Do charge for CDRs where source is equal or greater than the specified digits, when accountcode is toll free', - 'type' => 'checkbox', - }, - - 'accountcode_tollfree_ratenum' => { - 'name' => 'Optional alternate rate plan when accountcode is toll free: ', - 'type' => 'select', - 'select_table' => 'rate', - 'select_key' => 'ratenum', - 'select_label' => 'ratename', - 'disable_empty' => 0, - 'empty_label' => '', - }, - - 'skip_dst_length_less' => { 'name' => 'Do not charge for CDRs where the destination is less than this many digits:', - }, - - 'noskip_dst_length_accountcode_tollfree' => { 'name' => 'Do charge for CDRs where dst is less than the specified digits, when accountcode is toll free', - 'type' => 'checkbox', - }, - - 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value: ', - }, - - 'skip_max_callers' => { 'name' => 'Do not charge for CDRs where max_callers is less than or equal to this value: ', - }, - - 'use_duration' => { 'name' => 'Calculate usage based on the duration field instead of the billsec field', - 'type' => 'checkbox', - }, - - '411_rewrite' => { 'name' => 'Rewrite these (comma-separated) destination numbers to 411 for rating purposes (also ignore any carrierid check): ', - }, - - #false laziness w/cdr_termination.pm - 'output_format' => { 'name' => 'CDR invoice display format', - 'type' => 'select', - 'select_options' => { FS::cdr::invoice_formats() }, - 'default' => 'default', #XXX test - }, - - 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not): ', - }, - - 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section', - 'type' => 'checkbox', - }, - - 'usage_mandate' => { 'name' => 'Always put usage details in separate section', - 'type' => 'checkbox', - }, - #eofalse - - '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', - }, - - #XXX also have option for an external db -# 'cdr_location' => { 'name' => 'CDR database location' -# 'type' => 'select', -# 'select_options' => \%cdr_location, -# 'select_callback' => { -# 'external' => { -# 'enable' => [ 'datasrc', 'username', 'password' ], -# }, -# 'internal' => { -# 'disable' => [ 'datasrc', 'username', 'password' ], -# } -# }, -# }, -# 'datasrc' => { 'name' => 'DBI data source for external CDR table', -# 'disabled' => 'Y', -# }, -# 'username' => { 'name' => 'External database username', -# 'disabled' => 'Y', -# }, -# 'password' => { 'name' => 'External database password', -# 'disabled' => 'Y', -# }, - - }, - 'fieldorder' => [qw( - recur_temporality - recur_method cutoff_day - add_full_period - cdr_svc_method - rating_method ratenum min_charge sec_granularity - ignore_unrateable - default_prefix - disable_src - domestic_prefix international_prefix - disable_tollfree - use_amaflags use_disposition - use_disposition_taqua use_carrierid use_cdrtypenum - skip_dcontext skip_dst_prefix - skip_dstchannel_prefix skip_src_length_more - noskip_src_length_accountcode_tollfree - accountcode_tollfree_ratenum - skip_dst_length_less - noskip_dst_length_accountcode_tollfree - skip_lastapp - skip_max_callers - use_duration - 411_rewrite - output_format usage_mandate summarize_usage usage_section - bill_every_call bill_inactive_svcs - count_available_phones - ) - ], - 'weight' => 40, -); - -sub calc_setup { - my($self, $cust_pkg ) = @_; - $self->option('setup_fee'); -} - -sub calc_recur { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - my $charges = 0; - - $charges += $self->calc_usage(@_); - $charges += $self->calc_recur_Common(@_); - - $charges; - -} - -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 - -sub calc_usage { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - #my $last_bill = $cust_pkg->last_bill; - my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup - - return 0 - if $self->option('recur_temporality', 1) eq 'preceding' - && ( $last_bill eq '' || $last_bill == 0 ); - - my $ratenum = $cust_pkg->part_pkg->option('ratenum'); - - my $spool_cdr = $cust_pkg->cust_main->spool_cdr; - - my %included_min = (); - - my $charges = 0; - -# my $downstream_cdr = ''; - - my $cdr_svc_method = $self->option('cdr_svc_method',1)||'svc_phone.phonenum'; - my $rating_method = $self->option('rating_method') || 'prefix'; - my $intl = $self->option('international_prefix') || '011'; - my $domestic_prefix = $self->option('domestic_prefix'); - my $disable_tollfree = $self->option('disable_tollfree'); - my $ignore_unrateable = $self->option('ignore_unrateable', 'Hush!'); - my $use_duration = $self->option('use_duration'); - - my $output_format = $self->option('output_format', 'Hush!') - || ( $rating_method eq 'upstream_simple' - ? 'simple' - : 'default' - ); - - my @dirass = (); - if ( $self->option('411_rewrite') ) { - my $dirass = $self->option('411_rewrite'); - $dirass =~ s/\s//g; - @dirass = split(',', $dirass); - } - - my %interval_cache = (); # for timed rates - - #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); - - my @cust_svc; - if( $self->option('bill_inactive_svcs',1) ) { - #XXX in this mode do we need to restrict the set of CDRs by date also? - @cust_svc = $cust_pkg->h_cust_svc($$sdate, $last_bill); - } - else { - @cust_svc = $cust_pkg->cust_svc; - } - @cust_svc = grep { $_->part_svc->svcdb eq $svc_table } @cust_svc; - - foreach my $cust_svc (@cust_svc) { - - my $svc_x; - if( $self->option('bill_inactive_svcs',1) ) { - $svc_x = $cust_svc->h_svc_x($$sdate, $last_bill); - } - else { - $svc_x = $cust_svc->svc_x; - } - my %options = ( - 'disable_src' => $self->option('disable_src'), - 'default_prefix' => $self->option('default_prefix'), - 'status' => '', - 'for_update' => 1, - ); # $last_bill, $$sdate ) - $options{'by_svcnum'} = 1 if $svc_field eq 'svcnum'; - - foreach my $cdr ( - $svc_x->get_cdrs( %options ) - ) { - if ( $DEBUG > 1 ) { - warn "rating CDR $cdr\n". - join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr ); - } - - my $rate_detail; - my( $rate_region, $regionnum ); - my $rate; - my $pretty_destnum; - my $charge = ''; - my $seconds = ''; - my $weektime = ''; - my $regionname = ''; - my $classnum = ''; - my $countrycode; - my $number; - - my @call_details = (); - if ( $rating_method eq 'prefix' ) { - - my $da_rewrote = 0; - if ( length($cdr->dst) && grep { $cdr->dst eq $_ } @dirass ){ - $cdr->dst('411'); - $da_rewrote = 1; - } - - my $reason = $self->check_chargable( $cdr, - 'da_rewrote' => $da_rewrote, - 'option_cache' => \%opt_cache, - ); - - if ( $reason ) { - - warn "not charging for CDR ($reason)\n" if $DEBUG; - $charge = 0; - - } else { - - ### - # look up rate details based on called station id - # (or calling station id for toll free calls) - ### - - my( $to_or_from ); - if ( $cdr->is_tollfree && ! $disable_tollfree ) - { #tollfree call - $to_or_from = 'from'; - $number = $cdr->src; - } else { #regular call - $to_or_from = 'to'; - $number = $cdr->dst; - } - - warn "parsing call $to_or_from $number\n" if $DEBUG; - - #remove non-phone# stuff and whitespace - $number =~ s/\s//g; -# my $proto = ''; -# $dest =~ s/^(\w+):// and $proto = $1; #sip: -# my $siphost = ''; -# $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com - - #determine the country code - $countrycode = ''; - if ( $number =~ /^$intl(((\d)(\d))(\d))(\d+)$/ - || $number =~ /^\+(((\d)(\d))(\d))(\d+)$/ - ) - { - - my( $three, $two, $one, $u1, $u2, $rest ) = ( $1,$2,$3,$4,$5,$6 ); - #first look for 1 digit country code - if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { - $countrycode = $one; - $number = $u1.$u2.$rest; - } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 - $countrycode = $two; - $number = $u2.$rest; - } else { #3 digit country code - $countrycode = $three; - $number = $rest; - } - - } else { - $countrycode = $domestic_prefix || '1'; - $number =~ s/^$countrycode//;# if length($number) > 10; - } - - warn "rating call $to_or_from +$countrycode $number\n" if $DEBUG; - $pretty_destnum = "+$countrycode $number"; - #asterisks here causes inserting the detail to barf, so: - $pretty_destnum =~ s/\*//g; - - my $eff_ratenum = $cdr->is_tollfree('accountcode') - ? $cust_pkg->part_pkg->option('accountcode_tollfree_ratenum') - : ''; - $eff_ratenum ||= $ratenum; - $rate = qsearchs('rate', { 'ratenum' => $eff_ratenum }) - or die "ratenum $eff_ratenum not found!"; - - my @ltime = localtime($cdr->startdate); - $weektime = $ltime[0] + - $ltime[1]*60 + #minutes - $ltime[2]*3600 + #hours - $ltime[6]*86400; #days since sunday - # if there's no timed rate_detail for this time/region combination, - # dest_detail returns the default. There may still be a timed rate - # that applies after the starttime of the call, so be careful... - $rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode, - 'phonenum' => $number, - 'weektime' => $weektime, - }); - - if ( $rate_detail ) { - - $rate_region = $rate_detail->dest_region; - $regionnum = $rate_region->regionnum; - $regionname = $rate_region->regionname; - warn " found rate for regionnum $regionnum ". - "and rate detail $rate_detail\n" - if $DEBUG; - - if ( !exists($interval_cache{$regionnum}) ) { - my @intervals = ( - sort { $a->stime <=> $b->stime } - map { my $r = $_->rate_time; $r ? $r->intervals : () } - $rate->rate_detail - ); - $interval_cache{$regionnum} = \@intervals; - warn " cached ".scalar(@intervals)." interval(s)\n" - if $DEBUG; - } - - } elsif ( $ignore_unrateable ) { - - $rate_region = ''; - $regionnum = ''; - #code below will throw a warning & skip - - } else { - - die "FATAL: no rate_detail found in ". - $rate->ratenum. ":". $rate->ratename. " rate plan ". - "for +$countrycode $number (CDR acctid ". $cdr->acctid. "); ". - "add a rate or set ignore_unrateable flag on the package def\n"; - } - - } - - } elsif ( $rating_method eq 'upstream_simple' ) { - - #XXX $charge = sprintf('%.2f', $cdr->upstream_price); - $charge = sprintf('%.3f', $cdr->upstream_price); - $charges += $charge; - warn "Incrementing \$charges by $charge. Now $charges\n" if $DEBUG; - - @call_details = ($cdr->downstream_csv( 'format' => $output_format, - 'charge' => $charge, - ) - ); - $classnum = $cdr->calltypenum; - - } elsif ( $rating_method eq 'single_price' ) { - - # a little false laziness w/below - # $rate_detail = new FS::rate_detail({sec_granularity => ... }) ? - - my $granularity = length($self->option('sec_granularity')) - ? $self->option('sec_granularity') - : 60; - - $seconds = $use_duration ? $cdr->duration : $cdr->billsec; - - $seconds += $granularity - ( $seconds % $granularity ) - if $seconds # don't granular-ize 0 billsec calls (bills them) - && $granularity # 0 is per call - && $seconds % $granularity; - my $minutes = $seconds / 60; - # XXX config? - #$charge = sprintf('%.2f', ( $self->option('min_charge') * $minutes ) - #+ 0.00000001 ); #so 1.005 rounds to 1.01 - $charge = sprintf('%.4f', ( $self->option('min_charge') * $minutes ) - + 0.0000000001 ); #so 1.00005 rounds to 1.0001 - - warn "Incrementing \$charges by $charge. Now $charges\n" if $DEBUG; - $charges += $charge; - - @call_details = ($cdr->downstream_csv( 'format' => $output_format, - 'charge' => $charge, - 'seconds' => ($use_duration ? - $cdr->duration : - $cdr->billsec), - 'granularity' => $granularity, - ) - ); - - } else { - die "don't know how to rate CDRs using method: $rating_method\n"; - } - - ### - # find the price and add detail to the invoice - ### - - # if $rate_detail is not found, skip this CDR... i.e. - # don't add it to invoice, don't set its status to done, - # don't call downstream_csv or something on it... - # but DO emit a warning... - #if ( ! $rate_detail && ! scalar(@call_details) ) {} - if ( ! $rate_detail && $charge eq '' ) { - - warn "no rate_detail found for CDR.acctid: ". $cdr->acctid. - "; skipping\n" - - } else { # there *is* a rate_detail (or call_details), proceed... - # About this section: - # We don't round _anything_ (except granularizing) - # until the final $charge = sprintf("%.2f"...). - - unless ( @call_details || ( $charge ne '' && $charge == 0 ) ) { - - my $seconds_left = $use_duration ? $cdr->duration : $cdr->billsec; - # charge for the first (conn_sec) seconds - $seconds = min($seconds_left, $rate_detail->conn_sec); - $seconds_left -= $seconds; - $weektime += $seconds; - $charge = $rate_detail->conn_charge; - - my $etime; - while($seconds_left) { - my $ratetimenum = $rate_detail->ratetimenum; # may be empty - - # find the end of the current rate interval - if(@{ $interval_cache{$regionnum} } == 0) { - # There are no timed rates in this group, so just stay - # in the default rate_detail for the entire duration. - # Set an "end" of 1 past the end of the current call. - $etime = $weektime + $seconds_left + 1; - } - elsif($ratetimenum) { - # This is a timed rate, so go to the etime of this interval. - # If it's followed by another timed rate, the stime of that - # interval should match the etime of this one. - my $interval = $rate_detail->rate_time->contains($weektime); - $etime = $interval->etime; - } - else { - # This is a default rate, so use the stime of the next - # interval in the sequence. - my $next_int = first { $_->stime > $weektime } - @{ $interval_cache{$regionnum} }; - if ($next_int) { - $etime = $next_int->stime; - } - else { - # weektime is near the end of the week, so decrement - # it by a full week and use the stime of the first - # interval. - $weektime -= (3600*24*7); - $etime = $interval_cache{$regionnum}->[0]->stime; - } - } - - my $charge_sec = min($seconds_left, $etime - $weektime); - - $seconds_left -= $charge_sec; - - $included_min{$regionnum}{$ratetimenum} = $rate_detail->min_included - unless exists $included_min{$regionnum}{$ratetimenum}; - - my $granularity = $rate_detail->sec_granularity; - - my $minutes; - if ( $granularity ) { # charge per minute - # Round up to the nearest $granularity - if ( $charge_sec and $charge_sec % $granularity ) { - $charge_sec += $granularity - ($charge_sec % $granularity); - } - $minutes = $charge_sec / 60; #don't round this - } - else { # per call - $minutes = 1; - $seconds_left = 0; - } - - $seconds += $charge_sec; - - $included_min{$regionnum}{$ratetimenum} -= $minutes; - if ( $included_min{$regionnum}{$ratetimenum} <= 0 ) { - my $charge_min = 0 - $included_min{$regionnum}{$ratetimenum}; #XXX should preserve - #(display?) this - $included_min{$regionnum}{$ratetimenum} = 0; - $charge += ($rate_detail->min_charge * $charge_min); #still not rounded - } - - # choose next rate_detail - $rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode, - 'phonenum' => $number, - 'weektime' => $etime }) - if($seconds_left); - # we have now moved forward to $etime - $weektime = $etime; - - } #while $seconds_left - # this is why we need regionnum/rate_region.... - warn " (rate region $rate_region)\n" if $DEBUG; - - $classnum = $rate_detail->classnum; - $charge = sprintf('%.2f', $charge + 0.000001); # NOW round it. - 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(there is a rate_detail) - - - if ( $charge > 0 ) { - #just use FS::cust_bill_pkg_detail objects? - my $call_details; - my $phonenum = $svc_x->phonenum; - - if ( scalar(@call_details) == 1 ) { - $call_details = - [ 'C', - $call_details[0], - $charge, - $classnum, - $phonenum, - $seconds, - $regionname, - ]; - } else { #only used for $rating_method eq 'upstream' now - $csv->combine(@call_details); - $call_details = - [ 'C', - $csv->string, - $charge, - $classnum, - $phonenum, - $seconds, - $regionname, - ]; - } - warn " adding details on charge to invoice: [ ". - join(', ', @{$call_details} ). " ]" - if ( $DEBUG && ref($call_details) ); - push @$details, $call_details; #\@call_details, - } - - # if the customer flag is on, call "downstream_csv" or something - # like it to export the call downstream! - # XXX price plan option to pick format, or something... - #$downstream_cdr .= $cdr->downstream_csv( 'format' => 'XXX format' ) - # if $spool_cdr; - - my $error = $cdr->set_status_and_rated_price( 'done', - $charge, - $cust_svc->svcnum, - ); - die $error if $error; - - } - - } # $cdr - - } # $cust_svc - - unshift @$details, [ 'C', - FS::cdr::invoice_header($output_format), - '', - '', - '', - '', - '', - ] - if @$details && $rating_method ne 'upstream'; - -# if ( $spool_cdr && length($downstream_cdr) ) { -# -# use FS::UID qw(datasrc); -# my $dir = '/usr/local/etc/freeside/export.'. datasrc. '/cdr'; -# mkdir $dir, 0700 unless -d $dir; -# $dir .= '/'. $cust_pkg->custnum. -# mkdir $dir, 0700 unless -d $dir; -# my $filename = time2str("$dir/CDR%Y%m%d-spool.CSV", time); #XXX invoice date instead? would require changing the order things are generated in cust_main::bill insert cust_bill first - with transactions it could be done though -# -# push @{ $param->{'precommit_hooks'} }, -# sub { -# #lock the downstream spool file and append the records -# use Fcntl qw(:flock); -# use IO::File; -# my $spool = new IO::File ">>$filename" -# or die "can't open $filename: $!\n"; -# flock( $spool, LOCK_EX) -# or die "can't lock $filename: $!\n"; -# seek($spool, 0, 2) -# or die "can't seek to end of $filename: $!\n"; -# print $spool $downstream_cdr; -# flock( $spool, LOCK_UN ); -# close $spool; -# }; -# -# } #if ( $spool_cdr && length($downstream_cdr) ) - - $charges; -} - -#returns a reason why not to rate this CDR, or false if the CDR is chargeable -sub check_chargable { - my( $self, $cdr, %flags ) = @_; - - #should have some better way of checking these options from a hash - #or something - - my @opt = qw( - use_amaflags - use_disposition - use_disposition_taqua - use_carrierid - use_cdrtypenum - skip_dst_prefix - skip_dcontext - skip_dstchannel_prefix - skip_src_length_more noskip_src_length_accountcode_tollfree - skip_dst_length_less noskip_dst_length_accountcode_tollfree - skip_lastapp - skip_max_callers - ); - foreach my $opt (grep !exists($flags{option_cache}->{$_}), @opt ) { - $flags{option_cache}->{$opt} = $self->option($opt, 1); - } - my %opt = %{ $flags{option_cache} }; - - return 'amaflags != 2' - if $opt{'use_amaflags'} && $cdr->amaflags != 2; - - return 'disposition != ANSWERED' - if $opt{'use_disposition'} && $cdr->disposition ne 'ANSWERED'; - - return "disposition != 100" - if $opt{'use_disposition_taqua'} && $cdr->disposition != 100; - - return "carrierid != $opt{'use_carrierid'}" - if length($opt{'use_carrierid'}) - && $cdr->carrierid ne $opt{'use_carrierid'} #ne otherwise 0 matches '' - && ! $flags{'da_rewrote'}; - - return "cdrtypenum != $opt{'use_cdrtypenum'}" - if length($opt{'use_cdrtypenum'}) - && $cdr->cdrtypenum ne $opt{'use_cdrtypenum'}; #ne otherwise 0 matches '' - - foreach(split(',',$opt{'skip_dst_prefix'})) { - return "dst starts with '$_'" - if length($_) && substr($cdr->dst,0,length($_)) eq $_; - } - - return "dcontext IN ( $opt{'skip_dcontext'} )" - if $opt{'skip_dcontext'} =~ /\S/ - && grep { $cdr->dcontext eq $_ } split(/\s*,\s*/, $opt{'skip_dcontext'}); - - my $len_prefix = length($opt{'skip_dstchannel_prefix'}); - return "dstchannel starts with $opt{'skip_dstchannel_prefix'}" - if $len_prefix - && substr($cdr->dstchannel,0,$len_prefix) eq $opt{'skip_dstchannel_prefix'}; - - my $dst_length = $opt{'skip_dst_length_less'}; - return "destination less than $dst_length digits" - if $dst_length && length($cdr->dst) < $dst_length - && ! ( $opt{'noskip_dst_length_accountcode_tollfree'} - && $cdr->is_tollfree('accountcode') - ); - - return "lastapp is $opt{'skip_lastapp'}" - if length($opt{'skip_lastapp'}) && $cdr->lastapp eq $opt{'skip_lastapp'}; - - my $src_length = $opt{'skip_src_length_more'}; - if ( $src_length ) { - - if ( $opt{'noskip_src_length_accountcode_tollfree'} ) { - - if ( $cdr->is_tollfree('accountcode') ) { - return "source less than or equal to $src_length digits" - if length($cdr->src) <= $src_length; - } else { - return "source more than $src_length digits" - if length($cdr->src) > $src_length; - } - - } else { - return "source more than $src_length digits" - if length($cdr->src) > $src_length; - } - - } - - return "max_callers <= $opt{skip_max_callers}" - if length($opt{'skip_max_callers'}) - and length($cdr->max_callers) - and $cdr->max_callers <= $opt{'skip_max_callers'}; - - #all right then, rate it - ''; -} - -sub is_free { - 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; -} - -1; - diff --git a/FS/FS/part_pkg/voip_inbound.pm b/FS/FS/part_pkg/voip_inbound.pm deleted file mode 100644 index 1b91575..0000000 --- a/FS/FS/part_pkg/voip_inbound.pm +++ /dev/null @@ -1,366 +0,0 @@ -package FS::part_pkg::voip_inbound; - -use strict; -use vars qw(@ISA $DEBUG %info); -use Date::Format; -use Tie::IxHash; -use FS::Conf; -use FS::Record qw(qsearchs qsearch); -use FS::part_pkg::recur_Common; -use FS::cdr; -use FS::part_pkg::recur_Common; - -@ISA = qw(FS::part_pkg::recur_Common); - -$DEBUG = 0; - -tie my %temporalities, 'Tie::IxHash', - 'upcoming' => "Upcoming (future)", - 'preceding' => "Preceding (past)", -; - -tie my %granularity, 'Tie::IxHash', FS::rate_detail::granularities(); - -%info = ( - 'name' => 'VoIP flat rate pricing of CDRs for inbound calls', - 'shortname' => 'VoIP/telco CDR rating (inbound)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - #false laziness w/flat.pm - 'recur_temporality' => { 'name' => 'Charge recurring fee for period', - 'type' => 'select', - 'select_options' => \%temporalities, - }, - 'cutoff_day' => { 'name' => 'Billing Day (1 - 28) for prorating or '. - 'subscription', - 'default' => '1', - }, - 'add_full_period'=> { 'name' => 'When prorating first month, also bill '. - 'for one full period after that', - 'type' => 'checkbox', - }, - - 'recur_method' => { 'name' => 'Recurring fee method', - 'type' => 'select', - 'select_options' => \%FS::part_pkg::recur_Common::recur_method, - }, - - 'min_charge' => { 'name' => 'Charge per minute', - }, - - 'sec_granularity' => { 'name' => 'Granularity', - 'type' => 'select', - 'select_options' => \%granularity, - }, - - 'default_prefix' => { 'name' => 'Default prefix optionally prepended to customer DID numbers when searching for CDR records', - 'default' => '+1', - }, - - 'disable_tollfree' => { 'name' => 'Disable automatic toll-free processing', - 'type' => 'checkbox', - }, - - 'use_amaflags' => { 'name' => 'Do not charge for CDRs where the amaflags field is not set to "2" ("BILL"/"BILLING").', - 'type' => 'checkbox', - }, - - 'use_disposition' => { 'name' => 'Do not charge for CDRs where the disposition flag is not set to "ANSWERED".', - 'type' => 'checkbox', - }, - - 'use_disposition_taqua' => { 'name' => 'Do not charge for CDRs where the disposition is not set to "100" (Taqua).', - 'type' => 'checkbox', - }, - - 'use_carrierid' => { 'name' => 'Do not charge for CDRs where the Carrier ID is not set to: ', - }, - - 'use_cdrtypenum' => { 'name' => 'Do not charge for CDRs where the CDR Type is not set to: ', - }, - - 'skip_dcontext' => { 'name' => 'Do not charge for CDRs where the dcontext is set to any of these (comma-separated) values:', - }, - - 'skip_dstchannel_prefix' => { 'name' => 'Do not charge for CDRs where the dstchannel starts with:', - }, - - 'skip_dst_length_less' => { 'name' => 'Do not charge for CDRs where the destination is less than this many digits:', - }, - - 'skip_lastapp' => { 'name' => 'Do not charge for CDRs where the lastapp matches this value', - }, - - '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', - 'type' => 'select', - 'select_options' => { FS::cdr::invoice_formats() }, - 'default' => 'default', #XXX test - }, - - 'usage_section' => { 'name' => 'Section in which to place usage charges (whether separated or not)', - }, - - 'summarize_usage' => { 'name' => 'Include usage summary with recurring charges when usage is in separate section', - 'type' => 'checkbox', - }, - - 'usage_mandate' => { 'name' => 'Always put usage details in separate section', - 'type' => 'checkbox', - }, - #eofalse - - 'bill_every_call' => { 'name' => 'Generate an invoice immediately for every call. Useful for prepaid.', - 'type' => 'checkbox', - }, - - #XXX also have option for an external db -# 'cdr_location' => { 'name' => 'CDR database location' -# 'type' => 'select', -# 'select_options' => \%cdr_location, -# 'select_callback' => { -# 'external' => { -# 'enable' => [ 'datasrc', 'username', 'password' ], -# }, -# 'internal' => { -# 'disable' => [ 'datasrc', 'username', 'password' ], -# } -# }, -# }, -# 'datasrc' => { 'name' => 'DBI data source for external CDR table', -# 'disabled' => 'Y', -# }, -# 'username' => { 'name' => 'External database username', -# 'disabled' => 'Y', -# }, -# 'password' => { 'name' => 'External database password', -# 'disabled' => 'Y', -# }, - - }, - 'fieldorder' => [qw( - recur_temporality - recur_method cutoff_day add_full_period - min_charge sec_granularity - default_prefix - disable_tollfree - use_amaflags use_disposition - use_disposition_taqua use_carrierid use_cdrtypenum - skip_dcontext skip_dstchannel_prefix - skip_dst_length_less skip_lastapp - use_duration - output_format usage_mandate summarize_usage usage_section - bill_every_call - ) - ], - 'weight' => 40, -); - -sub calc_setup { - my($self, $cust_pkg ) = @_; - $self->option('setup_fee'); -} - -sub calc_recur { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - my $charges = 0; - - $charges += $self->calc_usage(@_); - $charges += $self->calc_recur_Common(@_); - - $charges; - -} - -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 - -sub calc_usage { - my $self = shift; - my($cust_pkg, $sdate, $details, $param ) = @_; - - #my $last_bill = $cust_pkg->last_bill; - my $last_bill = $cust_pkg->get('last_bill'); #->last_bill falls back to setup - - return 0 - if $self->option('recur_temporality', 1) eq 'preceding' - && ( $last_bill eq '' || $last_bill == 0 ); - - my $spool_cdr = $cust_pkg->cust_main->spool_cdr; - - my %included_min = (); - - my $charges = 0; - -# my $downstream_cdr = ''; - - my $disable_tollfree = $self->option('disable_tollfree'); - my $ignore_unrateable = $self->option('ignore_unrateable', 'Hush!'); - my $use_duration = $self->option('use_duration'); - - my $output_format = $self->option('output_format', 'Hush!') || 'default'; - - #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; - - foreach my $cust_svc ( - grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc - ) { - my $svc_phone = $cust_svc->svc_x; - - foreach my $cdr ( $svc_phone->get_cdrs( - 'for_update' => 1, - 'status' => '', # unprocessed only - 'default_prefix' => $self->option('default_prefix'), - 'inbound' => 1, - ) - ) { - if ( $DEBUG > 1 ) { - warn "rating inbound CDR $cdr\n". - join('', map { " $_ => ". $cdr->{$_}. "\n" } keys %$cdr ); - } - my $granularity = length($self->option('sec_granularity')) - ? $self->option('sec_granularity') - : 60; - - my $seconds = $use_duration ? $cdr->duration : $cdr->billsec; - - $seconds += $granularity - ( $seconds % $granularity ) - if $seconds # don't granular-ize 0 billsec calls (bills them) - && $granularity; # 0 is per call - my $minutes = sprintf("%.1f",$seconds / 60); - $minutes =~ s/\.0$// if $granularity == 60; # count whole minutes, convert to integer - $minutes = 1 unless $granularity; # per call - my $charge = sprintf('%.2f', ( $self->option('min_charge') * $minutes ) - + 0.00000001 ); #so 1.00005 rounds to 1.0001 - next if !$charge; - $charges += $charge; - my @call_details = ($cdr->downstream_csv( 'format' => $output_format, - 'charge' => $charge, - 'minutes' => $minutes, - 'granularity' => $granularity, - ) - ); - push @$details, - [ 'C', - $call_details[0], - $charge, - $cdr->calltypenum, #classnum - $self->phonenum, - $seconds, - '', #regionname, not set for inbound calls - ]; - - my $error = $cdr->set_status_and_rated_price( 'done', - $charge, - $cust_svc->svcnum, - 'inbound' => 1 ); - die $error if $error; - - } #$cdr - } # $cust_svc - unshift @$details, [ 'C', - FS::cdr::invoice_header($output_format), - '', - '', - '', - '', - '', - ] - if @$details; - - $charges; -} - -#returns a reason why not to rate this CDR, or false if the CDR is chargeable -sub check_chargable { - my( $self, $cdr, %flags ) = @_; - - #should have some better way of checking these options from a hash - #or something - - my @opt = qw( - use_amaflags - use_disposition - use_disposition_taqua - use_carrierid - use_cdrtypenum - skip_dcontext - skip_dstchannel_prefix - skip_dst_length_less - skip_lastapp - ); - foreach my $opt (grep !exists($flags{option_cache}->{$_}), @opt ) { - $flags{option_cache}->{$opt} = $self->option($opt, 1); - } - my %opt = %{ $flags{option_cache} }; - - return 'amaflags != 2' - if $opt{'use_amaflags'} && $cdr->amaflags != 2; - - return 'disposition != ANSWERED' - if $opt{'use_disposition'} && $cdr->disposition ne 'ANSWERED'; - - return "disposition != 100" - if $opt{'use_disposition_taqua'} && $cdr->disposition != 100; - - return "carrierid != $opt{'use_carrierid'}" - if length($opt{'use_carrierid'}) - && $cdr->carrierid ne $opt{'use_carrierid'} #ne otherwise 0 matches '' - && ! $flags{'da_rewrote'}; - - return "cdrtypenum != $opt{'use_cdrtypenum'}" - if length($opt{'use_cdrtypenum'}) - && $cdr->cdrtypenum ne $opt{'use_cdrtypenum'}; #ne otherwise 0 matches '' - - return "dcontext IN ( $opt{'skip_dcontext'} )" - if $opt{'skip_dcontext'} =~ /\S/ - && grep { $cdr->dcontext eq $_ } split(/\s*,\s*/, $opt{'skip_dcontext'}); - - my $len_prefix = length($opt{'skip_dstchannel_prefix'}); - return "dstchannel starts with $opt{'skip_dstchannel_prefix'}" - if $len_prefix - && substr($cdr->dstchannel,0,$len_prefix) eq $opt{'skip_dstchannel_prefix'}; - - my $dst_length = $opt{'skip_dst_length_less'}; - return "destination less than $dst_length digits" - if $dst_length && length($cdr->dst) < $dst_length; - - return "lastapp is $opt{'skip_lastapp'}" - if length($opt{'skip_lastapp'}) && $cdr->lastapp eq $opt{'skip_lastapp'}; - - #all right then, rate it - ''; -} - -sub is_free { - 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 = - scalar(grep { $_->part_svc->svcdb eq 'svc_phone' } $cust_pkg->cust_svc); - $count; -} - -1; - diff --git a/FS/FS/part_pkg/voip_sqlradacct.pm b/FS/FS/part_pkg/voip_sqlradacct.pm deleted file mode 100644 index 5388767..0000000 --- a/FS/FS/part_pkg/voip_sqlradacct.pm +++ /dev/null @@ -1,185 +0,0 @@ -package FS::part_pkg::voip_sqlradacct; - -use strict; -use vars qw(@ISA $DEBUG %info); -use Date::Format; -use FS::Record qw(qsearchs qsearch); -use FS::part_pkg::flat; -#use FS::rate; -use FS::rate_prefix; - -@ISA = qw(FS::part_pkg::flat); - -$DEBUG = 1; - -%info = ( - 'disabled' => 1, #they're sucked into our CDR table now instead - 'name' => 'VoIP rating by plan of CDR records in an SQL RADIUS radacct table', - 'shortname' => 'VoIP/telco CDR rating (external RADIUS)', - 'inherit_fields' => [ 'global_Mixin' ], - 'fields' => { - 'ratenum' => { 'name' => 'Rate plan', - 'type' => 'select', - 'select_table' => 'rate', - 'select_key' => 'ratenum', - 'select_label' => 'ratename', - }, - }, - 'fieldorder' => [qw( ratenum ignore_unrateable )], - 'weight' => 40, -); - -sub calc_setup { - my($self, $cust_pkg ) = @_; - $self->option('setup_fee'); -} - -#false laziness w/voip_cdr... resolve it if this one ever gets used again -sub calc_recur { - my($self, $cust_pkg, $sdate, $details ) = @_; - - my $last_bill = $cust_pkg->last_bill; - - my $ratenum = $cust_pkg->part_pkg->option('ratenum'); - - my %included_min = (); - - my $charges = 0; - - foreach my $cust_svc ( - grep { $_->part_svc->svcdb eq 'svc_acct' } $cust_pkg->cust_svc - ) { - - foreach my $session ( - $cust_svc->get_session_history( $last_bill, $$sdate ) - ) { - if ( $DEBUG > 1 ) { - warn "rating session $session\n". - join('', map { " $_ => ". $session->{$_}. "\n" } keys %$session ); - } - - ### - # look up rate details based on called station id - ### - - my $dest = $session->{'calledstationid'}; - - #remove non-phone# stuff and whitespace - $dest =~ s/\s//g; - my $proto = ''; - $dest =~ s/^(\w+):// and $proto = $1; #sip: - my $siphost = ''; - $dest =~ s/\@(.*)$// and $siphost = $1; # @10.54.32.1, @sip.example.com - - #determine the country code - my $countrycode; - if ( $dest =~ /^011(((\d)(\d))(\d))(\d+)$/ ) { - - my( $three, $two, $one, $u1, $u2, $rest ) = ( $1, $2, $3, $4, $5, $6 ); - #first look for 1 digit country code - if ( qsearch('rate_prefix', { 'countrycode' => $one } ) ) { - $countrycode = $one; - $dest = $u1.$u2.$rest; - } elsif ( qsearch('rate_prefix', { 'countrycode' => $two } ) ) { #or 2 - $countrycode = $two; - $dest = $u2.$rest; - } else { #3 digit country code - $countrycode = $three; - $dest = $rest; - } - - } else { - $countrycode = '1'; - $dest =~ s/^1//;# if length($dest) > 10; - } - - warn "rating call to +$countrycode $dest\n" if $DEBUG; - - #find a rate prefix, first look at most specific (4 digits) then 3, etc., - # finally trying the country code only - my $rate_prefix = ''; - for my $len ( reverse(1..6) ) { - $rate_prefix = qsearchs('rate_prefix', { - 'countrycode' => $countrycode, - #'npa' => { op=> 'LIKE', value=> substr($dest, 0, $len) } - 'npa' => substr($dest, 0, $len), - } ) and last; - } - $rate_prefix ||= qsearchs('rate_prefix', { - 'countrycode' => $countrycode, - 'npa' => '', - }); - - die "Can't find rate for call to +$countrycode $dest\n" - unless $rate_prefix; - - my $regionnum = $rate_prefix->regionnum; - my $rate_detail = qsearchs('rate_detail', { - 'ratenum' => $ratenum, - 'dest_regionnum' => $regionnum, - } ); - - warn " found rate for regionnum $regionnum ". - "and rate detail $rate_detail\n" - if $DEBUG; - - ### - # find the price and add detail to the invoice - ### - - $included_min{$regionnum} = $rate_detail->min_included - unless exists $included_min{$regionnum}; - - my $granularity = $rate_detail->sec_granularity; - my $seconds = $session->{'acctsessiontime'}; - $seconds += $granularity - ( $seconds % $granularity ); - my $minutes = sprintf("%.1f", $seconds / 60); - $minutes =~ s/\.0$// if $granularity == 60; - - $included_min{$regionnum} -= $minutes; - - my $charge = 0; - if ( $included_min{$regionnum} < 0 ) { - my $charge_min = 0 - $included_min{$regionnum}; - $included_min{$regionnum} = 0; - $charge = sprintf('%.2f', $rate_detail->min_charge * $charge_min ); - $charges += $charge; - } - - my $rate_region = $rate_prefix->rate_region; - warn " (rate region $rate_region)\n" if $DEBUG; - - my @call_details = ( - #time2str("%Y %b %d - %r", $session->{'acctstarttime'}), - time2str("%c", $session->{'acctstarttime'}), - $minutes.'m', - '$'.$charge, - "+$countrycode $dest", - $rate_region->regionname, - ); - - warn " adding details on charge to invoice: ". - join(' - ', @call_details ) - if $DEBUG; - - push @$details, join(' - ', @call_details); #\@call_details, - - } # $session - - } # $cust_svc - - $self->option('recur_fee') + $charges; - -} - -sub can_discount { 0; } - -sub is_free { 0; } - -sub base_recur { - my($self, $cust_pkg) = @_; - $self->option('recur_fee'); -} - -1; - |