From fcc11540aa5d21a759ffbe294900b07d5245aad5 Mon Sep 17 00:00:00 2001 From: Jonathan Druart Date: Wed, 25 Sep 2013 16:45:14 +0200 Subject: [PATCH] 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. --- C4/Items.pm | 91 +++++- installer/data/mysql/kohastructure.sql | 2 + installer/data/mysql/updatedatabase.pl | 18 ++ .../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 ++++++++ 9 files changed, 705 insertions(+), 1 deletion(-) 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 2b83ffc..9b84d21 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -461,6 +461,7 @@ my %default_values_for_mod_from_marc = ( location => undef, permanent_location => undef, materials => undef, + new => undef, notforloan => 0, paidfor => undef, price => undef, @@ -2147,7 +2148,8 @@ sub _koha_new_item { enumchron = ?, more_subfields_xml = ?, copynumber = ?, - stocknumber = ? + stocknumber = ?, + new = ? "; my $sth = $dbh->prepare($query); my $today = C4::Dates->today('iso'); @@ -2190,6 +2192,7 @@ sub _koha_new_item { $item->{'more_subfields_xml'}, $item->{'copynumber'}, $item->{'stocknumber'}, + $item->{'new'}, ); my $itemnumber; @@ -2870,4 +2873,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/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql index 136104f..6c888a8 100644 --- a/installer/data/mysql/kohastructure.sql +++ b/installer/data/mysql/kohastructure.sql @@ -917,6 +917,7 @@ CREATE TABLE `deleteditems` ( `enumchron` text default NULL, -- serial enumeration/chronology for the item (MARC21 952$h) `copynumber` varchar(32) default NULL, -- copy number (MARC21 952$t) `stocknumber` varchar(32) default NULL, -- inventory number (MARC21 952$i) + `new` VARCHAR(32) DEFAULT NULL, -- new flag PRIMARY KEY (`itemnumber`), KEY `delitembarcodeidx` (`barcode`), KEY `delitemstocknumberidx` (`stocknumber`), @@ -1211,6 +1212,7 @@ CREATE TABLE `items` ( -- holdings/item information `enumchron` text default NULL, -- serial enumeration/chronology for the item (MARC21 952$h) `copynumber` varchar(32) default NULL, -- copy number (MARC21 952$t) `stocknumber` varchar(32) default NULL, -- inventory number (MARC21 952$i) + `new` VARCHAR(32) DEFAULT NULL, -- new flag PRIMARY KEY (`itemnumber`), UNIQUE KEY `itembarcodeidx` (`barcode`), KEY `itemstocknumberidx` (`stocknumber`), diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl index 80ab210..63d8c6e 100755 --- a/installer/data/mysql/updatedatabase.pl +++ b/installer/data/mysql/updatedatabase.pl @@ -7912,6 +7912,24 @@ if ( CheckVersion($DBversion) ) { SetVersion($DBversion); } + + + + + + +$DBversion = "3.15.00.XXX"; +if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) { + $dbh->do(q{ + ALTER TABLE `items` ADD `new` VARCHAR(32) NULL AFTER `stocknumber`; + }); + $dbh->do(q{ + ALTER TABLE `deleteditems` ADD `new` VARCHAR(32) NULL AFTER `stocknumber`; + }); + print "Upgrade to $DBversion done (Bug 11012: Adds field 'new' in items and deleteditems tables)\n"; + SetVersion($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) 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 1f8ea3f..9cc1851 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css +++ b/koha-tmpl/intranet-tmpl/prog/en/css/staff-global.css @@ -2719,3 +2719,32 @@ span.browse-button { #merge_table tr.active td { background-color: #FFFFCC; } + +/* 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:

+

+ +

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:

+ + +

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

+ +

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 4ab38c9..79911d3 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 @@ -122,6 +122,11 @@
Modify items in a batch
[% 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.10.4