View | Details | Raw Unified | Return to bug 23678
Collapse All | Expand All

(-)a/Koha/BackgroundJob.pm (-2 / +2 lines)
Lines 28-33 use Koha::BackgroundJob::BatchUpdateBiblio; Link Here
28
use Koha::BackgroundJob::BatchUpdateAuthority;
28
use Koha::BackgroundJob::BatchUpdateAuthority;
29
use Koha::BackgroundJob::BatchDeleteBiblio;
29
use Koha::BackgroundJob::BatchDeleteBiblio;
30
use Koha::BackgroundJob::BatchDeleteAuthority;
30
use Koha::BackgroundJob::BatchDeleteAuthority;
31
use Koha::BackgroundJob::BatchCancelHold;
31
32
32
use base qw( Koha::Object );
33
use base qw( Koha::Object );
33
34
Lines 159-165 sub process { Link Here
159
    $args ||= {};
160
    $args ||= {};
160
161
161
    return $derived_class->process({job_id => $self->id, %$args});
162
    return $derived_class->process({job_id => $self->id, %$args});
162
163
}
163
}
164
164
165
=head3 job_type
165
=head3 job_type
Lines 256-262 sub type_to_class_mapping { Link Here
256
        batch_authority_record_modification => 'Koha::BackgroundJob::BatchUpdateAuthority',
256
        batch_authority_record_modification => 'Koha::BackgroundJob::BatchUpdateAuthority',
257
        batch_biblio_record_deletion        => 'Koha::BackgroundJob::BatchDeleteBiblio',
257
        batch_biblio_record_deletion        => 'Koha::BackgroundJob::BatchDeleteBiblio',
258
        batch_biblio_record_modification    => 'Koha::BackgroundJob::BatchUpdateBiblio',
258
        batch_biblio_record_modification    => 'Koha::BackgroundJob::BatchUpdateBiblio',
259
    };
259
        batch_hold_cancel                   => 'Koha::BackgroundJob::BatchCancelHold',
260
}
260
}
261
261
262
=head3 _type
262
=head3 _type
(-)a/Koha/BackgroundJob/BatchCancelHold.pm (+154 lines)
Line 0 Link Here
1
package Koha::BackgroundJob::BatchCancelHold;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
use JSON qw( encode_json decode_json );
20
21
use Koha::BackgroundJobs;
22
use Koha::DateUtils qw( dt_from_string );
23
use Koha::Holds;
24
25
use base 'Koha::BackgroundJob';
26
27
=head1 NAME
28
29
Koha::BackgroundJob::BatchCancelHold - Batch cancel holds
30
31
This is a subclass of Koha::BackgroundJob.
32
33
=head1 API
34
35
=head2 Class methods
36
37
=head3 job_type
38
39
Define the job type of this job: batch_hold_cancel
40
41
=cut
42
43
sub job_type {
44
    return 'batch_hold_cancel';
45
}
46
47
=head3 process
48
49
Process the modification.
50
51
=cut
52
53
sub process {
54
    my ( $self, $args ) = @_;
55
56
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
57
58
    if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) {
59
        return;
60
    }
61
62
    my $job_progress = 0;
63
    $job->started_on(dt_from_string)->progress($job_progress)
64
      ->status('started')->store;
65
66
    my @hold_ids = @{ $args->{hold_ids} };
67
68
    my $report = {
69
        total_holds   => scalar @hold_ids,
70
        total_success => 0,
71
    };
72
    my @messages;
