From 6da87e4b1d03b8195f8483bfa04925354707b243 Mon Sep 17 00:00:00 2001 From: Lucas Gass Date: Tue, 17 Jun 2025 13:57:49 +0000 Subject: [PATCH] Bug 36135: Add ability to batch modify holds TO TEST: 0. APPLY PATCHES, updatedatabase, restart_all 1. Create a user with limited permissions, making sure you give them the "batch_modify_holds" permission 2. Login as chosen patron. 3. Navigate to Tools page. 4. Note there's a link "Batch modify holds". 5. Remove permission from user. 6. Link to new tool is not displayed. 7. Navigate to new tool Tools->Batch modify holds. 8. Fill in some criteria to search holds with. 9. Confirm that holds found match your search criteria. 10. Set e.g. new expiration date for all or just selected holds. 11. Confirm that holds were modified as they should have. 12. Confirm this also from database. 13. Find holds with a found/waiting status. 14. Attempt to modify their suspend status. 15. Alert box with text "One or more holds have found status and can't be suspended." should be displayed. 16. Play around, test different modifications. 17. Create SQL report which contains column for reserve_id. 18. e.g "SELECT reserve_id FROM reserves limit 10". 19.. Run report. 20. Over report select "Batch operations" should be displayed and under that there should be option "Batch modify holds". 21. Click "Batch modify holds" option. 22. Confirm that holds displayed in "Batch mofidy holds" tool are same as found with your report. 23. Do a holds search that will bring up multiple pages of results 24. Try selecting some holds from different pages to modify 25. Ensure all selected holds are being modified 26. Play around, test different modifications. Sponsored-by: Koha-Suomi Oy --- admin/columns_settings.yml | 47 +- api/v1/swagger/definitions/hold.yaml | 5 + api/v1/swagger/paths/holds.yaml | 2 + .../modals/batch_modify_hold_errors.inc | 17 + .../prog/en/includes/tools-menu.inc | 3 + .../modules/reports/guided_reports_start.tt | 32 +- .../en/modules/tools/batch_modify_holds.tt | 716 ++++++++++++++++++ .../prog/en/modules/tools/tools-home.tt | 7 +- tools/batch_modify_holds.pl | 107 +++ 9 files changed, 930 insertions(+), 6 deletions(-) create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/modals/batch_modify_hold_errors.inc 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/admin/columns_settings.yml b/admin/columns_settings.yml index f4820008150..54f269f3b9c 100644 --- a/admin/columns_settings.yml +++ b/admin/columns_settings.yml @@ -1987,7 +1987,52 @@ modules: columnname: suspend - columnname: print_slip - + holds_to_modify: + default_display_length: 20 + columns: + - + columnname: checkbox + cannot_be_toggled: 1 + cannot_be_modified: 1 + - + columnname: expiration_date + - + columnname: title + - + columnname: barcode + - + columnname: patron + - + columnname: status + - + columnname: pickup_library + - + columnname: suspended + - + columnname: suspended_until + - + columnname: notes + modified_holds: + default_display_length: 20 + columns: + - + columnname: expiration_date + - + columnname: title + - + columnname: barcode + - + columnname: patron + - + columnname: status + - + columnname: pickup_library + - + columnname: suspended + - + columnname: suspended_until + - + columnname: notes holdsratios: holds-ratios: default_display_length: 20 diff --git a/api/v1/swagger/definitions/hold.yaml b/api/v1/swagger/definitions/hold.yaml index 810f2647db0..8e6b6e219e4 100644 --- a/api/v1/swagger/definitions/hold.yaml +++ b/api/v1/swagger/definitions/hold.yaml @@ -135,5 +135,10 @@ properties: - object - "null" description: Pickup library + patron: + type: + - object + - "null" + description: Patron for hold additionalProperties: false diff --git a/api/v1/swagger/paths/holds.yaml b/api/v1/swagger/paths/holds.yaml index cac66270182..310af48bf03 100644 --- a/api/v1/swagger/paths/holds.yaml +++ b/api/v1/swagger/paths/holds.yaml @@ -105,6 +105,8 @@ - biblio - deleted_biblio - pickup_library + - item + - patron collectionFormat: csv produces: - application/json diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/modals/batch_modify_hold_errors.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/batch_modify_hold_errors.inc new file mode 100644 index 00000000000..ed9d1526228 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/modals/batch_modify_hold_errors.inc @@ -0,0 +1,17 @@ + + diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc index 28cd71f6b7c..15bdd2fc970 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/tools-menu.inc @@ -39,6 +39,9 @@ [% IF ( CAN_user_tools_batch_extend_due_dates ) %]
  • Batch extend due dates
  • [% END %] + [% IF ( CAN_user_tools_batch_modify_holds ) %] +
  • Batch modify holds
  • + [% END %] [% IF ( CAN_user_tools_moderate_tags ) %]
  • Tags
  • [% END %] diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reports/guided_reports_start.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reports/guided_reports_start.tt index c61553b88b3..76221287328 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/reports/guided_reports_start.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reports/guided_reports_start.tt @@ -1071,6 +1071,16 @@ > +
    + [% INCLUDE 'csrf-token.inc' %] + + +
    + [% BLOCK batch_list %] [%- FOREACH result IN results %] [%- FOREACH cells IN result.cells %] @@ -1095,9 +1105,9 @@ - [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers ) || ( unlimited_total > 10 && limit <= 1000 ) %] + [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers || batch_reserve_ids ) || ( unlimited_total > 10 && limit <= 1000 ) %]
    - [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers ) %] + [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_borrowernumbers || batch_reserve_ids ) %]
    - [% END # /IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers ) %] + [% END # /IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_reserve_ids ) %] [% IF ( unlimited_total > 10 && limit <= 1000 ) %]
    @@ -1238,7 +1262,7 @@
    [% END # /IF ( unlimited_total > 10 && limit <= 1000 ) %] - [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers ) %] + [% IF ( batch_biblionumbers || batch_itemnumbers || batch_cardnumbers || batch_reserve_ids ) %] 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 00000000000..b0fe375af1b --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_modify_holds.tt @@ -0,0 +1,716 @@ +[% USE raw %] +[% USE Asset %] +[% SET footerjs = 1 %] +[% USE Branches %] +[% USE Categories %] +[% USE KohaDates %] +[% USE ItemTypes %] +[% USE TablesSettings %] +[% USE To %] +[% 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' %] + [% INCLUDE modals/batch_modify_hold_errors.inc %] +[% END %] +[% WRAPPER 'sub-header.inc' %] + [% WRAPPER breadcrumbs %] + [% WRAPPER breadcrumb_item %] + Tools + [% END %] + [% IF ( view == 'form' || view == 'report' ) %] + [% WRAPPER breadcrumb_item %] + Batch modify holds + [% END %] + [% ELSE %] + [% WRAPPER breadcrumb_item bc_active= 1 %] + Batch modify holds + [% END %] + [% END %] + [% END #/ WRAPPER breadcrumbs %] +[% END #/ WRAPPER sub-header.inc %] + +
    +
    +
    +
    +

    Batch modify holds

    + [% IF view == "cud-form" %] + + + [% ELSIF view == "report" %] + + + [% END %] + + +
    +
    + +
    + +
    + +
    + [% MACRO jsinclude BLOCK %] + [% Asset.js("js/tools-menu.js") | $raw %] + [% INCLUDE 'calendar.inc' %] + [% INCLUDE 'datatables.inc' %] + [% INCLUDE 'select2.inc' %] + [% INCLUDE 'js-patron-format.inc' %] + [% Asset.js("lib/jquery/plugins/humanmsg.js") | $raw %] + + [% END %] + + [% 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 3aa18b39636..f7839ed8938 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 @@ -29,7 +29,7 @@

    Tools

    - [% IF ( CAN_user_tools_manage_patron_lists || CAN_user_clubs || CAN_user_tools_moderate_comments || CAN_user_tools_import_patrons || CAN_user_tools_edit_notices || CAN_user_tools_edit_notice_status_triggers || CAN_user_tools_label_creator || CAN_user_tools_delete_anonymize_patrons || CAN_user_tools_edit_patrons || CAN_user_tools_batch_extend_due_dates || CAN_user_tools_moderate_tags || CAN_user_tools_rotating_collections || ( CAN_user_tools_batch_upload_patron_images && Koha.Preference('patronimages') ) ) %] + [% IF ( CAN_user_tools_manage_patron_lists || CAN_user_clubs || CAN_user_tools_moderate_comments || CAN_user_tools_import_patrons || CAN_user_tools_edit_notices || CAN_user_tools_edit_notice_status_triggers || CAN_user_tools_label_creator || CAN_user_tools_delete_anonymize_patrons || CAN_user_tools_edit_patrons || CAN_user_tools_batch_extend_due_dates || CAN_user_tools_batch_modify_holds || CAN_user_tools_moderate_tags || CAN_user_tools_rotating_collections || ( CAN_user_tools_batch_upload_patron_images && Koha.Preference('patronimages') ) ) %]

    Patrons and circulation

    [% END %]
    @@ -88,6 +88,11 @@
    Modify the due date checkouts in batch
    [% END %] + [% IF ( CAN_user_tools_batch_modify_holds ) %] +
    Batch modify holds
    +
    Modify holds in batch
    + [% END %] + [% IF ( CAN_user_tools_moderate_tags ) %]
    Tags diff --git a/tools/batch_modify_holds.pl b/tools/batch_modify_holds.pl new file mode 100755 index 00000000000..c73b1f332e8 --- /dev/null +++ b/tools/batch_modify_holds.pl @@ -0,0 +1,107 @@ +#!/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 JSON qw( to_json ); + +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|cud-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' }, + } +); + +my @hold_ids; + +if ( $op eq 'cud-form' ) { + my $reserve_ids_list = $input->param('reserve_ids_list') || undef; + if ($reserve_ids_list) { + my @reserve_ids = split /\n/, $reserve_ids_list; + $template->param( reserve_ids_list => to_json( \@reserve_ids ), ); + } + $template->param( view => 'cud-form', ); +} elsif ( $op eq 'cud-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_hold_note = $input->param('new_hold_note'); + my $clear_hold_notes = $input->param('clear_hold_notes'); + + @hold_ids = $input->multi_param('hold_id'); + + my $holds_to_update = + Koha::Holds->search( { reserve_id => { -in => \@hold_ids } }, { join => [ "item", "biblio" ] } ); + + while ( my $hold = $holds_to_update->next ) { + + if ($new_expiration_date) { + $hold->expirationdate($new_expiration_date)->store; + } + + if ( $new_pickup_loc && ( $hold->branchcode ne $new_pickup_loc ) ) { + $hold->branchcode($new_pickup_loc)->store; + } + + if ( $new_suspend_status && $new_suspend_status ne "" && !$hold->is_found ) { + $hold->suspend(1)->store; + if ($new_suspend_date) { + $hold->suspend_until($new_suspend_date)->store; + } else { + $hold->suspend_until(undef)->store; + } + } elsif ( !$new_suspend_status && $new_suspend_date ) { + $hold->suspend(1)->store; + $hold->suspend_until($new_suspend_date)->store; + } else { + $hold->suspend(0)->store; + $hold->suspend_until(undef)->store; + } + + if ($new_hold_note) { + $hold->reservenotes($new_hold_note)->store; + } + + if ($clear_hold_notes) { + $hold->reservenotes(undef)->store; + } + } + + $template->param( + updated_holds => to_json( \@hold_ids ), + view => 'report', + ); + +} + +output_html_with_http_headers $input, $cookie, $template->output; -- 2.39.5