From 42cbbaa195c87c2563209cbe7856df1450e18f83 Mon Sep 17 00:00:00 2001 From: Emmi Takkinen Date: Fri, 8 Mar 2024 13:46:29 +0200 Subject: [PATCH] Bug 36135: Add new tool to batch modify holds We sometimes need to modify holds due library closing down etc. For this we need tool to batch modify holds which allows us to filter holds and modify them. Tool allows users to select holds with following parameters: - expiration date -> if patron_expiration_date is set, it is used as main search filter -> otherwise expirationdate is used - library - found status - suspended status - suspend until date - hold note Users are able to modify following with tool: - expiration date (not expiration date set by patron) - pick up library - suspend status (only if hold has no found status) - suspend until date (only if suspend status is set) - hold note To test: 1. Apply this patch. 2. Navigate to new tool Tools->Batch modify holds. 3. Fill in some criteria to search holds with. => Confirm that holds found match your search criteria. 4. Set e.g. new expiration date for all or just selected holds. => Confirm that holds were modified as they should have. => Confirm this also from database. To test suspending holds with found status: 1. Find holds with found status. 2. Attempt to modify their suspend status. => Alert box with text "One or more holds have found status and can't be suspended." should be displayed. Play around, test different search compinations and modifications. Sponsored-by: Koha-Suomi Oy Signed-off-by: Laura Escamilla Signed-off-by: Lucas Gass --- .../en/modules/tools/batch_modify_holds.tt | 313 ++++++++++++++++++ tools/batch_modify_holds.pl | 175 ++++++++++ 2 files changed, 488 insertions(+) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_modify_holds.tt create mode 100755 tools/batch_modify_holds.pl diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_modify_holds.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_modify_holds.tt new file mode 100644 index 0000000000..7705c36beb --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_modify_holds.tt @@ -0,0 +1,313 @@ +[% USE raw %] +[% USE Asset %] +[% SET footerjs = 1 %] +[% USE Branches %] +[% USE Categories %] +[% USE KohaDates %] +[% USE ItemTypes %] +[% PROCESS 'html_helpers.inc' %] + +[% INCLUDE 'doc-head-open.inc' %] +Batch modify holds › Tools › Koha +[% INCLUDE 'doc-head-close.inc' %] +[% Asset.css("css/humanmsg.css") | $raw %] + + + + [% WRAPPER 'header.inc' %] + [% INCLUDE 'cat-search.inc' %] +[% END %] + + [% WRAPPER 'sub-header.inc' %] + + [% END %] + +
+
+
+
+ +

Batch modify holds

+ + [% IF view == 'form' %] +
+
+ Hold search criteria: + NOTE! Column patron_expiration_date is used as a main filter for expiration date field. Otherwise column expirationdate is used. +
    +
  1. + +
  2. + + +
  3. +
  4. + + +
  5. + +
  6. + + +
  7. +
  8. + + +
  9. +
  10. + + +
  11. +
  12. + +
  13. + + +
  14. +
  15. + + +
  16. + +
  17. + + +
  18. +
+
+
+ + + Cancel +
+
+ [% ELSIF view == 'list' %] +
+ [% IF holds.count %] +
+ + + + + + + + + + + + + + + + [% FOREACH hold IN holds %] + + + + + + + + + + + [% END %] + +
 Expiration datePatron expiration dateStatusHold pickup librarySuspendedSuspended untilNote
[% hold.expirationdate | $KohaDates %][% hold.patron_expiration_date | $KohaDates %] + [% IF hold.found == "T" %] + In transit + [% ELSIF hold.found == "P" %] + In processing + [% ELSIF hold.found == "W" %] + Waiting + [% ELSE %] + No status + [% END %] + [% Branches.GetName( hold.branchcode ) | html %][% IF hold.suspend %]Yes[% ELSE %]No[% END %][% hold.suspend_until | $KohaDates %][% hold.reservenotes | html %]
+
+

Modify holds