73
      HOLD_IDS: for my $hold_id ( sort { $a <=> $b } @hold_ids ) {
74
        next unless $hold_id;
75
76
        # Authorities
77
        my ( $hold, $patron, $biblio );
78
        $hold = Koha::Holds->find($hold_id);
79
80
        my $error = eval {
81
            $patron = $hold->patron;
82
            $biblio = $hold->biblio;
83
            $hold->cancel( { cancellation_reason => $args->{reason} } );
84
        };
85
86
        if ( $error and $error != $hold or $@ ) {
87
            push @messages,
88
              {
89
                type        => 'error',
90
                code        => 'hold_not_cancelled',
91
                patron_id   => defined $patron ? $patron->borrowernumber : '',
92
                patron_name => defined $patron
93
                ? ( $patron->firstname ? $patron->firstname . ', ' : '' )
94
                  . $patron->surname
95
                : '',
96
                biblio_id    => defined $biblio ? $biblio->biblionumber : '',
97
                biblio_title => defined $biblio ? $biblio->title        : '',
98
                hold_id      => $hold_id,
99
                error        => defined $hold
100
                ? ( $@ ? $@ : 0 )
101
                : 'No hold with id ' . $hold_id . ' found',
102
              };
103
        }
104
        else {
105
            push @messages,
106
              {
107
                type      => 'success',
108
                code      => 'hold_cancelled',
109
                patron_id => $patron->borrowernumber,
110
                patron_name =>
111
                  ( $patron->firstname ? $patron->firstname . ', ' : '' )
112
                  . $patron->surname,
113
                biblio_id    => $biblio->biblionumber,
114
                biblio_title => $biblio->title,
115
                hold_id      => $hold_id,
116
              };
117
            $report->{total_success}++;
118
        }
119
        $job->progress( ++$job_progress )->store;
120
    }
121
122
    my $job_data = decode_json $job->data;
123
    $job_data->{messages} = \@messages;
124
    $job_data->{report}   = $report;
125
126
    $job->ended_on(dt_from_string)->data( encode_json $job_data);
127
    $job->status('finished') if $job->status ne 'cancelled';
128
    $job->store;
