Bugzilla – Attachment 118553 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), 31.80 KB, created by
Agustín Moyano
on 2021-03-19 21:14:25 UTC
(
hide
)
Description:
Bug 23678: Allow cancel holds in bulk
Filename:
MIME Type:
Creator:
Agustín Moyano
Created:
2021-03-19 21:14:25 UTC
Size:
31.80 KB
patch
obsolete
>From 62c7660ccfb1dda77628ba2b31f764732f966f29 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. >10. Check In some of the items so the get in Waiting state. >11. Update expirationdate os some of those holds and set it to > ReservesMaxPickUpDelay + 1 days earlier >NOTE => ReservesMaxPickUpDelay = 7 days by default, so sql syntax to update would be > => update reserves set expirationdate = date_sub(expirationdate, interval 8 day) where reserve_id in (...) >12. Repeat steps 4 to 6 but in waitingreserves.pl, in both tabs. >--- > Koha/BackgroundJob.pm | 3 + > Koha/BackgroundJob/BatchCancelHold.pm | 144 ++++++++++++++++++ > circ/waitingreserves.pl | 17 +++ > .../prog/en/includes/holds_table.inc | 4 +- > .../prog/en/includes/waiting_holds.inc | 8 +- > .../prog/en/modules/admin/background_jobs.tt | 32 ++++ > .../prog/en/modules/circ/waitingreserves.tt | 123 ++++++++++++++- > .../prog/en/modules/reserve/request.tt | 74 ++++++++- > misc/background_jobs_worker.pl | 2 +- > reserve/request.pl | 14 ++ > 10 files changed, 409 insertions(+), 12 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..47426f8455 >--- /dev/null >+++ b/Koha/BackgroundJob/BatchCancelHold.pm >@@ -0,0 +1,144 @@ >+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, $patron, $biblio); >+ $hold = Koha::Holds->find($hold_id); >+ >+ my $error = eval { >+ $patron = $hold->patron; >+ $biblio = $hold->biblio; >+ $hold->cancel({cancellation_reason => $args->{reason}}); >+ }; >+ >+ if ( $error and $error != $hold or $@ ) { >+ push @messages, { >+ type => 'error', >+ code => 'hold_not_cancelled', >+ patron_id => defined $patron?$patron->borrowernumber:'', >+ patron_name => defined $patron?($patron->firstname?$patron->firstname.', ':'').$patron->surname:'', >+ biblio_id => defined $biblio?$biblio->biblionumber:'', >+ biblio_title => defined $biblio?$biblio->title:'', >+ hold_id => $hold_id, >+ error => defined $hold?($@ ? $@ : 0):'No hold with id '.$hold_id.' found', >+ }; >+ } 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/circ/waitingreserves.pl b/circ/waitingreserves.pl >index 1a8d74ade4..0dd76549b2 100755 >--- a/circ/waitingreserves.pl >+++ b/circ/waitingreserves.pl >@@ -39,6 +39,7 @@ use Koha::BiblioFrameworks; > use Koha::Items; > use Koha::ItemTypes; > use Koha::Patrons; >+use Koha::BackgroundJob::BatchCancelHold; > > my $input = CGI->new; > >@@ -49,6 +50,7 @@ my $tbr = $input->param('tbr') || ''; > my $all_branches = $input->param('allbranches') || ''; > my $cancelall = $input->param('cancelall'); > my $tab = $input->param('tab'); >+my $cancelBulk = $input->param('cancelBulk'); > > my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user( > { >@@ -80,6 +82,21 @@ if ( C4::Context->preference('IndependentBranches') ) { > } > $template->param( all_branches => 1 ) if $all_branches; > >+if ( $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 >+ ) >+} >+ > my (@reserve_loop, @over_loop); > # FIXME - Is priority => 0 useful? If yes it must be moved to waiting, otherwise we need to remove it from here. > my $holds = Koha::Holds->waiting->search({ priority => 0, ( $all_branches ? () : ( branchcode => $default ) ) }, { order_by => ['waitingdate'] }); >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/includes/waiting_holds.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc >index bf47e822aa..27e7fb7724 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc >+++ b/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc >@@ -1,8 +1,11 @@ > [% USE ItemTypes %] > [% USE AuthorisedValues %] >-<table id="[% table_name | html %]"> >+<table class="holds_table" id="[% table_name | html %]"> > <thead> > <tr> >+ [% IF select_column %] >+ <th class="NoSort"><input type="checkbox" class="select_hold_all"/></th> >+ [% END %] > <th class="title-string">Waiting since</th> > <th class="title-string">Date hold placed</th> > <th class="anti-the">Title</th> >@@ -19,6 +22,9 @@ > <tbody> > [% FOREACH reserveloo IN reserveloop %] > <tr> >+ [% IF select_column %] >+ <th><input type="checkbox" class="select_hold" data-id="[% reserveloo.reserve_id | html %]"/></th> >+ [% END %] > <td><span title="[% reserveloo.waitingdate | html %]">[% reserveloo.waitingdate | $KohaDates %]</span></td> > <td><span title="[% reserveloo.reservedate | html %]">[% reserveloo.reservedate | $KohaDates %]</span></td> > <td> >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..ba8ce810f7 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' %] >+ [% UNLESS !m.biblio_id || !m.patron_id %] 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. [% END %]An error occurred on cancelling.[% 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/circ/waitingreserves.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt >index 38882f3b5d..3b947d861b 100644 >--- a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt >+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt >@@ -4,6 +4,7 @@ > [% USE KohaDates %] > [% USE Branches %] > [% USE TablesSettings %] >+[% USE AuthorisedValues %] > [% SET footerjs = 1 %] > [% INCLUDE 'doc-head-open.inc' %] > <title>Koha › Circulation › Holds awaiting pickup</title> >@@ -63,6 +64,12 @@ > [% END %] > [% END %] > [% ELSE %] >+ [% 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 %] > <div id="resultlist" class="toptabs"> > <ul> > <li><a href="#holdswaiting">Holds waiting: [% reservecount | html %]</a></li> >@@ -74,7 +81,10 @@ > </ul> > <div id="holdswaiting"> > [% IF ( reserveloop ) %] >- [% INCLUDE waiting_holds.inc table_name='holdst' reserveloop=reserveloop tab='holdwaiting' %] >+ <div id="toolbar" class="btn-toolbar"> >+ <button class="cancel_selected_holds" data-bulk="true"></button> >+ </div> >+ [% INCLUDE waiting_holds.inc select_column='1' table_name='holdst' reserveloop=reserveloop tab='holdwaiting' %] > [% ELSE %] > <div class="dialog message">No holds found.</div> > [% END %] >@@ -83,6 +93,7 @@ > [% IF ( ReservesMaxPickUpDelay ) %]<p>Holds listed here have been awaiting pickup for more than [% ReservesMaxPickUpDelay | html %] days.</p>[% END %] > [% IF ( overloop ) %] > <span id="holdsover-cancel-all"> >+ <button class="cancel_selected_holds" data-bulk="true"></button> > <form name="cancelAllReserve" action="waitingreserves.pl" method="post"> > <input type="hidden" name="cancelall" value="1" /> > <input type="hidden" name="allbranches" value="[% allbranches | html %]" /> >@@ -96,8 +107,9 @@ > [% UNLESS TransferWhenCancelAllWaitingHolds %] > Only items that need not be transferred will be cancelled (TransferWhenCancelAllWaitingHolds syspref) > [% END %] >+ > </span> >- [% INCLUDE waiting_holds.inc table_name='holdso' reserveloop=overloop tab='holdsover' %] >+ [% INCLUDE waiting_holds.inc select_column='1' table_name='holdso' reserveloop=overloop tab='holdsover' %] > [% ELSE %] > <div class="dialog message">No holds found.</div> > [% END %] >@@ -119,25 +131,128 @@ > </div> <!-- /.col-sm-12 --> > </div> <!-- /.row --> > >+ <div id="cancelModal" class="modal" tabindex="-1" role="dialog" aria-hidden="true"> >+ <div class="modal-dialog" role="document"> >+ <div class="modal-content"> >+ <div class="modal-header"> >+ <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">Ã</button> >+ <h3>Confirm deletion</h3> >+ </div> >+ >+ <div class="modal-body"> >+ <p>Are you sure you want to cancel this hold?</p> >+ >+ <fieldset class="action"> >+ [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %] >+ [% IF hold_cancellation %] >+ <label for="cancellation-reason">Cancellation reason: </label> >+ <select class="cancellation-reason" name="modal-cancellation-reason" id="modal-cancellation-reason"> >+ <option value="">No reason given</option> >+ [% FOREACH reason IN hold_cancellation %] >+ <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option> >+ [% END %] >+ </select> >+ [% END %] >+ </fieldset> >+ </div> >+ >+ <div class="modal-footer"> >+ <button id="cancelModalConfirmBtn" type="button" class="btn btn-danger">Confirm cancellation</button> >+ <a href="#" data-dismiss="modal">Cancel</a> >+ </div> >+ </div> >+ </div> >+ </div> >+ > [% MACRO jsinclude BLOCK %] > [% INCLUDE 'datatables.inc' %] > [% INCLUDE 'columns_settings.inc' %] > <script> >+ var MSG_CANCEL_SELECTED = _("Cancel selected (%s)"); > var holdst_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdst', 'json' ) | $raw %]; > var holdso_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdso', 'json' ) | $raw %]; > > $(document).ready(function() { > > KohaTable("holdst", { >- "sPaginationType": "full" >+ "sPaginationType": "full", >+ "order": [[1, 'asc']] > }, holdst_columns_settings); > > KohaTable("holdso", { >- "sPaginationType": "full" >+ "sPaginationType": "full", >+ "order": [[1, 'asc']] > }, holdso_columns_settings); > > $('#resultlist').tabs(); > >+ let cancel_link; >+ >+ $("#cancelModalConfirmBtn").on("click",function(e) { >+ var ids = cancel_link.data('ids'); >+ localStorage.selectedWaitingHolds = JSON.stringify(JSON.parse(localStorage.selectedWaitingHolds).filter(id => !ids.includes(id))); >+ let link = `waitingreserves.pl?cancelBulk=1&ids=${ids.join(',')}`; >+ let reason = $("#modal-cancellation-reason").val(); >+ if ( reason ) { >+ link += "&cancellation-reason=" + reason >+ } >+ window.location.href = link; >+ return false; >+ }); >+ >+ if(!localStorage.selectedWaitingHolds || document.referrer.replace(/\?.*/, '') !== document.location.origin+document.location.pathname) { >+ localStorage.selectedWaitingHolds = '[]'; >+ } >+ >+ try { >+ JSON.parse(localStorage.selectedWaitingHolds); >+ } catch(e) { >+ localStorage.selectedWaitingHolds = '[]'; >+ } >+ >+ $('.holds_table .select_hold').each(function() { >+ if(JSON.parse(localStorage.selectedWaitingHolds).includes($(this).data('id'))) { >+ $(this).prop('checked', true); >+ } >+ }); >+ >+ $('.holds_table').each(function() { >+ var table = $(this); >+ var parent = table.parents('.ui-tabs-panel'); >+ >+ $('.holds_table .select_hold_all', parent).each(function() { >+ var count = $('.select_hold:not(:checked)', table).length; >+ $('.select_hold_all', table).prop('checked', !count); >+ }); >+ >+ $('.cancel_selected_holds', parent).html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked', parent).length)); >+ >+ $('.holds_table .select_hold_all', parent).click(function() { >+ var count = $('.select_hold:checked', table).length; >+ $('.select_hold', table).prop('checked', !count); >+ $(this).prop('checked', !count); >+ $('.cancel_selected_holds', parent).data('ids', $('.holds_table .select_hold:checked', parent).toArray().map(el => $(el).data('id'))).html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked', parent).length)); >+ localStorage.selectedWaitingHolds = JSON.stringify($('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id'))); >+ }); >+ >+ $('.holds_table .select_hold', parent).click(function() { >+ var count = $('.select_hold:not(:checked)', table).length; >+ $('.select_hold_all', table).prop('checked', !count); >+ $('.cancel_selected_holds', parent).data('ids', $('.holds_table .select_hold:checked', parent).toArray().map(el => $(el).data('id'))).html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked', parent).length)); >+ localStorage.selectedWaitingHolds = JSON.stringify($('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id'))); >+ }); >+ >+ $('.cancel_selected_holds', parent).click(function(e) { >+ e.preventDefault(); >+ if($('.select_hold:checked', table).length) { >+ cancel_link = $(this); >+ $('#cancelModal').modal(); >+ } >+ return false; >+ }); >+ }); >+ >+ > }); > </script> > [% END %] >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..09d033c77e 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 || document.referrer.replace(/\?.*/, '') !== document.location.origin+document.location.pathname) { >+ 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