diff options
Diffstat (limited to 'FS')
-rw-r--r-- | FS/FS/Report/Table.pm | 126 | ||||
-rw-r--r-- | FS/FS/Report/Table/Monthly.pm | 43 | ||||
-rw-r--r-- | FS/FS/Schema.pm | 17 | ||||
-rw-r--r-- | FS/FS/cdr.pm | 20 | ||||
-rw-r--r-- | FS/FS/cdr/zintel.pm | 12 | ||||
-rw-r--r-- | FS/FS/cust_pkg/Search.pm | 15 | ||||
-rw-r--r-- | FS/FS/h_Common.pm | 49 | ||||
-rw-r--r-- | FS/FS/h_cust_pkg.pm | 89 | ||||
-rw-r--r-- | FS/FS/part_pkg/agent_cdr.pm | 8 | ||||
-rw-r--r-- | FS/FS/rate.pm | 31 | ||||
-rw-r--r-- | FS/FS/rate_detail.pm | 20 | ||||
-rw-r--r-- | FS/FS/svc_Common.pm | 3 | ||||
-rw-r--r-- | FS/FS/tax_rate.pm | 4 |
13 files changed, 344 insertions, 93 deletions
diff --git a/FS/FS/Report/Table.pm b/FS/FS/Report/Table.pm index 98f66e904..3a4a1695d 100644 --- a/FS/FS/Report/Table.pm +++ b/FS/FS/Report/Table.pm @@ -664,37 +664,10 @@ sub cust_bill_pkg_discount { } -sub pkg_field_where { - my( $self, $field, $speriod, $eperiod, $agentnum, %opt ) = @_; - # someday this will use an aggregate query and return all the columns - # at once - # and I will drive a Tesla and have a live-in sushi chef who is also a - # ninja bodyguard - my @where = ( - $self->in_time_period_and_agent($speriod, - $eperiod, - $agentnum, - "cust_pkg.$field", - ), - $self->with_refnum(%opt), - $self->with_towernum(%opt), - $self->with_zip(%opt), - # can't use with_classnum here... - ); - if ($opt{classnum}) { - my $classnum = $opt{classnum}; - $classnum = [ $classnum ] if !ref($classnum); - @$classnum = grep /^\d+$/, @$classnum; - my $in = 'IN ('. join(',', @$classnum). ')'; - push @where, "COALESCE(part_pkg.classnum, 0) $in" if scalar @$classnum; - } +##### churn report ##### - ' WHERE ' . join(' AND ', grep $_, @where); -} - -=item setup_pkg: The number of packages with setup dates in the period. - -This excludes packages created by package changes. Options: +=item active_pkg: The number of packages that were active at the start of +the period. The end date of the period is ignored. Options: - refnum: Limit to customers with this advertising source. - classnum: Limit to packages with this class. @@ -704,61 +677,86 @@ This excludes packages created by package changes. Options: Except for zip, any of these can be an arrayref to allow multiple values for the field. -=item susp_pkg: The number of suspended packages that were last suspended -in the period. Options are as for setup_pkg. +=item setup_pkg: The number of packages with setup dates in the period. This +excludes packages created by package changes. Options are as for active_pkg. + +=item susp_pkg: The number of packages that were suspended in the period +(and not canceled). Options are as for active_pkg. + +=item unsusp_pkg: The number of packages that were unsuspended in the period. +Options are as for active_pkg. =item cancel_pkg: The number of packages with cancel dates in the period. Excludes packages that were canceled to be changed to a new package. Options -are as for setup_pkg. +are as for active_pkg. =cut +sub active_pkg { + my $self = shift; + $self->churn_pkg('active', @_); +} + sub setup_pkg { my $self = shift; - my $sql = 'SELECT COUNT(*) FROM cust_pkg - LEFT JOIN part_pkg USING (pkgpart) - LEFT JOIN cust_main USING (custnum)'. - $self->pkg_field_where('setup', @_) . - ' AND change_pkgnum IS NULL'; + $self->churn_pkg('setup', @_); +} - $self->scalar_sql($sql); +sub cancel_pkg { + my $self = shift; + $self->churn_pkg('cancel', @_); } sub susp_pkg { - # number of currently suspended packages that were suspended in the period my $self = shift; - my $sql = 'SELECT COUNT(*) FROM cust_pkg - LEFT JOIN part_pkg USING (pkgpart) - LEFT JOIN cust_main USING (custnum) '. - $self->pkg_field_where('susp', @_); + $self->churn_pkg('susp', @_); +} - $self->scalar_sql($sql); +sub unsusp_pkg { + my $self = shift; + $self->churn_pkg('unsusp', @_); } -sub cancel_pkg { - # number of packages canceled in the period and not changed to another - # package +sub churn_pkg { my $self = shift; - my $sql = 'SELECT COUNT(*) FROM cust_pkg - LEFT JOIN part_pkg USING (pkgpart) - LEFT JOIN cust_main USING (custnum) - LEFT JOIN cust_pkg changed_to_pkg ON( - cust_pkg.pkgnum = changed_to_pkg.change_pkgnum - ) '. - $self->pkg_field_where('cancel', @_) . - ' AND changed_to_pkg.pkgnum IS NULL'; + my ( $status, $speriod, $eperiod, $agentnum, %opt ) = @_; + my ($from, @where) = + FS::h_cust_pkg->churn_fromwhere_sql( $status, $speriod, $eperiod); + + push @where, $self->pkg_where(%opt, 'agentnum' => $agentnum); + + my $sql = "SELECT COUNT(*) FROM $from + JOIN part_pkg ON (cust_pkg.pkgpart = part_pkg.pkgpart) + JOIN cust_main ON (cust_pkg.custnum = cust_main.custnum)"; + $sql .= ' WHERE '.join(' AND ', @where) + if scalar(@where); $self->scalar_sql($sql); } -#this is going to be harder.. -#sub unsusp_pkg { -# my( $self, $speriod, $eperiod, $agentnum ) = @_; -# $self->scalar_sql(" -# SELECT COUNT(*) FROM h_cust_pkg -# WHERE -# -#} +sub pkg_where { + my $self = shift; + my %opt = @_; + my @where = ( + "part_pkg.freq != '0'", + $self->with_refnum(%opt), + $self->with_towernum(%opt), + $self->with_zip(%opt), + ); + if ($opt{agentnum} =~ /^(\d+)$/) { + push @where, "cust_main.agentnum = $1"; + } + if ($opt{classnum}) { + my $classnum = $opt{classnum}; + $classnum = [ $classnum ] if !ref($classnum); + @$classnum = grep /^\d+$/, @$classnum; + my $in = 'IN ('. join(',', @$classnum). ')'; + push @where, "COALESCE(part_pkg.classnum, 0) $in" if scalar @$classnum; + } + @where; +} + +##### end of churn report stuff ##### sub in_time_period_and_agent { my( $self, $speriod, $eperiod, $agentnum ) = splice(@_, 0, 4); diff --git a/FS/FS/Report/Table/Monthly.pm b/FS/FS/Report/Table/Monthly.pm index b8e52ae63..0ff7efd16 100644 --- a/FS/FS/Report/Table/Monthly.pm +++ b/FS/FS/Report/Table/Monthly.pm @@ -88,6 +88,13 @@ hidden rows (due to C<remove_empty>) filtered out, which is the only reason to do this. Now that we have C<indices> it's probably better to use that. +=item PROCESSING + +=item normalize: Set this to an item index to have all other items expressed +as a percentage of that one. That item will then be omitted from the output. +If the normalization item is zero in some period, all the values in that +period will be undef. + =head1 RETURNED DATA The C<data> method runs the report and returns a hashref of the following: @@ -180,7 +187,7 @@ sub data { my $eperiod = timelocal(0,0,0,1,$smonth-1,$syear); push @{$data{eperiod}}, $eperiod; - my $col = 0; + my $col = 0; # a "column" here is the data corresponding to an item my @items = @{$self->{'items'}}; my $i; @@ -214,7 +221,30 @@ sub data { $data{'colors'} = $self->{'colors'}; $data{'links'} = $self->{'links'} || []; - if ( !$self->{'cross_params'} and $self->{'remove_empty'} ) { + if ( defined $self->{'normalize'} ) { + my $norm_col = $self->{'normalize'}; + my $norm_data = $data{data}->[$norm_col]; + + my $row = 0; + while ( exists $data{speriod}->[$row] ) { + my $col = 0; + while ( exists $data{items}->[$col ] ) { + if ( $col != $norm_col ) { + if ( $norm_data->[$row] == 0 ) { + $data{data}->[$col][$row] = undef; + } else { + $data{data}->[$col][$row] = + ( $data{data}->[$col][$row] * 100 / $norm_data->[$row] ); + } + } + $col++; + } + $row++; + } + } + + if ( !$self->{'cross_params'} ) { + # remove unnecessary rows my $col = 0; #these need to get generalized, sheesh @@ -228,6 +258,12 @@ sub data { my @indices = (); foreach my $item ( @{$self->{'items'}} ) { + # if remove_empty, then remove rows of zeroes + my $is_nonzero = scalar( grep { $_ != 0 } @{ $data{'data'}->[$col] }); + next if ($self->{'remove_empty'} and $is_nonzero == 0); + # if normalizing, strip out the norm column + next if (defined($self->{'normalize'}) and $self->{'normalize'} == $col); + if ( grep { $_ != 0 } @{$data{'data'}->[$col]} ) { push @newitems, $data{'items'}->[$col]; push @newlabels, $data{'item_labels'}->[$col]; @@ -236,7 +272,7 @@ sub data { push @newlinks, $data{'links'}->[$col]; push @indices, $col; } - + } continue { $col++; } @@ -248,6 +284,7 @@ sub data { $data{'indices'} = \@indices; } + # clean up after ourselves #dbh->rollback; # leave in until development is finished, for diagnostics diff --git a/FS/FS/Schema.pm b/FS/FS/Schema.pm index 4d6c2e9f2..0b82f91a5 100644 --- a/FS/FS/Schema.pm +++ b/FS/FS/Schema.pm @@ -4151,9 +4151,9 @@ sub tables_hashref { [ 'job' ], [ 'svcnum' ], [ 'custnum' ], [ 'status' ], ], 'foreign_keys' => [ - { columns => [ 'svcnum' ], - table => 'cust_svc', - }, +# { columns => [ 'svcnum' ], +# table => 'cust_svc', +# }, { columns => [ 'custnum' ], table => 'cust_main', }, @@ -4808,9 +4808,10 @@ sub tables_hashref { 'rate' => { 'columns' => [ - 'ratenum', 'serial', '', '', '', '', - 'ratename', 'varchar', '', $char_d, '', '', - 'agentnum', 'int', 'NULL', '', '', '', + 'ratenum', 'serial', '', '', '', '', + 'ratename', 'varchar', '',$char_d, '', '', + 'agentnum', 'int', 'NULL', '', '', '', + 'default_detailnum', 'int', 'NULL', '', '', '', ], 'primary_key' => 'ratenum', 'unique' => [], @@ -4827,7 +4828,7 @@ sub tables_hashref { 'ratedetailnum', 'serial', '', '', '', '', 'ratenum', 'int', '', '', '', '', 'orig_regionnum', 'int', 'NULL', '', '', '', - 'dest_regionnum', 'int', '', '', '', '', + 'dest_regionnum', 'int', 'NULL', '', '', '', 'min_included', 'int', '', '', '', '', 'conn_charge', 'decimal', '', '10,4', '0.0000', '', 'conn_cost', 'decimal', '', '10,4', '0.0000', '', @@ -4839,6 +4840,8 @@ sub tables_hashref { 'classnum', 'int', 'NULL', '', '', '', 'cdrtypenum', 'int', 'NULL', '', '', '', 'region_group', 'char', 'NULL', 1, '', '', + 'upstream_mult_charge', 'decimal', '', '10,4', '0.0000', '', + 'upstream_mult_cost', 'decimal', '', '10,4', '0.0000', '', ], 'primary_key' => 'ratedetailnum', 'unique' => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ], diff --git a/FS/FS/cdr.pm b/FS/FS/cdr.pm index 9859dfade..7a5668d52 100644 --- a/FS/FS/cdr.pm +++ b/FS/FS/cdr.pm @@ -799,8 +799,8 @@ sub rate_prefix { } + my $regionnum = $rate_detail->dest_regionnum; my $rate_region = $rate_detail->dest_region; - my $regionnum = $rate_region->regionnum; warn " found rate for regionnum $regionnum ". "and rate detail $rate_detail\n" if $DEBUG; @@ -842,6 +842,11 @@ sub rate_prefix { my $charge = 0; my $connection_charged = 0; + # before doing anything else, if there's an upstream multiplier and + # an upstream price, add that to the charge. (usually the rate detail + # will then have a minute charge of zero, but not necessarily.) + $charge += ($self->upstream_price || 0) * $rate_detail->upstream_mult_charge; + my $etime; while($seconds_left) { my $ratetimenum = $rate_detail->ratetimenum; # may be empty @@ -989,7 +994,7 @@ sub rate_prefix { $price, $opt{'svcnum'}, 'rated_pretty_dst' => $pretty_dst, - 'rated_regionname' => $rate_region->regionname, + 'rated_regionname' => ($rate_region ? $rate_region->regionname : ''), 'rated_seconds' => $rated_seconds, #$seconds, 'rated_granularity' => $rate_detail->sec_granularity, #$granularity 'rated_ratedetailnum' => $rate_detail->ratedetailnum, @@ -1073,10 +1078,15 @@ sub rate_cost { my $rate_detail = qsearchs('rate_detail', { 'ratedetailnum' => $self->rated_ratedetailnum } ); - return $rate_detail->min_cost if $self->rated_granularity == 0; + my $charge = 0; + $charge += ($self->upstream_price || 0) * ($rate_detail->upstream_mult_cost); - my $minutes = $self->rated_seconds / 60; - my $charge = $rate_detail->conn_cost + $minutes * $rate_detail->min_cost; + if ( $self->rated_granularity == 0 ) { + $charge += $rate_detail->min_cost; + } else { + my $minutes = $self->rated_seconds / 60; + $charge += $rate_detail->conn_cost + $minutes * $rate_detail->min_cost; + } sprintf('%.2f', $charge + .00001 ); diff --git a/FS/FS/cdr/zintel.pm b/FS/FS/cdr/zintel.pm index 1d2236c38..eb08038ff 100644 --- a/FS/FS/cdr/zintel.pm +++ b/FS/FS/cdr/zintel.pm @@ -16,7 +16,17 @@ use Date::Parse; 'accountcode', #customer 'src', #anumber - 'dst', #bnumber + sub { my ($cdr, $dst) = @_; # Handling cosolidated local calls in the CDR formats + + my $src = $cdr->src; + + if ($dst =~ /^64\/U$/) { + $cdr->set('dst', $src); + } else { + $cdr->set('dst', $dst); + } + }, #bnumber + sub { my ($cdr, $calldate) = @_; $cdr->set('calldate', $calldate); diff --git a/FS/FS/cust_pkg/Search.pm b/FS/FS/cust_pkg/Search.pm index 77196568b..9cd1ff063 100644 --- a/FS/FS/cust_pkg/Search.pm +++ b/FS/FS/cust_pkg/Search.pm @@ -281,6 +281,21 @@ sub search { } ### + # parse refnum (advertising source) + ### + + if ( exists($params->{'refnum'}) ) { + my @refnum; + if (ref $params->{'refnum'}) { + @refnum = @{ $params->{'refnum'} }; + } else { + @refnum = ( $params->{'refnum'} ); + } + my $in = join(',', grep /^\d+$/, @refnum); + push @where, "refnum IN($in)" if length $in; + } + + ### # parse package report options ### diff --git a/FS/FS/h_Common.pm b/FS/FS/h_Common.pm index ca13e1ba5..9b5ad09d9 100644 --- a/FS/FS/h_Common.pm +++ b/FS/FS/h_Common.pm @@ -110,6 +110,55 @@ sub sql_h_searchs { ($select, $where, $cacheobj, $as); } +=item sql_diff START_TIMESTAMP, END_TIMESTAMP[, WHERE] + +Returns a complete SQL statement to find all records that were changed +between START_TIMESTAMP and END_TIMESTAMP. This finds only replacements, +not new or deleted records. + +For each modified record, this will return I<one> row (not two rows as in +the history table) with the primary key of the record, "old_historynum" +(the historynum of the last modification before START_TIMESTAMP), and +"new_historynum" (the last modification before END_TIMESTAMP). Join these +back to the h_* table to retrieve the actual field values. + +Within the query, the last history records as of START and END are aliased +as "old" and "new"; you can append a WHERE clause to take advantage of this. + +=cut + +sub sql_diff { + my $class = shift; + my $table = $class->table; + my ($real_table) = ($table =~ /^h_(\w+)$/); + my $pkey = dbdef->table($real_table)->primary_key; + my @fields = "FS::$real_table"->fields; + + my ($sdate, $edate) = @_; + ($sdate, $edate) = ($edate, $sdate) if $edate < $sdate; + + my @select = ( + "old.$pkey", + 'old.historynum AS old_historynum', + 'new.historynum AS new_historynum', + ); + my $new = + "SELECT DISTINCT ON ($pkey) * FROM $table + WHERE history_action = 'replace_new' + AND history_date >= $sdate AND history_date < $edate + ORDER BY $pkey ASC, history_date DESC"; + my $old = + "SELECT DISTINCT ON ($pkey) * FROM $table + WHERE (history_action = 'replace_new' OR history_action = 'insert') + AND history_date < $sdate + ORDER BY $pkey ASC, history_date DESC"; + + my $from = "($new) AS new JOIN ($old) AS old USING ($pkey)"; + + return "SELECT ".join(',', @select)." FROM $from"; +} + + =back =head1 BUGS diff --git a/FS/FS/h_cust_pkg.pm b/FS/FS/h_cust_pkg.pm index 99037c22f..0c3db107f 100644 --- a/FS/FS/h_cust_pkg.pm +++ b/FS/FS/h_cust_pkg.pm @@ -67,7 +67,7 @@ sub search { # make some adjustments $query->{'table'} = 'h_cust_pkg'; - foreach (qw(select addl_from extra_sql count_query)) { + foreach (qw(select addl_from extra_sql count_query order_by)) { $query->{$_} =~ s/cust_pkg\b/h_cust_pkg/g; $query->{$_} =~ s/cust_main\b/h_cust_main/g; } @@ -92,9 +92,95 @@ sub search { $query; } +=item churn_fromwhere_sql STATUS, START, END + +Returns SQL fragments to do queries related to "package churn". STATUS +is one of "active", "setup", "cancel", "susp", or "unsusp". These do NOT +correspond directly to package statuses. START and END define a date range. + +- active: limit to packages that were active on START. END is ignored. +- setup: limit to packages that were set up between START and END, except +those created by package changes. +- cancel: limit to packages that were canceled between START and END, except +those changed into other packages. +- susp: limit to packages that were suspended between START and END. +- unsusp: limit to packages that were unsuspended between START and END. + +The logic of these may change in the future, especially with respect to +package changes. Watch this space. + +Returns a list of: +- a fragment usable as a FROM clause (without the keyword FROM), in which + the package table is named or aliased to 'cust_pkg' +- one or more conditions to include in the WHERE clause + +=cut + +sub churn_fromwhere_sql { + my ($self, $status, $speriod, $eperiod) = @_; + + my ($from, @where); + if ( $status eq 'active' ) { + # for all packages that were setup before $speriod, find the pkgnum + # and the most recent update of the package before $speriod + my $setup_before = "SELECT DISTINCT ON (pkgnum) pkgnum, historynum + FROM h_cust_pkg + WHERE setup < $speriod + AND history_date < $speriod + AND history_action IN('insert', 'replace_new') + ORDER BY pkgnum ASC, history_date DESC"; + # for each of these, exclude if the package was suspended or canceled + # in the most recent update before $speriod + $from = "h_cust_pkg AS cust_pkg + JOIN ($setup_before) AS setup_before USING (historynum)"; + @where = ( 'susp IS NULL', 'cancel IS NULL' ); + } elsif ( $status eq 'setup' ) { + # the simple case, because packages should only get set up once + # (but exclude those that were created due to a package change) + # XXX or should we include if they were created by a pkgpart change? + $from = "cust_pkg"; + @where = ( + "setup >= $speriod", + "setup < $eperiod", + "change_pkgnum IS NULL" + ); + } elsif ( $status eq 'cancel' ) { + # also simple, because packages should only be canceled once + # (exclude those that were canceled due to a package change) + $from = "cust_pkg"; + @where = ( + "cust_pkg.cancel >= $speriod", + "cust_pkg.cancel < $eperiod", + "NOT EXISTS(SELECT 1 FROM cust_pkg AS changed_to_pkg ". + "WHERE cust_pkg.pkgnum = changed_to_pkg.change_pkgnum)", + ); + } elsif ( $status eq 'susp' ) { + # more complicated + # find packages that were changed from susp = null to susp != null + my $susp_during = $self->sql_diff($speriod, $eperiod) . + ' WHERE old.susp IS NULL AND new.susp IS NOT NULL'; + $from = "h_cust_pkg AS cust_pkg + JOIN ($susp_during) AS susp_during + ON (susp_during.new_historynum = cust_pkg.historynum)"; + @where = ( 'cust_pkg.cancel IS NULL' ); + } elsif ( $status eq 'unsusp' ) { + # similar to 'susp' + my $unsusp_during = $self->sql_diff($speriod, $eperiod) . + ' WHERE old.susp IS NOT NULL AND new.susp IS NULL'; + $from = "h_cust_pkg AS cust_pkg + JOIN ($unsusp_during) AS unsusp_during + ON (unsusp_during.new_historynum = cust_pkg.historynum)"; + @where = ( 'cust_pkg.cancel IS NULL' ); + } else { + die "'$status' makes no sense"; + } + return ($from, @where); +} =head1 BUGS +churn_fromwhere_sql fails on MySQL. + =head1 SEE ALSO L<FS::cust_pkg>, L<FS::h_Common>, L<FS::Record>, schema.html from the base @@ -104,4 +190,3 @@ documentation. 1; - diff --git a/FS/FS/part_pkg/agent_cdr.pm b/FS/FS/part_pkg/agent_cdr.pm index 55792f2d2..a638b5b5a 100644 --- a/FS/FS/part_pkg/agent_cdr.pm +++ b/FS/FS/part_pkg/agent_cdr.pm @@ -23,7 +23,7 @@ tie my %temporalities, 'Tie::IxHash', %info = ( 'name' => 'Wholesale CDR cost billing, for master customers of an agent.', - 'shortname' => 'Whilesale CDR cost billing for agent.', + 'shortname' => 'Wholesale CDR cost billing for agent', 'inherit_fields' => [ 'prorate_Mixin', 'global_Mixin' ], 'fields' => { #false laziness w/cdr_termination @@ -177,7 +177,11 @@ sub calc_recur { my $classnum = ''; #usage class? #option to turn off? or just use squelch_cdr for the customer probably - push @$details, [ 'C', $call_details, $cost, $classnum ]; + # XXX use detail_format for this at some point + push @$details, { 'format' => 'C', + 'detail' => $call_details, + 'amount' => $cost, + 'classnum' => $classnum }; #eofalse laziness w/cdr_termination diff --git a/FS/FS/rate.pm b/FS/FS/rate.pm index aef9d8bec..9a5b90546 100644 --- a/FS/FS/rate.pm +++ b/FS/FS/rate.pm @@ -46,6 +46,16 @@ Rate name Optional agent (see L<FS::agent>) for agent-virtualized rates. +=item default_detailnum + +Optional rate detail to apply when a call doesn't match any region in the +rate plan. If this is not set, the call will either be left unrated (though +it may still be processed under a different pricing addon package), or be +marked as 'skipped', or throw a fatal error, depending on the setting of +the 'ignore_unrateable' package option. + +=item + =back =head1 METHODS @@ -268,6 +278,7 @@ sub check { $self->ut_numbern('ratenum') || $self->ut_text('ratename') #|| $self->ut_foreign_keyn('agentnum', 'agent', 'agentnum') + || $self->ut_numbern('default_detailnum') ; return $error if $error; @@ -277,8 +288,8 @@ sub check { =item dest_detail REGIONNUM | RATE_REGION_OBJECTD | HASHREF Returns the rate detail (see L<FS::rate_detail>) for this rate to the -specificed destination, or the empty string if no rate can be found for -the given destination. +specificed destination. If no rate can be found, returns the default +rate if there is one, and an empty string otherwise. Destination can be specified as an FS::rate_detail object or regionnum (see L<FS::rate_detail>), or as a hashref containing the following keys: @@ -379,8 +390,8 @@ sub dest_detail { foreach (@details) { return $_ if $_->ratetimenum eq ''; } - # found nothing - return; + # if still nothing, return the global default rate for this plan + return $self->default_detail; } =item rate_detail @@ -389,6 +400,18 @@ Returns all region-specific details (see L<FS::rate_detail>) for this rate. =back +=item default_detail + +Returns the default rate detail, if there is one. + +=cut + +sub default_detail { + my $self = shift; + $self->default_detailnum ? + FS::rate_detail->by_key($self->default_detailnum) : '' +} + =head1 SUBROUTINES =over 4 diff --git a/FS/FS/rate_detail.pm b/FS/FS/rate_detail.pm index d81d9dbda..d50c89f80 100644 --- a/FS/FS/rate_detail.pm +++ b/FS/FS/rate_detail.pm @@ -60,6 +60,13 @@ inherits from FS::Record. The following fields are currently supported: =item region_group - Group in region group for rate plan +=item upstream_mult_charge - the multiplier to apply to the upstream price. +Defaults to zero, and should stay zero unless this rate is intended to include +a markup on pre-rated CDRs. + +=item upstream_mult_cost - the multiplier to apply to the upstream price to +calculate the wholesale cost. + =back =head1 METHODS @@ -124,7 +131,7 @@ sub check { $self->ut_numbern('ratedetailnum') || $self->ut_foreign_key('ratenum', 'rate', 'ratenum') || $self->ut_foreign_keyn('orig_regionnum', 'rate_region', 'regionnum' ) - || $self->ut_foreign_key('dest_regionnum', 'rate_region', 'regionnum' ) + || $self->ut_foreign_keyn('dest_regionnum', 'rate_region', 'regionnum' ) || $self->ut_number('min_included') #|| $self->ut_money('min_charge') @@ -138,6 +145,9 @@ sub check { || $self->ut_foreign_keyn('classnum', 'usage_class', 'classnum' ) || $self->ut_enum('region_group', [ '', 'Y' ]) + + || $self->ut_floatn('upstream_mult_charge') + || $self->ut_floatn('upstream_mult_cost') ; return $error if $error; @@ -182,10 +192,11 @@ with this call plan rate. sub dest_regionname { my $self = shift; - $self->dest_region->regionname; + my $dest_region = $self->dest_region; + $dest_region ? $dest_region->regionname : 'Global default'; } -=item dest_regionname +=item dest_prefixes_short Returns a short list of the prefixes for the destination region (see L<FS::rate_region>) associated with this call plan rate. @@ -194,7 +205,8 @@ Returns a short list of the prefixes for the destination region sub dest_prefixes_short { my $self = shift; - $self->dest_region->prefixes_short; + my $dest_region = $self->dest_region; + $dest_region ? $dest_region->prefixes_short : ''; } =item rate_time diff --git a/FS/FS/svc_Common.pm b/FS/FS/svc_Common.pm index 4a2d61786..e4591720d 100644 --- a/FS/FS/svc_Common.pm +++ b/FS/FS/svc_Common.pm @@ -804,6 +804,9 @@ If there is an error, returns the error, otherwise returns false. =cut sub set_auto_inventory { + # don't try to do this during an upgrade + return '' if $FS::CurrentUser::upgrade_hack; + my $self = shift; my $old = @_ ? shift : ''; diff --git a/FS/FS/tax_rate.pm b/FS/FS/tax_rate.pm index 50279172d..d773ff534 100644 --- a/FS/FS/tax_rate.pm +++ b/FS/FS/tax_rate.pm @@ -433,7 +433,9 @@ sub taxline { my $maxtype = $self->maxtype || 0; if ($maxtype != 0 && $maxtype != 1 - && $maxtype != 14 && $maxtype != 15) { + && $maxtype != 14 && $maxtype != 15 + && $maxtype != 18 # sigh + ) { return $self->_fatal_or_null( 'tax with "'. $self->maxtype_name. '" threshold' ); |