129
130
}
131
132
=head3 enqueue
133
134
Enqueue the new job
135
136
=cut
137
138
sub enqueue {
139
    my ( $self, $args ) = @_;
140
141
    # TODO Raise exception instead
142
    return unless exists $args->{hold_ids};
143
144
    my @hold_ids = @{ $args->{hold_ids} };
145
146
    $self->SUPER::enqueue(
147
        {
148
            job_size => scalar @hold_ids,
149
            job_args => { hold_ids => \@hold_ids, reason => $args->{reason} }
150
        }
151
    );
152
}
153
154
1;
(-)a/circ/waitingreserves.pl (+17 lines)
Lines 31-36 use Koha::BiblioFrameworks; Link Here
31
use Koha::Items;
31
use Koha::Items;
32
use Koha::ItemTypes;
32
use Koha::ItemTypes;
33
use Koha::Patrons;
33
use Koha::Patrons;
34
use Koha::BackgroundJob::BatchCancelHold;
34
35
35
my $input = CGI->new;
36
my $input = CGI->new;
36
37
Lines 41-46 my $tbr = $input->param('tbr') || ''; Link Here
41
my $all_branches   = $input->param('allbranches') || '';
42
my $all_branches   = $input->param('allbranches') || '';
42
my $cancelall      = $input->param('cancelall');
43
my $cancelall      = $input->param('cancelall');
43
my $tab            = $input->param('tab');
44
my $tab            = $input->param('tab');
45
my $cancelBulk     = $input->param('cancelBulk');
44
46
45
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
47
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
46
    {
48
    {
Lines 71-76 if ( C4::Context->preference('IndependentBranches') ) { Link Here
71
}
73
}
72
$template->param( all_branches => 1 ) if $all_branches;
74
$template->param( all_branches => 1 ) if $all_branches;
73
75
76
if ($cancelBulk) {
77
    my $reason   = $input->param("cancellation-reason");
78
    my @hold_ids = split ',', $input->param("ids");
79
    my $params   = {
80
        reason   => $reason,
81
        hold_ids => \@hold_ids,
82
    };
83
    my $job_id = Koha::BackgroundJob::BatchCancelHold->new->enqueue($params);
84
85
    $template->param(
86
        enqueued => 1,
87
        job_id   => $job_id
88
    );
89
}
90
74
my (@reserve_loop, @over_loop);
91
my (@reserve_loop, @over_loop);
75
# FIXME - Is priority => 0 useful? If yes it must be moved to waiting, otherwise we need to remove it from here.
92
# FIXME - Is priority => 0 useful? If yes it must be moved to waiting, otherwise we need to remove it from here.
76
my $holds = Koha::Holds->waiting->search({ priority => 0, ( $all_branches ? () : ( branchcode => $default ) ) }, { order_by => ['waitingdate'] });
93
my $holds = Koha::Holds->waiting->search({ priority => 0, ( $all_branches ? () : ( branchcode => $default ) ) }, { order_by => ['waitingdate'] });
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/holds_table.inc (-1 / +3 lines)
Lines 1-8 Link Here
1
[% USE Koha %]
1
[% USE Koha %]
2
[% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
2
[% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
3
[% USE AuthorisedValues %]
3
[% USE AuthorisedValues %]
4
<table>
4
<table class="holds_table">
5
    <tr>
5
    <tr>
6
        <th><input type="checkbox" class="select_hold_all"/></th>
6
        [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %]
7
        [% IF ( CAN_user_reserveforothers_modify_holds_priority ) %]
7
            <th>Priority</th>
8
            <th>Priority</th>
8
            <th>&nbsp;</th>
9
            <th>&nbsp;</th>
Lines 48-53 Link Here
48
        [%- this_priority = loop.count() - found_holds -%]
49
        [%- this_priority = loop.count() - found_holds -%]
49
    [%- END -%]
50
    [%- END -%]
50
        <tr>
51
        <tr>
52
            <th><input type="checkbox" class="select_hold" data-id="[% hold.reserve_id | html %]"/></th>
51
            <td>
53
            <td>
52
                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
54
                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
53
                <input type="hidden" name="borrowernumber" value="[% hold.borrowernumber | html %]" />
55
                <input type="hidden" name="borrowernumber" value="[% hold.borrowernumber | html %]" />
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/waiting_holds.inc (-1 / +7 lines)
Lines 1-8 Link Here
1
[% USE ItemTypes %]
1
[% USE ItemTypes %]
2
[% USE AuthorisedValues %]
2
[% USE AuthorisedValues %]
3
<table id="[% table_name | html %]">
3
<table class="holds_table" id="[% table_name | html %]">
4
    <thead>
4
    <thead>
5
        <tr>
5
        <tr>
6
            [% IF select_column %]
7
            <th class="NoSort"><input type="checkbox" class="select_hold_all"/></th>
8
            [% END %]
6
            <th class="title-string">Waiting since</th>
9
            <th class="title-string">Waiting since</th>
7
            <th class="title-string">Date hold placed</th>
10
            <th class="title-string">Date hold placed</th>
8
            <th class="anti-the">Title</th>
11
            <th class="anti-the">Title</th>
Lines 19-24 Link Here
19
    <tbody>
22
    <tbody>
20
        [% FOREACH reserveloo IN reserveloop %]
23
        [% FOREACH reserveloo IN reserveloop %]
21
            <tr>
24
            <tr>
25
                [% IF select_column %]
26
                <th><input type="checkbox" class="select_hold" data-id="[% reserveloo.reserve_id | html %]"/></th>
27
                [% END %]
22
                <td><span title="[% reserveloo.waitingdate | html %]">[% reserveloo.waitingdate | $KohaDates %]</span></td>
28
                <td><span title="[% reserveloo.waitingdate | html %]">[% reserveloo.waitingdate | $KohaDates %]</span></td>
23
                <td><span title="[% reserveloo.reservedate | html %]">[% reserveloo.reservedate | $KohaDates %]</span></td>
29
                <td><span title="[% reserveloo.reservedate | html %]">[% reserveloo.reservedate | $KohaDates %]</span></td>
24
                <td>
30
                <td>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/circ/waitingreserves.tt (-4 / +119 lines)
Lines 4-9 Link Here
4
[% USE KohaDates %]
4
[% USE KohaDates %]
5
[% USE Branches %]
5
[% USE Branches %]
6
[% USE TablesSettings %]
6
[% USE TablesSettings %]
7
[% USE AuthorisedValues %]
7
[% SET footerjs = 1 %]
8
[% SET footerjs = 1 %]
8
[% INCLUDE 'doc-head-open.inc' %]
9
[% INCLUDE 'doc-head-open.inc' %]
9
<title>Holds awaiting pickup &rsaquo; Circulation &rsaquo; Koha</title>
10
<title>Holds awaiting pickup &rsaquo; Circulation &rsaquo; Koha</title>
Lines 72-77 Link Here
72
            [% END %]
73
            [% END %]
73
        [% END %]
74
        [% END %]
74
    [% ELSE %]
75
    [% ELSE %]
76
        [% IF enqueued %]
77
            <div class="dialog message">
78
                <p>The job has been enqueued! It will be processed as soon as possible.</p>
79
                <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>
80
            </div>
81
        [% END %]
75
        <div id="resultlist" class="toptabs">
82
        <div id="resultlist" class="toptabs">
76
            <ul>
83
            <ul>
77
                <li><a href="#holdswaiting">Holds waiting: [% reservecount | html %]</a></li>
84
                <li><a href="#holdswaiting">Holds waiting: [% reservecount | html %]</a></li>
Lines 83-89 Link Here
83
            </ul>
90
            </ul>
84
            <div id="holdswaiting">
91
            <div id="holdswaiting">
85
        [% IF ( reserveloop ) %]
92
        [% IF ( reserveloop ) %]
86
            [% INCLUDE waiting_holds.inc table_name='holdst' reserveloop=reserveloop tab='holdwaiting' %]
93
            <div id="toolbar" class="btn-toolbar">
94
                <button class="cancel_selected_holds" data-bulk="true"></button>
95
            </div>
96
            [% INCLUDE waiting_holds.inc select_column='1' table_name='holdst' reserveloop=reserveloop tab='holdwaiting' %]
87
        [% ELSE %]
97
        [% ELSE %]
88
            <div class="dialog message">No holds found.</div>
98
            <div class="dialog message">No holds found.</div>
89
        [% END %]
99
        [% END %]
Lines 92-97 Link Here
92
            [% IF ( ReservesMaxPickUpDelay ) %]<p>Holds listed here have been awaiting pickup for more than [% ReservesMaxPickUpDelay | html %] days.</p>[% END %]
102
            [% IF ( ReservesMaxPickUpDelay ) %]<p>Holds listed here have been awaiting pickup for more than [% ReservesMaxPickUpDelay | html %] days.</p>[% END %]
93
            [% IF ( overloop ) %]
103
            [% IF ( overloop ) %]
94
                <span id="holdsover-cancel-all">
104
                <span id="holdsover-cancel-all">
105
                   <button class="cancel_selected_holds" data-bulk="true"></button>
95
                   <form name="cancelAllReserve" action="waitingreserves.pl" method="post">
106
                   <form name="cancelAllReserve" action="waitingreserves.pl" method="post">
96
                       <input type="hidden" name="cancelall" value="1" />
107
                       <input type="hidden" name="cancelall" value="1" />
97
                       <input type="hidden" name="allbranches" value="[% allbranches | html %]" />
108
                       <input type="hidden" name="allbranches" value="[% allbranches | html %]" />
Lines 105-112 Link Here
105
                   [% UNLESS TransferWhenCancelAllWaitingHolds %]
116
                   [% UNLESS TransferWhenCancelAllWaitingHolds %]
106
                        Only items that need not be transferred will be cancelled (TransferWhenCancelAllWaitingHolds syspref)
117
                        Only items that need not be transferred will be cancelled (TransferWhenCancelAllWaitingHolds syspref)
107
                   [% END %]
118
                   [% END %]
119
108
                </span>
120
                </span>
109
               [% INCLUDE waiting_holds.inc table_name='holdso' reserveloop=overloop tab='holdsover' %]
121
               [% INCLUDE waiting_holds.inc select_column='1' table_name='holdso' reserveloop=overloop tab='holdsover' %]
110
            [% ELSE %]
122
            [% ELSE %]
111
                <div class="dialog message">No holds found.</div>
123
                <div class="dialog message">No holds found.</div>
112
            [% END %]
124
            [% END %]
Lines 128-152 Link Here
128
        </div> <!-- /.col-sm-12 -->
140
        </div> <!-- /.col-sm-12 -->
129
    </div> <!-- /.row -->
141
    </div> <!-- /.row -->
130
142
143
    <div id="cancelModal" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
144
        <div class="modal-dialog" role="document">
145
            <div class="modal-content">
146
                <div class="modal-header">
147
                    <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
148
                    <h3>Confirm deletion</h3>
149
                </div>
150
151
                <div class="modal-body">
152
                    <p>Are you sure you want to cancel this hold?</p>
153
154
                    <fieldset class="action">
155
                        [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
156
                        [% IF hold_cancellation %]
157
                            <label for="cancellation-reason">Cancellation reason: </label>
158
                            <select class="cancellation-reason" name="modal-cancellation-reason" id="modal-cancellation-reason">
159
                                <option value="">No reason given</option>
160
                                [% FOREACH reason IN hold_cancellation %]
161
                                    <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
162
                                [% END %]
163
                            </select>
164
                        [% END %]
165
                    </fieldset>
166
                </div>
167
168
                <div class="modal-footer">
169
                    <button id="cancelModalConfirmBtn" type="button" class="btn btn-danger">Confirm cancellation</button>
170
                    <a href="#" data-dismiss="modal">Cancel</a>
171
                </div>
172
            </div>
173
        </div>
174
    </div>
175
131
[% MACRO jsinclude BLOCK %]
176
[% MACRO jsinclude BLOCK %]
132
    [% INCLUDE 'datatables.inc' %]
177
    [% INCLUDE 'datatables.inc' %]
133
    [% INCLUDE 'columns_settings.inc' %]
178
    [% INCLUDE 'columns_settings.inc' %]
134
    <script>
179
    <script>
180
        var MSG_CANCEL_SELECTED = _("Cancel selected (%s)");
135
        var holdst_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdst', 'json' ) | $raw %];
181
        var holdst_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdst', 'json' ) | $raw %];
136
        var holdso_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdso', 'json' ) | $raw %];
182
        var holdso_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdso', 'json' ) | $raw %];
137
183
138
        $(document).ready(function() {
184
        $(document).ready(function() {
139
185
140
            KohaTable("holdst", {
186
            KohaTable("holdst", {
141
                "sPaginationType": "full"
187
                "sPaginationType": "full",
188
                "order": [[1, 'asc']]
142
            }, holdst_columns_settings);
189
            }, holdst_columns_settings);
143
190
144
            KohaTable("holdso", {
191
            KohaTable("holdso", {
145
                "sPaginationType": "full"
192
                "sPaginationType": "full",
193
                "order": [[1, 'asc']]
146
            }, holdso_columns_settings);
194
            }, holdso_columns_settings);
147
195
148
            $('#resultlist').tabs();
196
            $('#resultlist').tabs();
149
197
198
            let cancel_link;
199
200
            $("#cancelModalConfirmBtn").on("click",function(e) {
201
                var ids = cancel_link.data('ids');
202
                localStorage.selectedWaitingHolds = JSON.stringify(JSON.parse(localStorage.selectedWaitingHolds).filter(id => !ids.includes(id)));
203
                let link = `waitingreserves.pl?cancelBulk=1&amp;ids=${ids.join(',')}`;
204
                let reason = $("#modal-cancellation-reason").val();
205
                if ( reason ) {
206
                    link += "&amp;cancellation-reason=" + reason
207
                }
208
                window.location.href = link;
209
                return false;
210
            });
211
212
            if(!localStorage.selectedWaitingHolds || document.referrer.replace(/\?.*/, '') !== document.location.origin+document.location.pathname) {
213
                localStorage.selectedWaitingHolds = '[]';
214
            }
215
216
            try {
217
                JSON.parse(localStorage.selectedWaitingHolds);
218
            } catch(e) {
219
                localStorage.selectedWaitingHolds = '[]';
220
            }
221
222
            $('.holds_table .select_hold').each(function() {
223
                if(JSON.parse(localStorage.selectedWaitingHolds).includes($(this).data('id'))) {
224
                    $(this).prop('checked', true);
225
                }
226
            });
227
228
            $('.holds_table').each(function() {
229
              var table = $(this);
230
              var parent = table.parents('.ui-tabs-panel');
231
232
              $('.holds_table .select_hold_all', parent).each(function() {
233
                  var count = $('.select_hold:not(:checked)', table).length;
234
                  $('.select_hold_all', table).prop('checked', !count);
235
              });
236
237
              $('.cancel_selected_holds', parent).html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked', parent).length));
238
239
              $('.holds_table .select_hold_all', parent).click(function() {
240
                  var count = $('.select_hold:checked', table).length;
241
                  $('.select_hold', table).prop('checked', !count);
242
                  $(this).prop('checked', !count);
243
                  $('.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));
244
                  localStorage.selectedWaitingHolds = JSON.stringify($('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')));
245
              });
246
247
              $('.holds_table .select_hold', parent).click(function() {
248
                  var count = $('.select_hold:not(:checked)', table).length;
249
                  $('.select_hold_all', table).prop('checked', !count);
250
                  $('.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));
251
                  localStorage.selectedWaitingHolds = JSON.stringify($('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')));
252
              });
253
254
              $('.cancel_selected_holds', parent).click(function(e) {
255
                  e.preventDefault();
256
                  if($('.select_hold:checked', table).length) {
257
                      cancel_link = $(this);
258
                      $('#cancelModal').modal();
259
                  }
260
                  return false;
261
              });
262
            });
263
264
150
        });
265
        });
151
    </script>
266
    </script>
152
[% END %]
267
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-5 / +69 lines)
Lines 885-893 Link Here
885
                            <input type = "hidden" name="biblionumbers" value="[% biblionumbers | html %]"/>
885
                            <input type = "hidden" name="biblionumbers" value="[% biblionumbers | html %]"/>
886
                        [% END %]
886
                        [% END %]
887
887
888
                        [% IF enqueued %]
889
                            <div class="dialog message">
890
                                <p>The job has been enqueued! It will be processed as soon as possible.</p>
891
                                <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>
892
                            </div>
893
                        [% END %]
894
888
                        <h2>Existing holds</h2>
895
                        <h2>Existing holds</h2>
889
                        <div id="toolbar" class="btn-toolbar">
896
                        <div id="toolbar" class="btn-toolbar">
890
                            <input type="submit" name="submit" value="Update hold(s)" />
897
                            <input type="submit" name="submit" value="Update hold(s)" /> <button class="cancel_selected_holds" data-bulk="true"></button>
891
                        <fieldset id="cancellation-reason-fieldset" class="action">
898
                        <fieldset id="cancellation-reason-fieldset" class="action">
892
                            [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
899
                            [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
893
                            [% IF hold_cancellation %]
900
                            [% IF hold_cancellation %]
Lines 1092-1097 Link Here
1092
            cannotBeTransferred: _("Cannot be transferred to pickup library"),
1099
            cannotBeTransferred: _("Cannot be transferred to pickup library"),
1093
            pickupNotInHoldGroup: _("Only pickup locations within the same hold group are allowed")
1100
            pickupNotInHoldGroup: _("Only pickup locations within the same hold group are allowed")
1094
        }
1101
        }
1102
1103
        var MSG_CANCEL_SELECTED = _("Cancel selected (%s)");
1095
        columns_settings_borrowers_table = [% TablesSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %];
1104
        columns_settings_borrowers_table = [% TablesSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %];
1096
        $.fn.select2.defaults.set("width", "100%" );
1105
        $.fn.select2.defaults.set("width", "100%" );
1097
        $.fn.select2.defaults.set("dropdownAutoWidth", true );
1106
        $.fn.select2.defaults.set("dropdownAutoWidth", true );
Lines 1556-1566 Link Here
1556
                return false;
1565
                return false;
1557
            });
1566
            });
