From f8578e441237b549f17470091e46a1bc9aea18f0 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 25 Sep 2013 16:45:14 +0200 Subject: [PATCH] [PASSED QA] Bug 11023: Toggle "new" status This patch adds: - a new DB field items.new. - a new page to configure this new status (tools/toggle_new_status.pl). - a new cronjob script (misc/cronjob/toggle_new_status.pl). Why this status is useful for some libraries ? The use cases are: - to know easily what are the new items (with a simple sql query). - to display an icon in the search results. - issuing rules can be adapt for new items. Automatically (using the cronjob script), the status change (depending the configuration) and the item can be issued, for example. - a RSS/Atom feeds can be created on these new items. Test plan: - log in with a librarian having the tools > items_batchmod permission. - navigate to Home > Tools > Toggle new status - click on the edit button - there are 3 "blocks": * duration: the duration during an item is considered as new. * conditions: the status will change only if the conditions are meet. * substitutions: if there is no substitution, no action will be done. You can add some change to apply to the matching items. E.g. ccode=3 new='' If the value is an empty string (in other words, the input does not contain anything), the field will be deleted. You can create as many rules as you want. - test the interface : add/remove rule, conditions, substitutions, submit the form, edit, etc. (There is a looot of JS everywhere, so certainly a looot of bugs...). - when you have your rules defined, you can now launch the cronjob script without any parameter. A report will be displayed with the matching itemnumber and the substitutions to apply. Verify results are consistent. - launch the script with the -c argument and verify values have been modified depending the substitution rules. Signed-off-by: juliette et remy Signed-off-by: Kyle M Hall --- C4/Items.pm | 91 ++++++- .../intranet-tmpl/prog/en/css/staff-global.css | 29 ++ .../en/modules/help/tools/toggle_new_status.tt | 45 +++ .../prog/en/modules/tools/toggle_new_status.tt | 294 ++++++++++++++++++++ .../prog/en/modules/tools/tools-home.tt | 5 + misc/cronjobs/toggle_new_status.pl | 106 +++++++ tools/toggle_new_status.pl | 116 ++++++++ 7 files changed, 685 insertions(+), 1 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/help/tools/toggle_new_status.tt create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/toggle_new_status.tt create mode 100755 misc/cronjobs/toggle_new_status.pl create mode 100755 tools/toggle_new_status.pl diff --git a/C4/Items.pm b/C4/Items.pm index 33143a5..c4d55e7 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -470,6 +470,7 @@ sub _build_default_values_for_mod_marc { location => undef, permanent_location => undef, materials => undef, + new => undef, notforloan => 0, # paidfor => undef, # commented, see bug 12817 price => undef, @@ -2164,7 +2165,8 @@ sub _koha_new_item { enumchron = ?, more_subfields_xml = ?, copynumber = ?, - stocknumber = ? + stocknumber = ?, + new = ? "; my $sth = $dbh->prepare($query); my $today = C4::Dates->today('iso'); @@ -2207,6 +2209,7 @@ sub _koha_new_item { $item->{'more_subfields_xml'}, $item->{'copynumber'}, $item->{'stocknumber'}, + $item->{'new'}, ); my $itemnumber; @@ -3056,4 +3059,90 @@ sub PrepareItemrecordDisplay { }; } +=head2 columns + + my @columns = C4::Items::columns(); + +Returns an array of items' table columns on success, +and an empty array on failure. + +=cut + +sub columns { + # Pure ANSI SQL goodness. + my $sql = 'SELECT * FROM items WHERE 1=0;'; + + # Get the database handle. + my $dbh = C4::Context->dbh; + + # Run the SQL statement to load STH's readonly properties. + my $sth = $dbh->prepare($sql); + my $rv = $sth->execute(); + + # This only fails if the table doesn't exist. + # This will always be called AFTER an install or upgrade, + # so borrowers will exist! + my @data; + if ($sth->{NUM_OF_FIELDS}>0) { + @data = @{$sth->{NAME}}; + } + else { + @data = (); + } + return @data; +} + +sub ToggleNewStatus { + my ( $params ) = @_; + my @rules = @{ $params->{rules} }; + my $report_only = $params->{report_only}; + + my $dbh = C4::Context->dbh; + my @errors; + my @items_columns = C4::Items::columns; + my $report; + for my $rule ( @rules ) { + my $duration = $rule->{duration}; + my $conditions = $rule->{conditions}; + my $substitutions = $rule->{substitutions}; + my @params; + + my $query = q|SELECT biblionumber, itemnumber FROM items WHERE 1 |; + for my $condition ( @$conditions ) { + if ( grep {/^$condition->{field}$/} @items_columns ) { + if ( $condition->{value} =~ /\|/ ) { + my @values = split /\|/, $condition->{value}; + $query .= qq| AND $condition->{field} IN (| + . join( ',', ('?') x scalar @values ) + . q|)|; + push @params, @values; + } else { + $query .= qq| AND $condition->{field} = ?|; + push @params, $condition->{value}; + } + } + } + if ( defined $duration ) { + $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |; + push @params, $duration; + } + my $sth = $dbh->prepare($query); + $sth->execute( @params ); + while ( my $values = $sth->fetchrow_hashref ) { + my $biblionumber = $values->{biblionumber}; + my $itemnumber = $values->{itemnumber}; + my $item = C4::Items::GetItem( $itemnumber ); + for my $substitution ( @$substitutions ) { + next unless $substitution->{field}; + C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber ) + unless $report_only; + push @{ $report->{$itemnumber} }, $substitution; + } + } + } + + return $report; +} + + 1; diff --git a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css index 9d5b7ea..9e50d0e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -2758,3 +2758,32 @@ span.onsite_checkout { border-radius: 4px; border : 1px solid #FFF2CE; } + +/* Tools > toggle_new_status */ +div.rules { + display: block; +} +div#new_rule, div.rule { + background-color: #F4F8F9; + border: 2px solid #B9D8D9; + border-radius: 5px; + margin: .3em; + padding: .3em; +} + +div.duration, div.blocks { + border: 2px solid #B9D8D9; + border-radius: 5px 5px 5px 5px; + margin: .3em; + padding: 0 .3em .3em .3em; +} + +div.duration h5, div.blocks h5 { + padding-bottom: 4px; + padding-left: 0.2em; + background-color: #E6F0F2; + border-radius: 1px; +} +div.duration span, div.blocks div { + display:block; +} diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/help/tools/toggle_new_status.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/help/tools/toggle_new_status.tt new file mode 100644 index 0000000..0bb591f --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/help/tools/toggle_new_status.tt @@ -0,0 +1,45 @@ +[% INCLUDE 'help-top.inc' %] +

Toggle new status configuration

+ +

This configuration page allows to configure the rules for the toggle new status cronjob script.

+ +

Libraries can manage the 'new' status for items. With this script, it will be possible to:

+

    +
  • know easily what are the new items in the catalogue.
  • +
  • display an icon in the search results for new items.
  • +
  • configure issuing rules depending the 'new' status.
  • +
  • get a RSS/Atom feeds on these new items.
  • +
+ +

How to work the configuration page?

+

There are 3 values to define:

+

The duration

+

This value corresponds to the duration an item is considered as new.

+

The conditions

+

Conditions should be defined if you want to test some values before to substitute fields in the items.

+

They are cumulatives but you can separate with a pipe '|' for a field with several values.

+

The substitutions

+

Substitutions are changes to apply to the matching items.

+

At least one substitution must be defined, else there is no sense to launch the script.

+

If the value is an empty string, the field will be deleted.

+

Examples

+

You want to remove the items.new value for items created 10 days ago:

+
    +
  • Duration: 10 days
  • +
  • No condition
  • +
  • Substitution: items.new = '' (no value in the input)
  • +
+ +

You want to change the items.ccode=1 to items.ccode=2 for items created 7 days ago. +

    +
  • Duration: 7 days
  • +
  • Condition: items.ccode = 1
  • +
  • Substitution: items.ccode = 2
  • +
+ +

How to execute the cronjob script?

+

The cronjob script is misc/cronjobs/toggle_new_status.pl.

+

Try the -h parameter in order to see the help.

+

Without any parameter, the script will be launched in a dry-run mode. If the -c (or --confirm) flag is given, the script will apply the changes.

+ +[% INCLUDE 'help-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/toggle_new_status.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/toggle_new_status.tt new file mode 100644 index 0000000..d1a0d2e --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/toggle_new_status.tt @@ -0,0 +1,294 @@ +[% INCLUDE 'doc-head-open.inc' %] +Koha › Tools › Toggle new status +[% INCLUDE 'doc-head-close.inc' %] + + + +[% INCLUDE 'header.inc' %] +[% INCLUDE 'cat-search.inc' %] + + +
+
+
+
+

Toggle new status

+
+ Edit +
+ [% FOR message IN messages %] + [% IF message.type == "error" %] +
+ [% END %] + [% IF message.code == "unable_to_load_configuration" %] + An error occurs: Unable to load the configuration. + [% END %] +
+ [% END %] + + [% IF op == 'edit_form' %] +
+
+

List of rules

+
+ [% FOR rule IN rules %] + [% SET id = loop.count %] +
+ +
+
Duration
+ days +
+
+
Conditions
+ [% FOR condition IN rule.conditions %] +
+ + = + + + +
+ [% END %] +
+
+
Substitutions
+ [% FOR substitution IN rule.substitutions %] +
+ + = + + + +
+ [% END %] +
+ Remove this rule +
+ [% END %] +
+
+ There is no rule defined. +
+
+ + Cancel + +
+
+
+

Add a new rule

+
+ +
+
Duration
+ days +
+
+
Conditions
+
+ + = + + + +
+
+
+
Substitutions
+
+ + = + + + +
+
+ Add this rule + Remove this rule +
+ [% ELSIF rules and op == 'show' %] +
+

List of rules

+ [% FOR rule IN rules %] +
+
+
Duration
+ [% IF rule.duration.defined and rule.duration.length > 0 %] + [% rule.duration %] days + [% ELSE %] + There is no duration for this rule. + [% END %] +
+
+
Conditions
+ [% FOR condition IN rule.conditions %] + [% IF condition.field %] +
+ [% condition.field %] = [% condition.value %] +
+ [% ELSE %] + There is no condition for this rule. + [% END %] + [% END %] +
+
+
Substitutions
+ [% FOR substitution IN rule.substitutions %] +
+ [% substitution.field %] = [% substitution.value %] +
+ [% END %] +
+
+ [% END %] +
+ [% ELSE %] + There is no rule defined. Please click on the edit button. + [% END %] + +
+
+
+ [% INCLUDE 'tools-menu.inc' %] +
+
+[% INCLUDE 'intranet-bottom.inc' %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt index a87857c..c2f4e5f 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt @@ -127,6 +127,11 @@
Delete a batch of records (bibliographic or authority)
[% END %] + [% IF ( CAN_user_tools_items_batchmod ) %] +
Toggle new status
+
Toggle the "new" status for items
+ [% END %] + [% IF ( CAN_user_tools_export_catalog ) %]
Export data
Export bibliographic, holdings, and authority records
diff --git a/misc/cronjobs/toggle_new_status.pl b/misc/cronjobs/toggle_new_status.pl new file mode 100755 index 0000000..3ed3e46 --- /dev/null +++ b/misc/cronjobs/toggle_new_status.pl @@ -0,0 +1,106 @@ +#!/usr/bin/perl + +use Modern::Perl; + +use Getopt::Long; +use Pod::Usage; +use JSON; + +use C4::Context; +use C4::Items; + +# Getting options +my ( $verbose, $help, $confirm ); +my $result = GetOptions( + 'h|help' => \$help, + 'v|verbose' => \$verbose, + 'c|confirm' => \$confirm, +); + +pod2usage(1) if $help; +$verbose = 1 unless $confirm; + +# Load configuration from the syspref +my $syspref_content = C4::Context->preference('toggle_new_status_configuration'); +my $rules = eval { JSON::from_json( $syspref_content ) }; +pod2usage({ -message => "Unable to load the configuration : $@", -exitval => 1 }) + if $@; + +my $report = C4::Items::ToggleNewStatus( { rules => $rules, report_only => not $confirm } ); + +if ( $verbose ) { + if ( $report ) { + say "Item to modify:"; + while ( my ( $itemnumber, $substitutions ) = each %$report ) { + for my $substitution ( @$substitutions ) { + if ( defined $substitution->{value} and $substitution->{value} ne q|| ) { + say "\titemnumber $itemnumber: $substitution->{field}=$substitution->{value}"; + } else { + say "\titemnumber $itemnumber: field $substitution->{field} to delete"; + } + } + } + } else { + say "There is no item to modify"; + } +} + +exit(0); + +__END__ + +=head1 NAME + +toggle_new_status.pl + +=head1 SYNOPSIS + +./toggle_new_status.pl -h + +Toggle recent acquisitions status. +Use this script to delete "new" status for items. + +=head1 OPTIONS + +=over 8 + +=item B<-h|--help> + +Prints this help message. + +=item B<-v|--verbose> + +Set the verbose flag. + +=item B<-c|--confirm> + +The script will modify the items. + +=back + +=head1 AUTHOR + +Jonathan Druart + +=head1 COPYRIGHT + +Copyright 2013 BibLibre + +=head1 LICENSE + +This file is part of Koha. + +Koha is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 of the License, or +(at your option) any later version. + +Koha is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Koha; if not, see . + +=cut diff --git a/tools/toggle_new_status.pl b/tools/toggle_new_status.pl new file mode 100755 index 0000000..9b7bf02 --- /dev/null +++ b/tools/toggle_new_status.pl @@ -0,0 +1,116 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright 2013 BibLibre +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +=head1 NAME + +toggle_new_status.pl: Update new status for items. + +=cut + +=head1 DESCRIPTION + +This script allows a user to update the new status for items. + +=cut + +use Modern::Perl; + +use CGI; +use JSON qw( to_json from_json ); + +use C4::Auth; +use C4::Context; +use C4::Items; +use C4::Output; +use C4::Koha; + +my $cgi = new CGI; + +# open template +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => "tools/toggle_new_status.tt", + query => $cgi, + type => "intranet", + authnotrequired => 0, + flagsrequired => { tools => 'items_batchmod' }, + } +); + +my $op = $cgi->param('op') // 'show'; + +my $syspref_name = q|toggle_new_status_configuration|; +if ( $op eq 'update' ) { + my @rules; + my @unique_ids = $cgi->param('unique_id'); + for my $unique_id ( @unique_ids ) { + my @substitution_fields = $cgi->param("substitution_field_$unique_id"); + my @substitution_values = $cgi->param("substitution_value_$unique_id"); + my @condition_fields = $cgi->param("condition_field_$unique_id"); + my @condition_values = $cgi->param("condition_value_$unique_id"); + my $rule = { + substitutions => [], + conditions => [], + }; + for my $value ( @substitution_values ) { + my $field = shift @substitution_fields; + last unless $field; + push @{ $rule->{substitutions} }, { field => $field, value => $value }; + } + push @{ $rule->{substitutions} }, {} + unless @{ $rule->{substitutions} }; + for my $value ( @condition_values ) { + my $field = shift @condition_fields; + last unless $field; + push @{ $rule->{conditions} }, { field => $field, value => $value }; + } + push @{ $rule->{conditions} }, {} + unless @{ $rule->{conditions} }; + $rule->{duration} = $cgi->param("duration_$unique_id"); + push @rules, $rule; + } + my $syspref_content = to_json( \@rules ); + C4::Context->set_preference($syspref_name, $syspref_content); + + $op = 'show'; +} + +my @messages; +my $syspref_content = C4::Context->preference($syspref_name); +my $rules; +$rules = eval { JSON::from_json( $syspref_content ) } + if $syspref_content; +if ( $@ ) { + push @messages, { + type => 'error', + code => 'unable_to_load_configuration' + }; + $template->param( messages => \@messages ); + output_html_with_http_headers $cgi, $cookie, $template->output; + exit; +} + +$template->param( + op => $op, + messages => \@messages, + item_fields => [ C4::Items::columns ], + rules => $rules, +); + +output_html_with_http_headers $cgi, $cookie, $template->output; -- 1.7.2.5