diff options
| author | Mark Wells <mark@freeside.biz> | 2013-02-19 14:34:28 -0800 | 
|---|---|---|
| committer | Mark Wells <mark@freeside.biz> | 2013-02-19 14:34:28 -0800 | 
| commit | 7b3c18f90c6677884bd39d29f011ea59a257184b (patch) | |
| tree | 0f3f68a6831f6dbe018a209615325d690fe9cd25 | |
| parent | c0fb4cbb552047079c3af5d3496fafd4f1b11c93 (diff) | |
better UI for package report classes, #13057
| -rw-r--r-- | FS/FS/AccessRight.pm | 2 | ||||
| -rwxr-xr-x | httemplate/browse/part_pkg.cgi | 68 | ||||
| -rw-r--r-- | httemplate/edit/bulk-part_pkg.html | 74 | ||||
| -rw-r--r-- | httemplate/edit/process/bulk-part_pkg.html | 30 | ||||
| -rw-r--r-- | httemplate/elements/checkbox-tristate.html | 78 | ||||
| -rwxr-xr-x | httemplate/search/477.html | 5 | ||||
| -rwxr-xr-x | httemplate/search/477partV.html | 4 | ||||
| -rw-r--r-- | httemplate/search/cdr.html | 35 | ||||
| -rw-r--r-- | httemplate/search/elements/checkbox-foot.html | 86 | ||||
| -rwxr-xr-x | httemplate/search/report_477.html | 4 | 
10 files changed, 351 insertions, 35 deletions
diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index 78e609021..12201a7a9 100644 --- a/FS/FS/AccessRight.pm +++ b/FS/FS/AccessRight.pm @@ -341,6 +341,8 @@ tie my %rights, 'Tie::IxHash',      'Edit package definitions',      { rightname=>'Edit global package definitions', global=>1 }, +    'Bulk edit package definitions', +      'Edit billing events',      { rightname=>'Edit global billing events', global=>1 }, diff --git a/httemplate/browse/part_pkg.cgi b/httemplate/browse/part_pkg.cgi index dd5edba82..f30bab436 100755 --- a/httemplate/browse/part_pkg.cgi +++ b/httemplate/browse/part_pkg.cgi @@ -1,6 +1,7 @@  <% include( 'elements/browse.html',                   'title'                 => 'Package Definitions',                   'html_init'             => $html_init, +                 'html_form'             => $html_form,                   'html_posttotal'        => $html_posttotal,                   'name'                  => 'package definitions',                   'disableable'           => 1, @@ -20,6 +21,8 @@                   'fields'                => \@fields,                   'links'                 => \@links,                   'align'                 => $align, +                 'html_init'             => $html_init, +                 'html_foot'             => $html_foot,               )  %>  <%init> @@ -33,6 +36,7 @@ my $acl_edit_global = $curuser->access_right($edit_global);  my $acl_config      = $curuser->access_right('Configuration'); #to edit services                                                                 #and agent types                                                                 #and bulk change +my $acl_edit_bulk   = $curuser->access_right('Bulk edit package definitions');  die "access denied"    unless $acl_edit || $acl_edit_global; @@ -119,9 +123,7 @@ $select = "  "; -my $html_init; -#unless ( $cgi->param('active') ) { -  $html_init = qq! +my $html_init = qq!      One or more service definitions are grouped together into a package       definition and given pricing information.  Customers purchase packages      rather than purchase services directly.<BR><BR> @@ -133,7 +135,6 @@ my $html_init;      </FORM>      <BR><BR>    !; -#}  $cgi->param('dummy', 1); @@ -402,6 +403,10 @@ if ( $taxclasses ) {    $align .= 'l';  } +# make a table of report class optionnames =>  the actual  +my %report_optionname_name = map { 'report_option_'.$_->num, $_->name } +  qsearch('part_pkg_report_option', { disabled => '' }); +  push @header, 'Plan options',                'Services';                #'Service', 'Quan', 'Primary'; @@ -412,8 +417,18 @@ push @fields,                      if ( $part_pkg->plan ) {                        my %options = $part_pkg->options; - -                      [ map {  +                      # gather any options that are really report options, +                      # convert them to their user-friendly names, +                      # and sort them (I think?) +                      my @report_options = +                        sort { $a cmp $b } +                        map { $report_optionname_name{$_} } +                        grep { $options{$_} +                               and exists($report_optionname_name{$_}) } +                        keys %options; + +                      my @rows = ( +                        map {                                 [                                  { 'data'  => "$_: ",                                    'align' => 'right', @@ -424,11 +439,30 @@ push @fields,                                ];                              }                          grep { $options{$_} =~ /\S/ }  -                        grep { $_ !~ /^(setup|recur)_fee$/ } +                        grep { $_ !~ /^(setup|recur)_fee$/  +                               and $_ !~ /^report_option_\d+$/ }                          keys %options -                      ]; +                      ); +                      if ( @report_options ) { +                        push @rows, +                          [ { 'data'  => 'Report classes', +                              'align' => 'center', +                              'style' => 'font-weight: bold', +                              'colspan' => 2 +                            } ]; +                        foreach (@report_options) { +                          push @rows, [ +                            { 'data'  => $_, +                              'align' => 'center', +                              'colspan' => 2 +                            } +                          ]; +                        } # foreach @report_options +                      } # if @report_options + +                      return \@rows; -                    } else { +                    } else { # should never happen...                        [ map { [                                  { 'data'  => uc($_), @@ -506,4 +540,20 @@ $extra_count = ( $count_extra_sql ? ' AND ' : ' WHERE ' ). $extra_count    if $extra_count;  my $count_query = "SELECT COUNT(*) FROM part_pkg $count_extra_sql $extra_count"; +my $html_form = ''; +my $html_foot = ''; +if ( $acl_edit_bulk ) { +  # insert a checkbox column +  push @header, ''; +  push @fields, sub { +    '<INPUT TYPE="checkbox" NAME="pkgpart" VALUE=' . $_[0]->pkgpart .'>'; +  }; +  push @links, ''; +  $align .= 'c'; +  $html_form = qq!<FORM ACTION="${p}edit/bulk-part_pkg.html" METHOD="POST">!; +  $html_foot = include('/search/elements/checkbox-foot.html', +      submit  => 'edit report classes', # for now it's only report classes +  ) . '</FORM>'; +} +  </%init> diff --git a/httemplate/edit/bulk-part_pkg.html b/httemplate/edit/bulk-part_pkg.html new file mode 100644 index 000000000..751bf7e5d --- /dev/null +++ b/httemplate/edit/bulk-part_pkg.html @@ -0,0 +1,74 @@ +<& /elements/header.html, 'Edit package report classes' &> +%# change that title if we add any other editing controls + +%# this should be centralized somewhere +<STYLE TYPE="text/css"> +.row0 { background-color: #eeeeee; } +.row1 { background-color: #ffffff; } +</STYLE> + +<FORM ACTION="process/bulk-part_pkg.html" METHOD="POST"> +<DIV> +The following packages will be changed:<BR> +% foreach my $pkgpart (sort keys(%part_pkg)) { +<INPUT TYPE="hidden" NAME="pkgpart" VALUE="<% $pkgpart %>"> +<% $part_pkg{$pkgpart}->pkg_comment %><BR> +% } +</DIV> +<BR> +<& /elements/table-grid.html &>\ +<& /elements/tr-justtitle.html, value => mt('Report classes') &> +% my $row = 0; +% foreach my $num (sort keys %report_class) { +  <TR CLASS="row<%$row % 2%>"> +    <TD> +%   if ( defined $initial_state{$num} ) { +      <& /elements/checkbox.html, +            field => 'report_option_'.$num, +            value => 1, +            curr_value => $initial_state{$num} +      &> +%   } else { +%     # needs to be a tristate so that you can say "don't change it" +      <& /elements/checkbox-tristate.html, field => 'report_option_'.$num &> +%   } +    </TD> +    <TD><% $report_class{$num}->name %></TD> +  </TR> +%   $row++; +% } +</TABLE> +<BR> +<INPUT TYPE="submit"> +</FORM> +<& /elements/footer.html &> +<%init> +die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Bulk edit package definitions'); +my @pkgparts = $cgi->param('pkgpart') +  or die "no package definitions selected"; + +my %part_pkg = map { $_ => FS::part_pkg->by_key($_) } @pkgparts; +my %part_pkg_option = map { $_ => { $part_pkg{$_}->options } } @pkgparts; +my %report_class = map { $_->num => $_ } +  qsearch('part_pkg_report_option', { disabled => '' }); + +my %initial_state; +foreach my $num (keys %report_class) { +  my $yes = 0; +  my $no = 0; +  foreach my $option (values %part_pkg_option) { +    if ( $option->{"report_option_$num"} ) { +      $yes = 1; +    } else { +      $no = 1; +    } +  } +  if ( $yes and $no ) { +    $initial_state{$num} = undef; +  } elsif ( $yes ) { +    $initial_state{$num} = 1; +  } elsif ( $no ) { +    $initial_state{$num} = 0; +  } # else, uh, you didn't provide any pkgparts +} +</%init> diff --git a/httemplate/edit/process/bulk-part_pkg.html b/httemplate/edit/process/bulk-part_pkg.html new file mode 100644 index 000000000..4775a9334 --- /dev/null +++ b/httemplate/edit/process/bulk-part_pkg.html @@ -0,0 +1,30 @@ +% if ( $error ) { +%  $cgi->param('error', $error); +<% $cgi->redirect(popurl(3).'/edit/bulk-part_pkg.cgi?', $cgi->query_string) %> +% } else { +<% $cgi->redirect(popurl(3).'/browse/part_pkg.cgi') %> +% } +<%init> +die "access denied" unless $FS::CurrentUser::CurrentUser->access_right('Bulk edit package definitions'); + +my @pkgparts = $cgi->param('pkgpart') +  or die "no package definitions selected"; + +my %changes; +foreach my $param (grep { /^report_option_\d+$/ } $cgi->param) { +  if ( length($cgi->param($param)) ) { +    if ( $cgi->param($param) == 1 ) { +      $changes{$param} = 1; +    } else { +      $changes{$param} = ''; +    } +  } +} + +my $error; +foreach my $pkgpart (@pkgparts) { +  my $part_pkg = FS::part_pkg->by_key($pkgpart); +  my %options = ( $part_pkg->options, %changes ); +  $error ||= $part_pkg->replace( options => \%options ); +} +</%init> diff --git a/httemplate/elements/checkbox-tristate.html b/httemplate/elements/checkbox-tristate.html new file mode 100644 index 000000000..4c26ed74e --- /dev/null +++ b/httemplate/elements/checkbox-tristate.html @@ -0,0 +1,78 @@ +<%doc> +A tristate checkbox (with three values: true, false, and null). +Internally, this creates a checkbox, coupled via javascript to a hidden +field that actually contains the value.  For now, the only values these +can have are 1, 0, and empty.  Clicking the checkbox cycles between them. +</%doc> +<%shared> +my $init = 0; +</%shared> +% if ( !$init ) { +%   $init = 1; +<SCRIPT TYPE="text/javascript"> +function tristate_onclick() { +  var checkbox = this; +  var input = checkbox.input; +  if ( input.value == "" ) { +    input.value = "0"; +    checkbox.checked = false; +    checkbox.indeterminate = false; +  } else if ( input.value == "0" ) { +    input.value = "1"; +    checkbox.checked = true; +    checkbox.indeterminate = false; +  } else if ( input.value == "1" ) { +    input.value = ""; +    checkbox.checked = true; +    checkbox.indeterminate = true +  } +} + +var tristates = []; +var tristate_boxes = []; +window.onload = function() { // don't do this until all of the checkboxes exist +%#  tristates = document.getElementsByClassName('tristate'); # curse you, IE8 +  var all_inputs = document.getElementsByTagName('input'); +  for (var i=0; i < all_inputs.length; i++) { +    if ( all_inputs[i].className == 'tristate' ) { +      tristates.push(all_inputs[i]); +    } +  } +  for (var i=0; i < tristates.length; i++) { +    tristate_boxes[i] = +      document.getElementById('checkbox_' + tristates[i].name); +    // make sure they can find each other +    tristate_boxes[i].input = tristates[i]; +    tristates[i].checkbox = tristate_boxes[i]; +    // set event handler +    tristate_boxes[i].onclick = tristate_onclick; +    // set initial value +    if ( tristates[i].value == "" ) { +      tristate_boxes[i].indeterminate = true +    } +    if ( tristates[i].value != "0" ) { +      tristate_boxes[i].checked = true; +    } +  } +}; +</SCRIPT> +% } # end of $init +<INPUT TYPE="hidden" NAME="<% $opt{field} %>" +                     ID="<% $opt{id} %>" +                     VALUE="<% $curr_value %>" +                     CLASS="tristate"> +<INPUT TYPE="checkbox" ID="checkbox_<%$opt{field}%>" CLASS="partial"> +<%init> + +my %opt = @_; + +# might be useful but I'm not implementing it yet +#my $onchange = $opt{'onchange'} +#                 ? 'onChange="'. $opt{'onchange'}. '(this)"' +#                 : ''; + +$opt{'id'} ||= 'hidden_'.$opt{'field'}; +my $curr_value = $opt{curr_value}; +$curr_value = undef +  unless $curr_value eq '0' or $curr_value eq '1'; +</%init> diff --git a/httemplate/search/477.html b/httemplate/search/477.html index 6f5fcdf3b..04764c1da 100755 --- a/httemplate/search/477.html +++ b/httemplate/search/477.html @@ -97,6 +97,11 @@ for(my $i=0; $i < scalar(@part2b_row_option); $i++) {      &FS::Report::FCC_477::save_fcc477map("part2b_row_option_$i",$part2b_row_option[$i]);  } +my $part5_report_option = $cgi->param('part5_report_option'); +if ( $part5_report_option ) { +  FS::Report::FCC_477::save_fcc477map('part5_report_option', $part5_report_option); +} +  my $url_mangler = sub {    my $part = shift;    my $url = $cgi->url('-path_info' => 1, '-full' => 1); diff --git a/httemplate/search/477partV.html b/httemplate/search/477partV.html index 57f80e63e..91345eb01 100755 --- a/httemplate/search/477partV.html +++ b/httemplate/search/477partV.html @@ -32,8 +32,8 @@ for ( qw(agentnum magic state) ) {  }  $search_hash{'country'} = 'US';  $search_hash{'classnum'} = [ $cgi->param('classnum') ]; -$search_hash{report_option} = $cgi->param('partv_report_option') -  if $cgi->param('partv_report_option'); +$search_hash{report_option} = $cgi->param('part5_report_option') +  if $cgi->param('part5_report_option');  my $sql_query = FS::cust_pkg->search( { %search_hash,                                          #'fcc_line'    => 1, diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html index 531688a9c..2426d8aea 100644 --- a/httemplate/search/cdr.html +++ b/httemplate/search/cdr.html @@ -9,26 +9,8 @@                 'fields' => \@fields,                 'links' => \@links,                 'html_form'   => qq!<FORM NAME="cdrForm" ACTION="$p/misc/cdr.cgi" METHOD="POST">!, -               #false laziness w/queue.html -               'html_foot' => sub { -                                if ( $areboxes ) { -                                  '<BR><INPUT TYPE="button" VALUE="select all" onClick="setAll(true)">'. -                                  '<INPUT TYPE="button" VALUE="unselect all" onClick="setAll(false)">'. -                                  qq!<BR><INPUT TYPE="submit" NAME="action" VALUE="reprocess selected" onClick="return confirm('Are you sure you want to reprocess the selected CDRs?')">!. -                                  qq!<INPUT TYPE="submit" NAME="action" VALUE="delete selected" onClick="return confirm('Are you sure you want to delete the selected CDRs?')"><BR>!. -                                  '<SCRIPT TYPE="text/javascript">'. -                                  '  function setAll(setTo) { '. -                                  '    theForm = document.cdrForm;'. -                                  '    for (i=0,n=theForm.elements.length;i<n;i++)'. -                                  '      if (theForm.elements[i].name.indexOf("acctid") != -1)'. -                                  '        theForm.elements[i].checked = setTo;'. -                                  '  }'. -                                  '</SCRIPT>'; -                                } else { -                                  ''; -                                } -                              }, -              +               'html_foot' => $html_foot, +             )  &>  <%init> @@ -44,8 +26,6 @@ my $totalminutes_sub = sub {  my $conf = new FS::Conf; -my $areboxes = 0; -  my $title = 'Call Detail Records';  my $hashref = {}; @@ -344,7 +324,6 @@ my %links = (  @fields = map { exists($fields{$_}) ? $fields{$_} : $_ } @fields;  unshift @fields, sub {                         return '' unless $edit_data; -                       $areboxes = 1;                         my $cdr = shift;                         my $acctid = $cdr->acctid;                         qq!<INPUT NAME="acctid$acctid" TYPE="checkbox" VALUE="1">!; @@ -398,4 +377,14 @@ if ( $topmode ) {      $nototalminutes = 1;  } +my $html_foot = include('/search/elements/checkbox-foot.html', +  actions => [ +    { submit  => "reprocess selected", +      name    => "action", +      confirm => "Are you sure you want to reprocess the selected CDRs?" }, +    { submit  => "delete selected", +      name    => "action", +      confirm => "Are you sure you want to delete the selected CDRs?" }, +  ] +);  </%init> diff --git a/httemplate/search/elements/checkbox-foot.html b/httemplate/search/elements/checkbox-foot.html new file mode 100644 index 000000000..be1caab91 --- /dev/null +++ b/httemplate/search/elements/checkbox-foot.html @@ -0,0 +1,86 @@ +<%doc> +<& /elements/search.html, +  # options... +  html_foot => include('elements/checkbox-foot.html', +                  actions => [ +                    { label   => 'Edit selected packages', +                      action  => 'popup_package_edit()', +                    }, +                    { submit  => 'Delete selected packages', +                      confirm => 'Really delete these packages?' +                    }, +                  ], +                  filter        => '.name = "pkgpart"', # see below +               ), +&> + +This creates a footer for a search page containing a column of checkboxes. +Typically this is used to select several items from the search result and  +apply some change to all of them at once.  The footer always provides  +"select all" and "unselect all" buttons. + +"actions" is an arrayref of action buttons to show.  Each element of the +array is a hashref of either: + +- "submit" and, optionally, "confirm".  Creates a submit button.  The value  +of "submit" becomes the "value" property of the button (and thus its label). +If "confirm" is specified, the button will have an onclick handler that  +displays the value of "confirm" in a popup message box and asks the user to  +confirm the choice. + +- "onclick" and "label".  Creates a non-submit button that executes the  +Javascript code in "onclick".  "label" is used as the text of the button. + +If you want only a single action, you can forget the arrayref-of-hashrefs +business and just put "submit" and "confirm" (or "onclick" and "label")  +elements in the argument list. + +"filter" is a javascript expression to limit which checkboxes are included in +the "select/unselect all" actions.  By default, any input with type="checkbox" +will be included.  If this option is given, it will be evaluated with the  +HTML node in a variable named "obj".  The expression should return true or +false. + +</%doc> +<DIV ID="checkbox_footer" STYLE="display:block"> +<INPUT TYPE="button" VALUE="<% emt('select all') %>" onclick="setAll(true)"> +<INPUT TYPE="button" VALUE="<% emt('unselect all') %>" onclick="setAll(false)"> +<BR> +% foreach my $action (@$actions) { +%   if ( $action->{onclick} ) { +<INPUT TYPE="button" <% $action->{name} %> onclick="<% $opt{onclick} %>"\ +  VALUE="<% $action->{label} |h%>"> +%   } elsif ( $action->{submit} ) { +<INPUT TYPE="submit" <% $action->{name} %> <% $action->{confirm} %>\ +  VALUE="<% $action->{submit} |h%>"> +%   } # else do nothing +% } #foreach +</DIV> +<SCRIPT> +var checkboxes = []; +var inputs = document.getElementsByTagName('input'); +for (var i = 0; i < inputs.length; i++) { +  var obj = inputs[i]; +  if ( obj.type == "checkbox" && <% $filter %> ) { +    checkboxes.push(obj); +  } +} +%# avoid the need for "$areboxes" late-evaluation hackery +if ( checkboxes.length == 0 ) { +  document.getElementById('checkbox_footer').style.display = 'none'; +} +function setAll(setTo) { +  for (var i = 0; i < checkboxes.length; i++) { +    checkboxes[i].checked = setTo; +  } +} +</SCRIPT> +<%init> +my %opt = @_; +my $actions = $opt{'actions'} || [ \%opt ]; +foreach (@$actions) { +  $_->{confirm} &&= qq!onclick="return confirm('! . $_->{confirm} . qq!')"!; +  $_->{name} &&= qq!NAME="! . $_->{name} . qq!"!; +} +my $filter = $opt{filter} || 'true'; +</%init> diff --git a/httemplate/search/report_477.html b/httemplate/search/report_477.html index 24fee0527..12918a148 100755 --- a/httemplate/search/report_477.html +++ b/httemplate/search/report_477.html @@ -228,7 +228,9 @@                       'table'        => 'part_pkg_report_option',                       'name_col'     => 'name',                       'hashref'      => { 'disabled' => '' }, -                     'element_name' => 'partv_report_option', +                     'element_name' => 'part5_report_option', +                     'curr_value'   => +                            FS::Report::FCC_477::restore_fcc477map("part5_report_option"),                   )              %>          </TD>  | 