1558
            $("#cancelModalConfirmBtn").on("click",function(e) {
1567
            $("#cancelModalConfirmBtn").on("click",function(e) {
1559
                let borrowernumber = cancel_link.data('borrowernumber');
1568
                let link;
1560
                let biblionumber = cancel_link.data('biblionumber');
1569
                if(cancel_link.data('bulk')) {
1561
                let reserve_id = cancel_link.data('id');
1570
                    [% IF biblionumbers %]
1571
                        link = `request.pl?biblionumbers=[% biblionumbers | url %]&amp;action=cancelBulk&amp;ids=${$('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(',')}`;
1572
                    [% ELSE %]
1573
                        link = `request.pl?biblionumber=[% biblionumber | url %]&amp;action=cancelBulk&amp;ids=${$('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(',')}`;
1574
                    [% END %]
1575
                } else {
1576
                    let borrowernumber = cancel_link.data('borrowernumber');
1577
                    let biblionumber = cancel_link.data('biblionumber');
1578
                    let reserve_id = cancel_link.data('id');
1579
                    link = `request.pl?action=cancel&amp;borrowernumber=${ borrowernumber }&amp;biblionumber=${ biblionumber }&amp;reserve_id=${ reserve_id }`;
1580
                }
1562
                let reason = $("#modal-cancellation-reason").val();
1581
                let reason = $("#modal-cancellation-reason").val();
1563
                let link = `request.pl?action=cancel&amp;borrowernumber=${ borrowernumber }&amp;biblionumber=${ biblionumber }&amp;reserve_id=${ reserve_id }`;
1564
                if ( reason ) {
1582
                if ( reason ) {
1565
                    link += "&amp;cancellation-reason=" + reason
1583
                    link += "&amp;cancellation-reason=" + reason
1566
                }
1584
                }
Lines 1608-1613 Link Here
1608
                stickTo: "#existing_holds",
1626
                stickTo: "#existing_holds",
1609
                stickyClass: "floating"
1627
                stickyClass: "floating"
1610
            });
1628
            });