+ + + + + + + + + + + + + + + + + + + +
New expiration dateNew pickup librarySuspend holdsSuspend untilNew reserve note
+ + + + + + + + + +
+
+ + + Cancel +
+
+ [% ELSE %] +
+ No holds were found for the selected filters. +
+ Return + [% END %] + [% ELSIF view == 'report' %] +
+ Holds have been modified! +
+
+ + + + + + + + + + + + + [% FOREACH hold IN updated_holds %] + + + + + + + + [% END %] + +
Expiration dateHold pickup librarySuspendedSuspended untilNote
[% hold.expirationdate | $KohaDates %][% Branches.GetName( hold.branchcode ) | html %][% IF hold.suspend == 0 %]No[% ELSE %]Yes[% END %][% hold.suspend_until | $KohaDates %][% hold.reservenotes | html %]
+
+ Return to batch hold modification + [% END %] +
+
+ + +
+ +
+
+ +[% MACRO jsinclude BLOCK %] + [% Asset.js("js/tools-menu.js") | $raw %] + [% INCLUDE 'calendar.inc' %] + [% INCLUDE 'datatables.inc' %] + [% Asset.js("lib/jquery/plugins/humanmsg.js") | $raw %] + +[% END %] + +[% INCLUDE 'intranet-bottom.inc' %] \ No newline at end of file diff --git a/tools/batch_modify_holds.pl b/tools/batch_modify_holds.pl new file mode 100755 index 0000000000..ddc08e3adc --- /dev/null +++ b/tools/batch_modify_holds.pl @@ -0,0 +1,175 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright 2024 Koha Development Team +# +# 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 +# + +use Modern::Perl; + +use CGI; + +use C4::Auth qw( get_template_and_user ); +use C4::Output qw( output_html_with_http_headers ); + +use Koha::DateUtils qw( dt_from_string ); +use Koha::Holds; + +my $input = CGI->new; +my $op = $input->param('op') // q|form|; + +my ( $template, $loggedinuser, $cookie ) = get_template_and_user( + { + template_name => 'tools/batch_modify_holds.tt', + query => $input, + type => "intranet", + flagsrequired => { tools => 'batch_modify_holds.tt' }, + } +); + +my @hold_ids; + +if ( $op eq 'form' ) { + $template->param( view => 'form', ); +} +elsif ( $op eq 'list' ) { + + my @reserve_ids = $input->multi_param('reserve_ids'); + my $expirationdate_from = $input->param('expirationdate_from'); + my $expirationdate_to = $input->param('expirationdate_to'); + my @branchcodes = $input->multi_param('branchcodes'); + my @found_status = $input->multi_param('found_status'); + my $suspend_status = $input->param('suspend_status'); + my $suspend_until_from = $input->param('suspend_until_from'); + my $suspend_until_to = $input->param('suspend_until_to'); + my $reservenotes = $input->param('reservenotes'); + + my $search_params; + + if( @reserve_ids ){ + $search_params->{'me.reserve_id'} = { -in => \@reserve_ids }; + } else { + if( $expirationdate_from && $expirationdate_to ){ + my $expirationdate_params = { -or => { + patron_expiration_date => { + -between => [ + $expirationdate_from, + $expirationdate_to + ] + }, + expirationdate => { + -between => [ + $expirationdate_from, + $expirationdate_to + ] + } + }}; + + $search_params = $expirationdate_params; + } + + if ( @branchcodes ) { + $search_params->{'me.branchcode'} = { -in => \@branchcodes }; + } + + if ( @found_status ) { + my $found_params = {}; + # if NULL is used as filter, use it as "is" param + if( grep {$_ eq "NULL"} @found_status ) { + $found_params->{'-or'}{'-is'} = undef; + $found_params->{'-or'}{'-in'} = \@found_status; + } else { + $found_params = { -in => \@found_status }; + } + + $search_params->{'me.found'} = $found_params; + } + + if( $suspend_status ne "none" ){ + $search_params->{'me.suspend'} = $suspend_status; + } + + if( $suspend_until_from && $suspend_until_to ){ + $search_params->{'me.suspend_until'} = { + -between => [ + $suspend_until_from, + $suspend_until_to, + ] + }; + } + + if( $reservenotes ){ + $search_params->{'me.reservenotes'} = { -like => "%".$reservenotes."%" } + } + } + + my $holds = Koha::Holds->search( $search_params ); + + $template->param( + holds => $holds, + view => 'list', + ); +} + +if ( $op eq 'modify' ) { + + my $new_expiration_date = $input->param('new_expiration_date'); + my $new_pickup_loc = $input->param('new_pickup_loc'); + my $new_suspend_status = $input->param('new_suspend_status'); + my $new_suspend_date = $input->param('new_suspend_date'); + my $new_reserve_note = $input->param('new_reserve_note'); + + @hold_ids = $input->multi_param('hold_id'); + + my $holds_to_update = Koha::Holds->search( { reserve_id => { -in => \@hold_ids } } ); + + while ( my $hold = $holds_to_update->next ) { + + if( $new_expiration_date ){ + $hold->expirationdate($new_expiration_date)->store; + } + + unless( $hold->branchcode eq $new_pickup_loc){ + $hold->branchcode($new_pickup_loc)->store; + } + + if( $new_suspend_status && !$hold->is_found ){ + if( $new_suspend_status eq "suspend" ){ + $hold->suspend(1)->store; + if( $new_suspend_date ){ + $hold->suspend_until( $new_suspend_date )->store; + } else { + $hold->suspend_until( undef )->store; + } + } else { + $hold->suspend(0)->store; + $hold->suspend_until( undef )->store; + } + } + + if( $new_reserve_note ){ + $hold->reservenotes($new_reserve_note)->store; + } + } + + $template->param( + updated_holds => $holds_to_update, + view => 'report', + ); + +} + +output_html_with_http_headers $input, $cookie, $template->output; -- 2.30.2