Bugzilla – Attachment 118338 Details for
Bug 23678
Cancel holds in bulk
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 23678: Allow cancel holds in bulk
Bug-23678-Allow-cancel-holds-in-bulk.patch (text/plain), 19.00 KB, created by
Agustín Moyano
on 2021-03-16 16:40:03 UTC
(
hide
)
Description:
Bug 23678: Allow cancel holds in bulk
Filename:
MIME Type:
Creator:
Agustín Moyano
Created:
2021-03-16 16:40:03 UTC
Size:
19.00 KB
patch
obsolete
>From e24f8ad92b6e814a9a623c7008716bbc5dad4435 Mon Sep 17 00:00:00 2001 >From: Agustin Moyano <agustinmoyano@theke.io> >Date: Thu, 25 Feb 2021 13:07:12 -0300 >Subject: [PATCH] Bug 23678: Allow cancel holds in bulk > >This patch allows staff patrons to cancel multiple holds in bulk. > >To test: >1. Apply this patch >2. restart_all >3. In cataloge go to a book and place many holds >CHECK => Holds table shows a column of checkboxes >4. Play with checkboxes (have some fun ;-P) >CHECK => When you manually check all checkboxes, the checkbox in the >header also gets checked. > => When you uncheck one of the checkboxes, the one in the header also gets unchecked. > => If no checkbox is checked and you check the one in the header, >all checkboxes get checked. > => If there are some checkboxes that are checked and others are >not, when you click on the checkbox in the header all checkboxes get >unchecked. > => If all checkboxes are checked, when you uncheck the one in the >header, all checkboxes get unchecked. > => Every time you play with checkboxes, the number in the button >"Cancel selected" changes. >5. Check some of the checkboxes and click on cancel selected. >SUCCESS => A background job gets fired to cancel all selected holds. > => A message should appear with a link to the job. >6. Wait a few seconds and click on the link >SUCCESS => A message appears with the report of the execution of the >background job. >7. Grab a patron and search to hold >8. Select multiple biblios and click on "place hold for <patron>" >CHECK => After holds are confirmed, multiple holds table are shown.. one for > each record. Checkboxes work exactly the same as before, but scoped >for each individual table. Checkboxes from one table will not affect >checkboxes from other tables. >9. Repeat steps 4 to 6. >--- > Koha/BackgroundJob.pm | 3 + > Koha/BackgroundJob/BatchCancelHold.pm | 142 ++++++++++++++++++ > .../prog/en/includes/holds_table.inc | 4 +- > .../prog/en/modules/admin/background_jobs.tt | 32 ++++ > .../prog/en/modules/reserve/request.tt | 74 ++++++++- > misc/background_jobs_worker.pl | 2 +- > reserve/request.pl | 14 ++ > 7 files changed, 264 insertions(+), 7 deletions(-) > create mode 100644 Koha/BackgroundJob/BatchCancelHold.pm > >diff --git a/Koha/BackgroundJob.pm b/Koha/BackgroundJob.pm >index 09139bdbdd..a939b1e150 100644 >--- a/Koha/BackgroundJob.pm >+++ b/Koha/BackgroundJob.pm >@@ -26,6 +26,7 @@ use Koha::DateUtils qw( dt_from_string ); > use Koha::Exceptions; > use Koha::BackgroundJob::BatchUpdateBiblio; > use Koha::BackgroundJob::BatchUpdateAuthority; >+use Koha::BackgroundJob::BatchCancelHold; > > use base qw( Koha::Object ); > >@@ -155,6 +156,8 @@ sub process { > ? Koha::BackgroundJob::BatchUpdateBiblio->process($args) > : $job_type eq 'batch_authority_record_modification' > ? Koha::BackgroundJob::BatchUpdateAuthority->process($args) >+ : $job_type eq 'batch_hold_cancel' >+ ? Koha::BackgroundJob::BatchCancelHold->process($args) > : Koha::Exceptions::Exception->throw('->process called without valid job_type'); > } > >diff --git a/Koha/BackgroundJob/BatchCancelHold.pm b/Koha/BackgroundJob/BatchCancelHold.pm >new file mode 100644 >index 0000000000..49149405ef >--- /dev/null >+++ b/Koha/BackgroundJob/BatchCancelHold.pm >@@ -0,0 +1,142 @@ >+package Koha::BackgroundJob::BatchCancelHold; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+use JSON qw( encode_json decode_json ); >+ >+use Koha::BackgroundJobs; >+use Koha::DateUtils qw( dt_from_string ); >+use Koha::Holds; >+ >+use base 'Koha::BackgroundJob'; >+ >+=head1 NAME >+ >+Koha::BackgroundJob::BatchCancelHold - Batch cancel holds >+ >+This is a subclass of Koha::BackgroundJob. >+ >+=head1 API >+ >+=head2 Class methods >+ >+=head3 job_type >+ >+Define the job type of this job: batch_hold_cancel >+ >+=cut >+ >+sub job_type { >+ return 'batch_hold_cancel'; >+} >+ >+=head3 process >+ >+Process the modification. >+ >+=cut >+ >+sub process { >+ my ( $self, $args ) = @_; >+ >+ my $job = Koha::BackgroundJobs->find( $args->{job_id} ); >+ >+ if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) { >+ return; >+ } >+ >+ my $job_progress = 0; >+ $job->started_on(dt_from_string) >+ ->progress($job_progress) >+ ->status('started') >+ ->store; >+ >+ my @hold_ids = @{ $args->{hold_ids} }; >+ >+ my $report = { >+ total_holds => scalar @hold_ids, >+ total_success => 0, >+ }; >+ my @messages; >+ HOLD_IDS: for my $hold_id ( sort { $a <=> $b } @hold_ids ) { >+ next unless $hold_id; >+ # Authorities >+ my $hold; >+ my $error = eval { >+ $hold = Koha::Holds->find($hold_id); >+ $hold->cancel({cancellation_reason => $args->{reason}}); >+ }; >+ my $patron = $hold->patron; >+ my $biblio = $hold->biblio; >+ if ( $error and $error != $hold or $@ ) { >+ push @messages, { >+ type => 'error', >+ code => 'hold_not_cancelled', >+ patron_id => $patron->borrowernumber, >+ patron_name => ($patron->firstname?$patron->firstname.', ':'').$patron->surname, >+ biblio_id => $biblio->biblionumber, >+ biblio_title => $biblio->title, >+ hold_id => $hold_id, >+ error => ($@ ? $@ : 0), >+ }; >+ } else { >+ push @messages, { >+ type => 'success', >+ code => 'hold_cancelled', >+ patron_id => $patron->borrowernumber, >+ patron_name => ($patron->firstname?$patron->firstname.', ':'').$patron->surname, >+ biblio_id => $biblio->biblionumber, >+ biblio_title => $biblio->title, >+ hold_id => $hold_id, >+ }; >+ $report->{total_success}++; >+ } >+ $job->progress( ++$job_progress )->store; >+ } >+ >+ my $job_data = decode_json $job->data; >+ $job_data->{messages} = \@messages; >+ $job_data->{report} = $report; >+ >+ $job->ended_on(dt_from_string) >+ ->data(encode_json $job_data); >+ $job->status('finished') if $job->status ne 'cancelled'; >+ $job->store; >+ >+} >+ >+=head3 enqueue >+ >+Enqueue the new job >+ >+=cut >+ >+sub enqueue { >+ my ( $self, $args) = @_; >+ >+ # TODO Raise exception instead >+ return unless exists $args->{hold_ids}; >+ >+ my @hold_ids = @{ $args->{hold_ids} }; >+ >+ $self->SUPER::enqueue({ >+ job_size => scalar @hold_ids, >+ job_args => {hold_ids => \@hold_ids, reason => $args->{reason}} >+ }); >+} >+ >+1; >diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >index 59f33cd2a0..fee360c65c 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc >@@ -1,8 +1,9 @@ > [% USE Koha %] > [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %] > [% USE AuthorisedValues %] >-<table> >+<table class="holds_table"> > <tr> >+ <th><input type="checkbox" class="select_hold_all"/></th> > [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %] > <th>Priority</th> > <th> </th> >@@ -28,6 +29,7 @@ > [% FOREACH hold IN holds %] > [% IF !hold.found && first_priority == 0 %][% first_priority = hold.priority %][% END %] > <tr> >+ <th><input type="checkbox" class="select_hold" data-id="[% hold.reserve_id | html %]"/></th> > <td> > <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" /> > <input type="hidden" name="borrowernumber" value="[% hold.borrowernumber | html %]" /> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt >index 86ffb3c540..66af5faeef 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt >@@ -85,6 +85,20 @@ > </div> > [% END %] > [% END %] >+ [% CASE 'batch_hold_cancel' %] >+ [% SET report = job.report %] >+ [% IF report %] >+ [% IF report.total_holds == report.total_success %] >+ <div class="dialog message"> >+ All holds have successfully been cancelled! >+ </div> >+ [% ELSE %] >+ <div class="dialog message"> >+ [% report.total_success | html %] / [% report.total_holds | html %] holds have successfully been modified. Some errors occurred. >+ [% IF job.status == 'cancelled' %]The job has been cancelled before it finished.[% END %] >+ </div> >+ [% END %] >+ [% END %] > [% CASE %]Job type "[% job.type | html %]" not handled in the template > [% END %] > </li> >@@ -126,6 +140,24 @@ > [% END %] > </div> > [% END %] >+ [% CASE 'batch_hold_cancel' %] >+ [% FOR m IN job.messages %] >+ <div class="dialog message"> >+ [% IF m.type == 'success' %] >+ <i class="fa fa-check success"></i> >+ [% ELSIF m.type == 'warning' %] >+ <i class="fa fa-warning warn"></i> >+ [% ELSIF m.type == 'error' %] >+ <i class="fa fa-exclamation error"></i> >+ [% END %] >+ [% SWITCH m.code %] >+ [% CASE 'hold_not_cancelled' %] >+ Hold on <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% m.biblio_id | uri %]">[% m.biblio_title | html %]</a> for <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% m.patron_id | uri %]">[% m.patron_name %]</a> has not been cancelled. An error occurred on modifying it.[% IF m.error %] ([% m.error | html %])[% END %]. >+ [% CASE 'hold_cancelled' %] >+ Hold on <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% m.biblio_id | uri %]">[% m.biblio_title | html %]</a> for <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% m.patron_id | uri %]">[% m.patron_name %]</a> has successfully been cancelled. >+ [% END %] >+ </div> >+ [% END %] > [% CASE %]Job type "[% job.type | html %]" not handled in the template > [% END %] > </li> >diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >index 5dfff18595..1fc5ab0d96 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt >@@ -808,9 +808,16 @@ > <input type = "hidden" name="biblionumbers" value="[% biblionumbers | html %]"/> > [% END %] > >+ [% IF enqueued %] >+ <div class="dialog message"> >+ <p>The job has been enqueued! It will be processed as soon as possible.</p> >+ <p><a href="/cgi-bin/koha/admin/background_jobs.pl?op=view&id=[% job_id | uri %]" title="View detail of the enqueued job">View detail of the enqueued job</a></p> >+ </div> >+ [% END %] >+ > <h2>Existing holds</h2> > <div id="toolbar" class="btn-toolbar"> >- <input type="submit" name="submit" value="Update hold(s)" /> >+ <input type="submit" name="submit" value="Update hold(s)" /> <button class="cancel_selected_holds" data-bulk="true"></button> > <fieldset id="cancellation-reason-fieldset" class="action"> > [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %] > [% IF hold_cancellation %] >@@ -1015,6 +1022,8 @@ > cannotBeTransferred: _("Cannot be transferred to pickup library"), > pickupNotInHoldGroup: _("Only pickup locations within the same hold group are allowed") > } >+ >+ var MSG_CANCEL_SELECTED = _("Cancel selected (%s)"); > columns_settings_borrowers_table = [% TablesSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %]; > $.fn.select2.defaults.set("width", "100%" ); > $.fn.select2.defaults.set("dropdownAutoWidth", true ); >@@ -1339,11 +1348,20 @@ > return false; > }); > $("#cancelModalConfirmBtn").on("click",function(e) { >- let borrowernumber = cancel_link.data('borrowernumber'); >- let biblionumber = cancel_link.data('biblionumber'); >- let reserve_id = cancel_link.data('id'); >+ let link; >+ if(cancel_link.data('bulk')) { >+ [% IF biblionumbers %] >+ link = `request.pl?biblionumbers=[% biblionumbers | url %]&action=cancelBulk&ids=${$('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(',')}`; >+ [% ELSE %] >+ link = `request.pl?biblionumber=[% biblionumber | url %]&action=cancelBulk&ids=${$('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(',')}`; >+ [% END %] >+ } else { >+ let borrowernumber = cancel_link.data('borrowernumber'); >+ let biblionumber = cancel_link.data('biblionumber'); >+ let reserve_id = cancel_link.data('id'); >+ link = `request.pl?action=cancel&borrowernumber=${ borrowernumber }&biblionumber=${ biblionumber }&reserve_id=${ reserve_id }`; >+ } > let reason = $("#modal-cancellation-reason").val(); >- let link = `request.pl?action=cancel&borrowernumber=${ borrowernumber }&biblionumber=${ biblionumber }&reserve_id=${ reserve_id }`; > if ( reason ) { > link += "&cancellation-reason=" + reason > } >@@ -1391,6 +1409,52 @@ > stickTo: "#existing_holds", > stickyClass: "floating" > }); >+ >+ if(!localStorage.selectedHolds) { >+ localStorage.selectedHolds = []; >+ } >+ >+ $('.holds_table .select_hold').each(function() { >+ if(localStorage.selectedHolds.includes($(this).data('id'))) { >+ $(this).prop('checked', true); >+ } >+ }); >+ >+ $('.holds_table .select_hold_all').each(function() { >+ var table = $(this).parents('.holds_table'); >+ var count = $('.select_hold:not(:checked)', table).length; >+ $('.select_hold_all', table).prop('checked', !count); >+ }); >+ >+ $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length)); >+ >+ $('.holds_table .select_hold_all').click(function() { >+ var table = $(this).parents('.holds_table'); >+ var count = $('.select_hold:checked', table).length; >+ $('.select_hold', table).prop('checked', !count); >+ $(this).prop('checked', !count); >+ $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length)); >+ localStorage.selectedHolds = $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')); >+ }); >+ >+ $('.holds_table .select_hold').click(function() { >+ var table = $(this).parents('.holds_table'); >+ var count = $('.select_hold:not(:checked)', table).length; >+ $('.select_hold_all', table).prop('checked', !count); >+ $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length)); >+ localStorage.selectedHolds = $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')); >+ }); >+ >+ $('.cancel_selected_holds').click(function(e) { >+ e.preventDefault(); >+ if($('.holds_table .select_hold:checked').length) { >+ cancel_link = $(this); >+ delete localStorage.selectedHolds; >+ $('#cancelModal').modal(); >+ } >+ return false; >+ }); >+ > }); > </script> > [% END %] >diff --git a/misc/background_jobs_worker.pl b/misc/background_jobs_worker.pl >index 0520189fa5..05de0ae2b3 100755 >--- a/misc/background_jobs_worker.pl >+++ b/misc/background_jobs_worker.pl >@@ -28,7 +28,7 @@ try { > warn sprintf "Cannot connect to the message broker, the jobs will be processed anyway (%s)", $_; > }; > >-my @job_types = qw( batch_biblio_record_modification batch_authority_record_modification ); >+my @job_types = qw( batch_biblio_record_modification batch_authority_record_modification batch_hold_cancel ); > > if ( $conn ) { > # FIXME cf note in Koha::BackgroundJob about $namespace >diff --git a/reserve/request.pl b/reserve/request.pl >index 1751a0aa92..52a5967618 100755 >--- a/reserve/request.pl >+++ b/reserve/request.pl >@@ -54,6 +54,7 @@ use Koha::ItemTypes; > use Koha::Libraries; > use Koha::Patrons; > use Koha::Clubs; >+use Koha::BackgroundJob::BatchCancelHold; > > my $dbh = C4::Context->dbh; > my $input = CGI->new; >@@ -115,6 +116,19 @@ if ( $action eq 'move' ) { > my $reserve_id = $input->param('reserve_id'); > my $suspend_until = $input->param('suspend_until'); > ToggleSuspend( $reserve_id, $suspend_until ); >+} elsif ( $action eq 'cancelBulk') { >+ my $cancellation_reason = $input->param("cancellation-reason"); >+ my @hold_ids = split ',', $input->param("ids"); >+ my $params = { >+ reason => $cancellation_reason, >+ hold_ids => \@hold_ids, >+ }; >+ my $job_id = Koha::BackgroundJob::BatchCancelHold->new->enqueue($params); >+ >+ $template->param( >+ enqueued => 1, >+ job_id => $job_id >+ ) > } > > if ($findborrower) { >-- >2.25.0
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 23678
:
118338
|
118553
|
120265
|
123185
|
123186
|
123187
|
125404
|
125405
|
125406
|
125470
|
125577
|
125578
|
125579
|
125589
|
125590
|
125600
|
125627
|
125628
|
125629
|
125631