'sec_granularity', 'int', '', '', '', '',
'ratetimenum', 'int', 'NULL', '', '', '',
'classnum', 'int', 'NULL', '', '', '',
+ 'cdrtypenum', 'int', 'NULL', '', '', '',
],
'primary_key' => 'ratedetailnum',
'unique' => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
=item cdrtypenum - primary key
-=item typename - CDR type name
+=item cdrtypename - CDR type name
=back
my $error =
$self->ut_numbern('cdrtypenum')
- || $self->ut_text('typename')
+ || $self->ut_text('cdrtypename')
;
return $error if $error;
my $disable_tollfree = $self->option('disable_tollfree');
my $ignore_unrateable = $self->option('ignore_unrateable', 'Hush!');
my $use_duration = $self->option('use_duration');
- my $region_group = ($rating_method eq 'prefix' && $self->option('min_included') > 0);
+ my $region_group = ($rating_method eq 'prefix' && ($self->option('min_included') || 0) > 0);
my $region_group_included_min = $region_group ? $self->option('min_included') : 0;
my $output_format = $self->option('output_format', 'Hush!')
$rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode,
'phonenum' => $number,
'weektime' => $weektime,
+ 'cdrtypenum' => $cdr->cdrtypenum,
});
if ( $rate_detail ) {
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
+ my $minutes = $granularity ? ($seconds / 60) : 1;
$charge = sprintf('%.4f', ( $self->option('min_charge') * $minutes )
+ 0.0000000001 ); #so 1.00005 rounds to 1.0001
# choose next rate_detail
$rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode,
'phonenum' => $number,
- 'weektime' => $etime })
+ 'weektime' => $etime,
+ 'cdrtypenum' => $cdr->cdrtypenum })
if($seconds_left);
# we have now moved forward to $etime
$weektime = $etime;
the given destination.
Destination can be specified as an FS::rate_detail object or regionnum
-(see L<FS::rate_detail>), or as a hashref with two keys: I<countrycode>
-and I<phonenum>.
+(see L<FS::rate_detail>), or as a hashref containing the following keys:
-An optional third key, I<weektime>, will return a timed rate (one with
-a non-null I<ratetimenum>) if one exists for a call at that time. If
-no matching timed rate exists, the non-timed rate will be returned.
+=over 2
+
+=item I<countrycode> - required.
+
+=item I<phonenum> - required.
+
+=item I<weektime> - optional. Specifies a time in seconds from the start
+of the week, and will return a timed rate (one with a non-null I<ratetimenum>)
+if one exists at that time. If not, returns a non-timed rate.
+
+=item I<cdrtypenum> - optional. Specifies a value for the cdrtypenum
+field, and will return a rate matching that, if one exists. If not, returns
+a rate with null cdrtypenum.
=cut
sub dest_detail {
my $self = shift;
- my $regionnum;
- my $weektime;
+ my( $regionnum, $weektime, $cdrtypenum );
if ( ref($_[0]) eq 'HASH' ) {
my $countrycode = $_[0]->{'countrycode'};
my $phonenum = $_[0]->{'phonenum'};
$weektime = $_[0]->{'weektime'};
+ $cdrtypenum = $_[0]->{'cdrtypenum'} || '';
#find a rate prefix, first look at most specific, then fewer digits,
# finally trying the country code only
$regionnum = $rate_prefix->regionnum;
- #$rate_region = $rate_prefix->rate_region;
-
} else {
$regionnum = ref($_[0]) ? shift->regionnum : shift;
}
-
- if(!defined($weektime)) {
- return qsearchs( 'rate_detail',
- { 'ratenum' => $self->ratenum,
- 'dest_regionnum' => $regionnum,
- 'ratetimenum' => '',
- } );
+
+ my %hash = (
+ 'ratenum' => $self->ratenum,
+ 'dest_regionnum' => $regionnum,
+ );
+
+ # find all rates matching ratenum, regionnum, cdrtypenum
+ my @details = qsearch( 'rate_detail', {
+ %hash,
+ 'cdrtypenum' => $cdrtypenum
+ });
+ # find all rates maching ratenum, regionnum and null cdrtypenum
+ if ( !@details and $cdrtypenum ) {
+ @details = qsearch( 'rate_detail', {
+ %hash,
+ 'cdrtypenum' => ''
+ });
}
- else {
- my @details = grep { my $rate_time = $_->rate_time;
- $rate_time && $rate_time->contains($weektime) }
- qsearch( 'rate_detail',
- { 'ratenum' => $self->ratenum,
- 'dest_regionnum' => $regionnum, } );
- if(!@details) {
- # this may change at some point
- return $self->dest_detail($regionnum);
+ # find one of those matching weektime
+ if ( defined($weektime) ) {
+ my @exact = grep {
+ my $rate_time = $_->rate_time;
+ $rate_time && $rate_time->contains($weektime)
+ } @details;
+ if ( @exact == 1 ) {
+ return $exact[0];
}
- elsif(@details == 1) {
- return $details[0];
- }
- else {
- die "overlapping rate_detail times (region $regionnum, time $weektime)\n";
+ elsif ( @exact > 1 ) {
+ die "overlapping rate_detail times (region $regionnum, time $weektime)\n"
}
+ # else @exact == 0
+ }
+ # if not found or there is no weektime, find one matching null weektime
+ foreach (@details) {
+ return $_ if $_->ratetimenum eq '';
}
+ # found nothing
+ return;
}
=item rate_detail
=item ratetimenum - rating time period (see L<FS::rate_time) if any
+=item cdrtypenum - CDR type (see L<FS::cdr_type>) if any for this rate
+
=back
=head1 METHODS
$usage_class ? $usage_class->classname : '';
}
+=item cdrtypename
+
+Returns the name of the CDR type (see L<FS::cdr_type) associated with this
+rate, if there is one. If not, returns the cdrtypenum itself. This will
+only return an empty string if cdrtypenum is NULL.
+
+=cut
+
+sub cdrtypename {
+ my $self = shift;
+ my $cdrtypenum = $self->cdrtypenum or return '';
+ my $cdr_type = qsearchs('cdr_type', { cdrtypenum => $cdrtypenum });
+ return $cdr_type ? $cdr_type->cdrtypename : $cdrtypenum;
+}
=back
<% include( '/search/elements/search.html',
- 'disable_download' => 1,
+ 'really_disable_download' => 1,
'disable_nonefound' => 1,
@_,
)
$p.'browse/rate_region.html',
'Time Periods' =>
$p.'browse/rate_time.html',
+ 'CDR Types' =>
+ $p.'edit/cdr_type.cgi',
],
'html_init' => $html_init,
'name' => 'rate plans',
'header' => [ '#', 'Rate plan', 'Rates' ],
'fields' => [ 'ratenum', 'ratename', $rates_sub ],
'links' => [ $link, $link, '' ],
+ 'really_disable_download' => 1
)
%>
<%once>
my $rate = shift;
my $ratenum = $rate->ratenum;
- qq( <FORM METHOD="GET" ACTION="${p}browse/rate_detail.html">
+ qq( <FORM METHOD="GET" ACTION="${p}edit/rate.cgi">
<INPUT TYPE="hidden" NAME="ratenum" VALUE="$ratenum">
<SELECT NAME="countrycode" onChange="this.form.submit();">
<OPTION SELECTED>Select Country Code
my $count_query = 'SELECT COUNT(*) FROM rate';
-my $link = [ $p.'edit/rate.cgi?', 'ratenum' ];
+my $link = [ $p.'edit/rate.cgi?ratenum=', 'ratenum' ];
</%once>
<%init>
+++ /dev/null
-<% include('/elements/init_overlib.html') %>
-<% include('/elements/header.html',$title) %>
-<% include('/elements/menubar.html',@menubar) %>
-<% include('/edit/elements/rate_detail.html',
- 'ratenum' => $ratenum,
- 'countrycode' => $countrycode,
-) %>
-<% include('/elements/footer.html') %>
-
-<%once>
-
-my $conf = new FS::Conf;
-my $money_char = $conf->config('money_char') || '$';
-
-my @menubar = ( 'Rate plans' => $p.'browse/rate.cgi',
- 'Regions and Prefixes' => $p.'browse/rate_region.html',
- 'Time Periods' => $p.'browse/rate_time.html',
- );
-
-</%once>
-<%init>
-
-die "access denied"
- unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
-
-$cgi->param('ratenum') =~ /^(\d+)$/ or die "unparsable ratenum";
-my $ratenum = $1;
-my $rate = qsearchs('rate', { 'ratenum' => $ratenum } )
- or die "unknown ratenum $ratenum";
-my $ratename = $rate->ratename;
-my $title = "$ratename rates";
-
-my $where;
-my $countrycode = '';
-if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) {
- $countrycode = $1;
- $title .= " for +$countrycode";
-}
-
-</%init>
my $edit_url = $p.'edit/rate_region.cgi';
-my $link = [ "$edit_url?", 'regionnum' ];
+my $link = [ "$edit_url?regionnum=", 'regionnum' ];
my $html_init =
'Regions and prefixes for VoIP and call billing.<BR><BR>'.
--- /dev/null
+<% include('/elements/header.html', { title => 'CDR Types' } ) %>
+<% include('/elements/menubar.html', 'Rate plans' => "${p}browse/rate.cgi" ) %>
+<BR><% include('/elements/error.html') %>
+<BR>
+CDR types define types of phone usage for billing, such as voice
+calls and SMS messages. Each CDR type must have a set of rates
+configured in the rate tables.
+<BR>
+<FORM METHOD="POST" ACTION="<% "${p}edit/process/cdr_type.cgi" %>">
+<% include('/elements/auto-table.html',
+ 'header' => [ 'Type#', 'Name' ],
+ 'fields' => [ qw( cdrtypenum cdrtypename ) ],
+ 'data' => \@data,
+ ) %>
+<INPUT TYPE="submit" VALUE="Apply changes"> </FORM> <BR>
+<% include('/elements/footer.html') %>
+<%init>
+
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my @data = (
+ map { [ $_->cdrtypenum, $_->cdrtypename ] }
+ qsearch({
+ 'table' => 'cdr_type',
+ 'hashref' => {},
+ 'order_by' => 'ORDER BY cdrtypenum ASC'
+ })
+);
+
+</%init>
--- /dev/null
+% if ( $error ) {
+% $cgi->param('error', $error);
+<% $cgi->redirect(popurl(2). "cdr_type.cgi?". $cgi->query_string ) %>
+% } else {
+<% $cgi->redirect(popurl(2). "cdr_type.cgi" ) %>
+% }
+<%init>
+my $error = '';
+die "access denied"
+ unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
+
+my %vars = $cgi->Vars;
+warn Dumper(\%vars)."\n";
+
+my %old = map { $_->cdrtypenum => $_ } qsearch('cdr_type', {});
+
+my @new;
+foreach ( keys(%vars) ) {
+ my ($i) = /^cdrtypenum(\d+)$/ or next;
+ my $cdrtypenum = $vars{"cdrtypenum$i"} or next;
+ my $cdrtypename = $vars{"cdrtypename$i"} or next;
+ # don't delete unchanged records
+ if ( $old{$i} and $old{$i}->cdrtypename eq $cdrtypename ) {
+ delete $old{$i};
+ next;
+ }
+ push @new, FS::cdr_type->new({
+ 'cdrtypenum' => $cdrtypenum,
+ 'cdrtypename' => $cdrtypename,
+ });
+}
+foreach (values(%old)) {
+ $error = $_->delete;
+ last if $error;
+}
+if(!$error) {
+ foreach (@new) {
+ $error = $_->insert;
+ last if $error;
+ }
+}
+</%init>
% if($rate->ratenum) {
<BR><BR><FONT SIZE="+2">Rates in this plan</FONT>
+% if ( my $select_cdr_type = include('/elements/select-cdr_type.html',
+% 'curr_value' => $cdrtypenum,
+% 'onchange' => 'form.submit();',
+% 'name_col' => 'cdrtypename',
+% 'value_col' => 'cdrtypenum',
+% 'empty_label' => '(default)',
+% ) ) {
+<FORM ACTION="<%$cgi->url%>" METHOD="GET">
+<INPUT TYPE="hidden" NAME="ratenum" VALUE="<% $rate->ratenum %>">
+<INPUT TYPE="hidden" NAME="countrycode" VALUE="<% $countrycode %>">
+<FONT SIZE="+1">Usage type: <% $select_cdr_type %></FONT>
+</FORM>
+% }
+
<% include('/edit/elements/rate_detail.html',
- 'ratenum' => $rate->ratenum
+ 'ratenum' => $rate->ratenum,
+ 'countrycode' => $countrycode,
+ 'cdrtypenum' => $cdrtypenum,
) %>
% }
unless $FS::CurrentUser::CurrentUser->access_right('Configuration');
my $rate;
-if ( $cgi->keywords ) {
- my($query) = $cgi->keywords;
- $query =~ /^(\d+)$/;
+if ( $cgi->param('ratenum') ) {
+ $cgi->param('ratenum') =~ /^(\d+)$/;
$rate = qsearchs( 'rate', { 'ratenum' => $1 } );
} else { #adding
$rate = new FS::rate {};
}
my $action = $rate->ratenum ? 'Edit' : 'Add';
+my $countrycode = '';
+if ( $cgi->param('countrycode') =~ /^(\d+)$/ ) {
+ $countrycode = $1;
+}
+
+my $cdrtypenum = '';
+if ( $cgi->param('cdrtypenum') =~ /^(\d+)$/ ) {
+ $cdrtypenum = $1;
+}
</%init>
{ field=>'orig_regionnum', type=>'hidden', },
{ field=>'dest_regionnum', type=>'hidden', },
{ field=>'ratetimenum', type=>'hidden', },
+ { field=>'cdrtypenum', type=>'hidden', },
{ field=>'dest_regionname', type=>'fixed', },
{ field=>'dest_prefixes_short', type=>'fixed', },
{ field=>'rate_time_name', type=>'fixed', },
{ ratenum => $cgi->param('ratenum'),
dest_regionnum => $cgi->param('dest_regionnum'),
ratetimenum => $cgi->param('ratetimenum'),
+ cdrtypenum => $cgi->param('cdrtypenum'),
+ min_included => 0,
+ conn_charge => 0,
}
},
)
</TABLE>
-<BR><BR>
+<BR>
<INPUT TYPE="submit" VALUE="<% $rate_region->regionnum ? "Apply changes" : "Add region" %>">
</FORM>
%# rate plan info, if the region has been created yet
% if($rate_region->regionnum) {
-<BR>
-<BR>
-<FONT SIZE="+2">Rates in this region</FONT>
+<BR><BR><FONT SIZE="+2">Rates in this region</FONT>
+% if ( my $select_cdr_type = include('/elements/select-cdr_type.html',
+% 'curr_value' => $cdrtypenum,
+% 'onchange' => 'form.submit();',
+% 'name_col' => 'cdrtypename',
+% 'value_col' => 'cdrtypenum',
+% 'empty_label' => '(default)',
+% ) ) {
+<FORM ACTION="<%$cgi->url%>" METHOD="GET">
+<INPUT TYPE="hidden" NAME="regionnum" VALUE="<% $rate_region->regionnum %>">
+<FONT SIZE="+1">Usage type: <% $select_cdr_type %></FONT>
+</FORM>
+% }
<% include('/edit/elements/rate_detail.html',
- 'regionnum' => $rate_region->regionnum,
+ 'regionnum' => $rate_region->regionnum,
+ 'cdrtypenum' => $cdrtypenum,
) %>
% }
$rate_region = new FS::rate_region ( {
map { $_, scalar($cgi->param($_)) } fields('rate_region')
} );
-} elsif ( $cgi->keywords ) {
- my($query) = $cgi->keywords;
- $query =~ /^(\d+)$/ or die "unparsable regionnum";
+} elsif ( $cgi->param('regionnum') ) {
+ $cgi->param('regionnum') =~ /^(\d+)$/ or die "unparseable regionnum";
$rate_region = qsearchs( 'rate_region', { 'regionnum' => $1 } )
or die "unknown regionnum $1\n";
} else { #adding
unless $rate_prefix->countrycode eq $countrycode;
}
}
-
+my $cdrtypenum = '';
+if ( $cgi->param('cdrtypenum') =~ /^(\d+)$/ ) {
+ $cdrtypenum = $1;
+}
</%init>
<TR>
% my $col = 0;
% for ( $col = 0; $col < scalar @fields; $col++ ) {
-% my $id = $prefix . $fields[$col] . $row;
+% my $id = $prefix . $fields[$col];
+% # don't suffix rownum in the final, blank row
+% $id .= $row if $row < (scalar @data) - 1;
<TD>
% my @o = @{ $select[$col] };
% if( @o ) {
var row = <% $prefix %>table.insertRow(-1);
var cells = <% $prefix %>_blank.cells;
for (i=0; i<cells.length; i++) {
- row.appendChild(cells[i].cloneNode(true));
+ var node = row.appendChild(cells[i].cloneNode(true));
+ var input = node.children[0];
+ input.id = input.id + row.sectionRowIndex;
+ input.name = input.name + row.sectionRowIndex;
}
<% $prefix %>rownum++;
}
--- /dev/null
+% if ( FS::Record->scalar_sql('SELECT COUNT(*) FROM cdr_type') ) {
+<% include( '/elements/select-table.html',
+ 'table' => 'cdr_type',
+ 'name_col' => 'cdrtypenum',
+ 'empty_label' => '(none)',
+ @_
+ )
+%>
+% }
+<%init>
+</%init>