From 6a0458ad05422b664918b0c7a18b456c022909ba Mon Sep 17 00:00:00 2001 From: Mark Wells Date: Thu, 14 Feb 2013 19:16:27 -0800 Subject: [PATCH] better UI for package report classes, #13507 --- FS/FS/AccessRight.pm | 2 + httemplate/browse/part_pkg.cgi | 68 ++++++++++++++++++--- httemplate/edit/bulk-part_pkg.html | 74 +++++++++++++++++++++++ httemplate/edit/process/bulk-part_pkg.html | 30 ++++++++++ httemplate/elements/checkbox-tristate.html | 78 ++++++++++++++++++++++++ httemplate/search/cdr.html | 35 ++++------- httemplate/search/elements/checkbox-foot.html | 86 +++++++++++++++++++++++++++ 7 files changed, 341 insertions(+), 32 deletions(-) create mode 100644 httemplate/edit/bulk-part_pkg.html create mode 100644 httemplate/edit/process/bulk-part_pkg.html create mode 100644 httemplate/elements/checkbox-tristate.html create mode 100644 httemplate/search/elements/checkbox-foot.html diff --git a/FS/FS/AccessRight.pm b/FS/FS/AccessRight.pm index c0bd8d163..3d58d208c 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 5b19a309b..5dee5b8d1 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, @@ -21,6 +22,8 @@ 'links' => \@links, 'align' => $align, 'link_field' => 'pkgpart', + 'html_init' => $html_init, + 'html_foot' => $html_foot, ) %> <%init> @@ -34,6 +37,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; @@ -131,9 +135,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.

@@ -145,7 +147,6 @@ my $html_init;

!; -#} $cgi->param('dummy', 1); @@ -436,6 +437,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'; @@ -446,8 +451,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', @@ -458,11 +473,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($_), @@ -540,4 +574,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 { + ''; + }; + push @links, ''; + $align .= 'c'; + $html_form = qq!
!; + $html_foot = include('/search/elements/checkbox-foot.html', + submit => 'edit report classes', # for now it's only report classes + ) . '
'; +} + 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 + + +
+
+The following packages will be changed:
+% foreach my $pkgpart (sort keys(%part_pkg)) { + +<% $part_pkg{$pkgpart}->pkg_comment %>
+% } +
+
+<& /elements/table-grid.html &>\ +<& /elements/tr-justtitle.html, value => mt('Report classes') &> +% my $row = 0; +% foreach my $num (sort keys %report_class) { + + +% 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 &> +% } + + <% $report_class{$num}->name %> + +% $row++; +% } + +
+ +
+<& /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 +} + 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 ); +} + 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. + +<%shared> +my $init = 0; + +% if ( !$init ) { +% $init = 1; + +% } # end of $init + + +<%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'; + diff --git a/httemplate/search/cdr.html b/httemplate/search/cdr.html index 642c2da5b..1b4604bbb 100644 --- a/httemplate/search/cdr.html +++ b/httemplate/search/cdr.html @@ -9,26 +9,8 @@ 'fields' => \@fields, 'links' => \@links, 'html_form' => qq!
!, - #false laziness w/queue.html - 'html_foot' => sub { - if ( $areboxes ) { - '
'. - ''. - qq!
!. - qq!
!. - ''; - } 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 = {}; @@ -355,7 +335,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!!; @@ -409,4 +388,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?" }, + ] +); 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. + + + + +<%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'; + -- 2.11.0