1629
1630
            if(!localStorage.selectedHolds  || document.referrer.replace(/\?.*/, '') !== document.location.origin+document.location.pathname) {
1631
                localStorage.selectedHolds = [];
1632
            }
1633
1634
            $('.holds_table .select_hold').each(function() {
1635
                if(localStorage.selectedHolds.includes($(this).data('id'))) {
1636
                    $(this).prop('checked', true);
1637
                }
1638
            });
1639
1640
            $('.holds_table .select_hold_all').each(function() {
1641
                var table = $(this).parents('.holds_table');
1642
                var count = $('.select_hold:not(:checked)', table).length;
1643
                $('.select_hold_all', table).prop('checked', !count);
1644
            });
1645
1646
            $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length));
1647
1648
            $('.holds_table .select_hold_all').click(function() {
1649
                var table = $(this).parents('.holds_table');
1650
                var count = $('.select_hold:checked', table).length;
1651
                $('.select_hold', table).prop('checked', !count);
1652
                $(this).prop('checked', !count);
1653
                $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length));
1654
                localStorage.selectedHolds = $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id'));
1655
            });
1656
1657
            $('.holds_table .select_hold').click(function() {
1658
                var table = $(this).parents('.holds_table');
1659
                var count = $('.select_hold:not(:checked)', table).length;
1660
                $('.select_hold_all', table).prop('checked', !count);
1661
                $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length));
