rate selection by CDR type, RT#10991
authormark <mark>
Fri, 11 Feb 2011 23:59:29 +0000 (23:59 +0000)
committermark <mark>
Fri, 11 Feb 2011 23:59:29 +0000 (23:59 +0000)
16 files changed:
FS/FS/Schema.pm
FS/FS/cdr_type.pm
FS/FS/part_pkg/voip_cdr.pm
FS/FS/rate.pm
FS/FS/rate_detail.pm
httemplate/browse/elements/browse.html
httemplate/browse/rate.cgi
httemplate/browse/rate_detail.html [deleted file]
httemplate/browse/rate_region.html
httemplate/edit/cdr_type.cgi [new file with mode: 0644]
httemplate/edit/process/cdr_type.cgi [new file with mode: 0644]
httemplate/edit/rate.cgi
httemplate/edit/rate_detail.html
httemplate/edit/rate_region.cgi
httemplate/elements/auto-table.html
httemplate/elements/select-cdr_type.html [new file with mode: 0644]

index 71403b4..040f6df 100644 (file)
@@ -2489,6 +2489,7 @@ sub tables_hashref {
         'sec_granularity', 'int',     '',     '', '', '', 
         'ratetimenum',     'int', 'NULL',     '', '', '',
         'classnum',        'int', 'NULL',     '', '', '', 
+        'cdrtypenum',      'int', 'NULL',     '', '', '',
       ],
       'primary_key' => 'ratedetailnum',
       'unique'      => [ [ 'ratenum', 'orig_regionnum', 'dest_regionnum' ] ],
index e258bf8..d16b85c 100644 (file)
@@ -34,7 +34,7 @@ FS::Record.  The following fields are currently supported:
 
 =item cdrtypenum - primary key
 
-=item typename - CDR type name
+=item cdrtypename - CDR type name
 
 
 =back
@@ -98,7 +98,7 @@ sub check {
 
   my $error = 
     $self->ut_numbern('cdrtypenum')
-    || $self->ut_text('typename')
+    || $self->ut_text('cdrtypename')
   ;
   return $error if $error;
 
index fea38c1..800f929 100644 (file)
@@ -333,7 +333,7 @@ sub calc_usage {
   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!')
@@ -502,6 +502,7 @@ sub calc_usage {
           $rate_detail = $rate->dest_detail({ 'countrycode' => $countrycode,
                                               'phonenum'    => $number,
                                               'weektime'    => $weektime,
+                                              'cdrtypenum'  => $cdr->cdrtypenum,
                                             });
 
           if ( $rate_detail ) {
@@ -568,10 +569,7 @@ sub calc_usage {
           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
 
@@ -692,7 +690,8 @@ sub calc_usage {
             # 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;
index f30e4c7..02d8250 100644 (file)
@@ -276,25 +276,34 @@ specificed destination, or the empty string if no rate can be found for
 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
@@ -315,36 +324,47 @@ sub dest_detail {
 
     $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
index 7b90452..918134a 100644 (file)
@@ -57,6 +57,8 @@ inherits from FS::Record.  The following fields are currently supported:
 
 =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
@@ -234,6 +236,20 @@ sub classname {
   $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
 
index 513c2c4..9099d65 100644 (file)
@@ -1,5 +1,5 @@
 <% include( '/search/elements/search.html',
-               'disable_download'  => 1,
+               'really_disable_download'  => 1,
                'disable_nonefound' => 1,
                @_,
            )
index c6cfec0..0c425a5 100644 (file)
@@ -4,6 +4,8 @@
                                    $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',
@@ -15,6 +17,7 @@
               'header'      => [ '#',       'Rate plan', 'Rates'    ],
               'fields'      => [ 'ratenum', 'ratename',  $rates_sub ],
               'links'       => [ $link,     $link,       ''         ],
+              'really_disable_download' => 1
           )
 %>
 <%once>
@@ -27,7 +30,7 @@ my $rates_sub = sub {
   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
@@ -55,7 +58,7 @@ my $html_init =
 
 my $count_query = 'SELECT COUNT(*) FROM rate';
 
-my $link = [ $p.'edit/rate.cgi?', 'ratenum' ];
+my $link = [ $p.'edit/rate.cgi?ratenum=', 'ratenum' ];
 
 </%once>
 <%init>
diff --git a/httemplate/browse/rate_detail.html b/httemplate/browse/rate_detail.html
deleted file mode 100644 (file)
index aef5505..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-<% 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>
index b7d9589..b958894 100644 (file)
@@ -23,7 +23,7 @@
 
 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>'.
diff --git a/httemplate/edit/cdr_type.cgi b/httemplate/edit/cdr_type.cgi
new file mode 100644 (file)
index 0000000..5d2c662
--- /dev/null
@@ -0,0 +1,31 @@
+<% 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>
diff --git a/httemplate/edit/process/cdr_type.cgi b/httemplate/edit/process/cdr_type.cgi
new file mode 100644 (file)
index 0000000..b661de7
--- /dev/null
@@ -0,0 +1,42 @@
+% 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>
index 13717dc..1abfb0d 100644 (file)
@@ -26,8 +26,24 @@ Rate plan
 
 % 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,
 ) %>
 % }
 
@@ -39,13 +55,21 @@ die "access denied"
   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>
index ec04e4c..1937820 100644 (file)
@@ -18,6 +18,7 @@
                    { 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',  },
@@ -49,6 +50,9 @@
         { ratenum        => $cgi->param('ratenum'),
           dest_regionnum => $cgi->param('dest_regionnum'),
           ratetimenum    => $cgi->param('ratetimenum'),
+          cdrtypenum     => $cgi->param('cdrtypenum'),
+          min_included   => 0,
+          conn_charge    => 0,
         }
       },
    )
index cae3003..367bbaf 100644 (file)
 
 </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,
 ) %>
 % }
 
@@ -68,9 +79,8 @@ if ( $cgi->param('error') ) {
   $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
@@ -91,5 +101,8 @@ if ( @rate_prefix ) {
       unless $rate_prefix->countrycode eq $countrycode;
   }
 }
-
+my $cdrtypenum = '';
+if ( $cgi->param('cdrtypenum') =~ /^(\d+)$/ ) {
+  $cdrtypenum = $1;
+}
 </%init>
index 9c7dfd0..4922274 100644 (file)
@@ -51,7 +51,9 @@ Values will be passed through as "mytable_id1", etc.
   <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 ) {
@@ -117,7 +119,10 @@ $val eq $data[$row][$col] ? ' SELECTED' : ''%>><% shift @o %></OPTION>
     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++;
   }
diff --git a/httemplate/elements/select-cdr_type.html b/httemplate/elements/select-cdr_type.html
new file mode 100644 (file)
index 0000000..9d26089
--- /dev/null
@@ -0,0 +1,11 @@
+% 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>