1662
                localStorage.selectedHolds = $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id'));
1663
            });
1664
1665
            $('.cancel_selected_holds').click(function(e) {
1666
                e.preventDefault();
1667
                if($('.holds_table .select_hold:checked').length) {
1668
                    cancel_link = $(this);
1669
                    delete localStorage.selectedHolds;
1670
                    $('#cancelModal').modal();
1671
                }
1672
                return false;
1673
            });
1674
1611
        });
1675
        });
1612
    </script>
1676
    </script>
1613
[% END %]
1677
[% END %]
(-)a/misc/background_jobs_worker.pl (+1 lines)
Lines 33-38 my @job_types = qw( Link Here
33
    batch_authority_record_modification
33
    batch_authority_record_modification
34
    batch_biblio_record_deletion
34
    batch_biblio_record_deletion
35
    batch_authority_record_deletion
35
    batch_authority_record_deletion
36
    batch_hold_cancel
36
);
37
);
37
38
38
if ( $conn ) {
39
if ( $conn ) {
(-)a/reserve/request.pl (-25 / +46 lines)
Lines 52-57 use Koha::ItemTypes; Link Here
52
use Koha::Libraries;
52
use Koha::Libraries;
53
use Koha::Patrons;
53
use Koha::Patrons;
54
use Koha::Clubs;
54
use Koha::Clubs;
55
use Koha::BackgroundJob::BatchCancelHold;
55
56
56
my $dbh = C4::Context->dbh;
57
my $dbh = C4::Context->dbh;
57
my $input = CGI->new;
58
my $input = CGI->new;
Lines 89-118 my $action = $input->param('action'); Link Here
89
$action ||= q{};
90
$action ||= q{};
90
91
91
if ( $action eq 'move' ) {
92
if ( $action eq 'move' ) {
92
  my $where           = $input->param('where');
93
    my $where           = $input->param('where');
93
  my $reserve_id      = $input->param('reserve_id');
94
    my $reserve_id      = $input->param('reserve_id');
94
  my $prev_priority   = $input->param('prev_priority');
95
    my $prev_priority   = $input->param('prev_priority');
95
  my $next_priority   = $input->param('next_priority');
96
    my $next_priority   = $input->param('next_priority');
96
  my $first_priority  = $input->param('first_priority');
97
    my $first_priority  = $input->param('first_priority');
97
  my $last_priority   = $input->param('last_priority');
98
    my $last_priority   = $input->param('last_priority');
98
  my $hold_itemnumber = $input->param('itemnumber');
99
    my $hold_itemnumber = $input->param('itemnumber');
99
  if ( $prev_priority == 0 && $next_priority == 1 ){
100
    if ( $prev_priority == 0 && $next_priority == 1 ) {
100
      C4::Reserves::RevertWaitingStatus({ itemnumber => $hold_itemnumber });
101
        C4::Reserves::RevertWaitingStatus( { itemnumber => $hold_itemnumber } );
101
  } else {
102
    }
102
      AlterPriority( $where, $reserve_id, $prev_priority, $next_priority, $first_priority, $last_priority );
103
    else {
103
  }
104
        AlterPriority(
104
} elsif ( $action eq 'cancel' ) {
105
            $where,         $reserve_id,     $prev_priority,
105
  my $reserve_id = $input->param('reserve_id');
106
            $next_priority, $first_priority, $last_priority
106
  my $cancellation_reason = $input->param("cancellation-reason");
107
        );
107
  my $hold = Koha::Holds->find( $reserve_id );
108
    }
108
  $hold->cancel({ cancellation_reason => $cancellation_reason }) if $hold;
109
}
109
} elsif ( $action eq 'setLowestPriority' ) {
110
elsif ( $action eq 'cancel' ) {
110
  my $reserve_id = $input->param('reserve_id');
111
    my $reserve_id          = $input->param('reserve_id');
111
  ToggleLowestPriority( $reserve_id );
112
    my $cancellation_reason = $input->param("cancellation-reason");
112
} elsif ( $action eq 'toggleSuspend' ) {
113
    my $hold                = Koha::Holds->find($reserve_id);
113
  my $reserve_id = $input->param('reserve_id');
114
    $hold->cancel( { cancellation_reason => $cancellation_reason } ) if $hold;
114
  my $suspend_until  = $input->param('suspend_until');
115
}
115
  ToggleSuspend( $reserve_id, $suspend_until );
116
elsif ( $action eq 'setLowestPriority' ) {
117
    my $reserve_id = $input->param('reserve_id');
118
    ToggleLowestPriority($reserve_id);
119
}
120
elsif ( $action eq 'toggleSuspend' ) {
121
    my $reserve_id    = $input->param('reserve_id');
122
    my $suspend_until = $input->param('suspend_until');
123
    ToggleSuspend( $reserve_id, $suspend_until );
124
}
125
elsif ( $action eq 'cancelBulk' ) {
126
    my $cancellation_reason = $input->param("cancellation-reason");
127
    my @hold_ids            = split ',', $input->param("ids");
128
    my $params              = {
129
        reason   => $cancellation_reason,
130
        hold_ids => \@hold_ids,
131
    };
132
    my $job_id = Koha::BackgroundJob::BatchCancelHold->new->enqueue($params);
133
134
    $template->param(
135
        enqueued => 1,
136
        job_id   => $job_id
137
    );
116
}
138
}
117
139
118
if ($findborrower) {
140
if ($findborrower) {
119
- 

Return to bug 23678