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

(-)a/Koha/BackgroundJob.pm (+3 lines)
Lines 26-31 use Koha::DateUtils qw( dt_from_string ); Link Here
26
use Koha::Exceptions;
26
use Koha::Exceptions;
27
use Koha::BackgroundJob::BatchUpdateBiblio;
27
use Koha::BackgroundJob::BatchUpdateBiblio;
28
use Koha::BackgroundJob::BatchUpdateAuthority;
28
use Koha::BackgroundJob::BatchUpdateAuthority;
29
use Koha::BackgroundJob::BatchCancelHold;
29
30
30
use base qw( Koha::Object );
31
use base qw( Koha::Object );
31
32
Lines 155-160 sub process { Link Here
155
      ? Koha::BackgroundJob::BatchUpdateBiblio->process($args)
156
      ? Koha::BackgroundJob::BatchUpdateBiblio->process($args)
156
      : $job_type eq 'batch_authority_record_modification'
157
      : $job_type eq 'batch_authority_record_modification'
157
      ? Koha::BackgroundJob::BatchUpdateAuthority->process($args)
158
      ? Koha::BackgroundJob::BatchUpdateAuthority->process($args)
159
      : $job_type eq 'batch_hold_cancel'
160
      ? Koha::BackgroundJob::BatchCancelHold->process($args)
158
      : Koha::Exceptions::Exception->throw('->process called without valid job_type');
161
      : Koha::Exceptions::Exception->throw('->process called without valid job_type');
159
}
162
}
160
163
(-)a/Koha/BackgroundJob/BatchCancelHold.pm (+144 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)
64
        ->progress($job_progress)
65
        ->status('started')
66
        ->store;
67
68
    my @hold_ids = @{ $args->{hold_ids} };
69
70
    my $report = {
71
        total_holds => scalar @hold_ids,
72
        total_success => 0,
73
    };
74
    my @messages;
75
    HOLD_IDS: for my $hold_id ( sort { $a <=> $b } @hold_ids ) {
76
        next unless $hold_id;
77
        # Authorities
78
        my ($hold, $patron, $biblio);
79
        $hold = Koha::Holds->find($hold_id);
80
81
        my $error = eval {
82
            $patron = $hold->patron;
83
            $biblio = $hold->biblio;
84
            $hold->cancel({cancellation_reason => $args->{reason}});
85
        };
86
87
        if ( $error and $error != $hold or $@ ) {
88
            push @messages, {
89
                type => 'error',
90
                code => 'hold_not_cancelled',
91
                patron_id => defined $patron?$patron->borrowernumber:'',
92
                patron_name => defined $patron?($patron->firstname?$patron->firstname.', ':'').$patron->surname:'',
93
                biblio_id => defined $biblio?$biblio->biblionumber:'',
94
                biblio_title => defined $biblio?$biblio->title:'',
95
                hold_id => $hold_id,
96
                error => defined $hold?($@ ? $@ : 0):'No hold with id '.$hold_id.' found',
97
            };
98
        } else {
99
            push @messages, {
100
                type => 'success',
101
                code => 'hold_cancelled',
102
                patron_id => $patron->borrowernumber,
103
                patron_name => ($patron->firstname?$patron->firstname.', ':'').$patron->surname,
104
                biblio_id => $biblio->biblionumber,
105
                biblio_title => $biblio->title,
106
                hold_id => $hold_id,
107
            };
108
            $report->{total_success}++;
109
        }
110
        $job->progress( ++$job_progress )->store;
111
    }
112
113
    my $job_data = decode_json $job->data;
114
    $job_data->{messages} = \@messages;
115
    $job_data->{report} = $report;
116
117
    $job->ended_on(dt_from_string)
118
        ->data(encode_json $job_data);
119
    $job->status('finished') if $job->status ne 'cancelled';
120
    $job->store;
121
122
}
123
124
=head3 enqueue
125
126
Enqueue the new job
127
128
=cut
129
130
sub enqueue {
131
    my ( $self, $args) = @_;
132
133
    # TODO Raise exception instead
134
    return unless exists $args->{hold_ids};
135
136
    my @hold_ids = @{ $args->{hold_ids} };
137
138
    $self->SUPER::enqueue({
139
        job_size => scalar @hold_ids,
140
        job_args => {hold_ids => \@hold_ids, reason => $args->{reason}}
141
    });
142
}
143
144
1;
(-)a/circ/waitingreserves.pl (+17 lines)
Lines 39-44 use Koha::BiblioFrameworks; Link Here
39
use Koha::Items;
39
use Koha::Items;
40
use Koha::ItemTypes;
40
use Koha::ItemTypes;
41
use Koha::Patrons;
41
use Koha::Patrons;
42
use Koha::BackgroundJob::BatchCancelHold;
42
43
43
my $input = CGI->new;
44
my $input = CGI->new;
44
45
Lines 49-54 my $tbr = $input->param('tbr') || ''; Link Here
49
my $all_branches   = $input->param('allbranches') || '';
50
my $all_branches   = $input->param('allbranches') || '';
50
my $cancelall      = $input->param('cancelall');
51
my $cancelall      = $input->param('cancelall');
51
my $tab            = $input->param('tab');
52
my $tab            = $input->param('tab');
53
my $cancelBulk     = $input->param('cancelBulk');
52
54
53
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
55
my ( $template, $loggedinuser, $cookie, $flags ) = get_template_and_user(
54
    {
56
    {
Lines 80-85 if ( C4::Context->preference('IndependentBranches') ) { Link Here
80
}
82
}
81
$template->param( all_branches => 1 ) if $all_branches;
83
$template->param( all_branches => 1 ) if $all_branches;
82
84
85
if ( $cancelBulk ) {
86
  my $cancellation_reason = $input->param("cancellation-reason");
87
  my @hold_ids = split ',', $input->param("ids");
88
  my $params = {
89
      reason       => $cancellation_reason,
90
      hold_ids  => \@hold_ids,
91
  };
92
  my $job_id = Koha::BackgroundJob::BatchCancelHold->new->enqueue($params);
93
94
  $template->param(
95
    enqueued => 1,
96
    job_id => $job_id
97
  )
98
}
99
83
my (@reserve_loop, @over_loop);
100
my (@reserve_loop, @over_loop);
84
# FIXME - Is priority => 0 useful? If yes it must be moved to waiting, otherwise we need to remove it from here.
101
# FIXME - Is priority => 0 useful? If yes it must be moved to waiting, otherwise we need to remove it from here.
85
my $holds = Koha::Holds->waiting->search({ priority => 0, ( $all_branches ? () : ( branchcode => $default ) ) }, { order_by => ['waitingdate'] });
102
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 28-33 Link Here
28
    [% FOREACH hold IN holds %]
29
    [% FOREACH hold IN holds %]
29
    [% IF !hold.found && first_priority == 0 %][% first_priority = hold.priority %][% END %]
30
    [% IF !hold.found && first_priority == 0 %][% first_priority = hold.priority %][% END %]
30
        <tr>
31
        <tr>
32
            <th><input type="checkbox" class="select_hold" data-id="[% hold.reserve_id | html %]"/></th>
31
            <td>
33
            <td>
32
                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
34
                <input type="hidden" name="reserve_id" value="[% hold.reserve_id | html %]" />
33
                <input type="hidden" name="borrowernumber" value="[% hold.borrowernumber | html %]" />
35
                <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/admin/background_jobs.tt (+32 lines)
Lines 85-90 Link Here
85
                            </div>
85
                            </div>
86
                        [% END %]
86
                        [% END %]
87
                    [% END %]
87
                    [% END %]
88
                [% CASE 'batch_hold_cancel' %]
89
                    [% SET report = job.report %]
90
                    [% IF report %]
91
                        [% IF report.total_holds == report.total_success %]
92
                            <div class="dialog message">
93
                                All holds have successfully been cancelled!
94
                            </div>
95
                        [% ELSE %]
96
                            <div class="dialog message">
97
                                [% report.total_success | html %] / [% report.total_holds | html %] holds have successfully been modified. Some errors occurred.
98
                                [% IF job.status == 'cancelled' %]The job has been cancelled before it finished.[% END %]
99
                            </div>
100
                        [% END %]
101
                    [% END %]
88
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
102
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
89
                [% END %]
103
                [% END %]
90
            </li>
104
            </li>
Lines 126-131 Link Here
126
                            [% END %]
140
                            [% END %]
127
                        </div>
141
                        </div>
128
                    [% END %]
142
                    [% END %]
143
                [% CASE 'batch_hold_cancel' %]
144
                    [% FOR m IN job.messages %]
145
                        <div class="dialog message">
146
                            [% IF m.type == 'success' %]
147
                                <i class="fa fa-check success"></i>
148
                            [% ELSIF m.type == 'warning' %]
149
                                <i class="fa fa-warning warn"></i>
150
                            [% ELSIF m.type == 'error' %]
151
                                <i class="fa fa-exclamation error"></i>
152
                            [% END %]
153
                            [% SWITCH m.code %]
154
                            [% CASE 'hold_not_cancelled' %]
155
                                [% 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 %].
156
                            [% CASE 'hold_cancelled' %]
157
                                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.
158
                            [% END %]
159
                        </div>
160
                    [% END %]
129
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
161
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
130
                [% END %]
162
                [% END %]
131
            </li>
163
            </li>
(-)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>Koha &rsaquo; Circulation &rsaquo; Holds awaiting pickup</title>
10
<title>Koha &rsaquo; Circulation &rsaquo; Holds awaiting pickup</title>
Lines 63-68 Link Here
63
            [% END %]
64
            [% END %]
64
        [% END %]
65
        [% END %]
65
    [% ELSE %]
66
    [% ELSE %]
67
        [% IF enqueued %]
68
            <div class="dialog message">
69
                <p>The job has been enqueued! It will be processed as soon as possible.</p>
70
                <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>
71
            </div>
72
        [% END %]
66
        <div id="resultlist" class="toptabs">
73
        <div id="resultlist" class="toptabs">
67
            <ul>
74
            <ul>
68
                <li><a href="#holdswaiting">Holds waiting: [% reservecount | html %]</a></li>
75
                <li><a href="#holdswaiting">Holds waiting: [% reservecount | html %]</a></li>
Lines 74-80 Link Here
74
            </ul>
81
            </ul>
75
            <div id="holdswaiting">
82
            <div id="holdswaiting">
76
        [% IF ( reserveloop ) %]
83
        [% IF ( reserveloop ) %]
77
            [% INCLUDE waiting_holds.inc table_name='holdst' reserveloop=reserveloop tab='holdwaiting' %]
84
            <div id="toolbar" class="btn-toolbar">
85
                <button class="cancel_selected_holds" data-bulk="true"></button>
86
            </div>
87
            [% INCLUDE waiting_holds.inc select_column='1' table_name='holdst' reserveloop=reserveloop tab='holdwaiting' %]
78
        [% ELSE %]
88
        [% ELSE %]
79
            <div class="dialog message">No holds found.</div>
89
            <div class="dialog message">No holds found.</div>
80
        [% END %]
90
        [% END %]
Lines 83-88 Link Here
83
            [% IF ( ReservesMaxPickUpDelay ) %]<p>Holds listed here have been awaiting pickup for more than [% ReservesMaxPickUpDelay | html %] days.</p>[% END %]
93
            [% IF ( ReservesMaxPickUpDelay ) %]<p>Holds listed here have been awaiting pickup for more than [% ReservesMaxPickUpDelay | html %] days.</p>[% END %]
84
            [% IF ( overloop ) %]
94
            [% IF ( overloop ) %]
85
                <span id="holdsover-cancel-all">
95
                <span id="holdsover-cancel-all">
96
                   <button class="cancel_selected_holds" data-bulk="true"></button>
86
                   <form name="cancelAllReserve" action="waitingreserves.pl" method="post">
97
                   <form name="cancelAllReserve" action="waitingreserves.pl" method="post">
87
                       <input type="hidden" name="cancelall" value="1" />
98
                       <input type="hidden" name="cancelall" value="1" />
88
                       <input type="hidden" name="allbranches" value="[% allbranches | html %]" />
99
                       <input type="hidden" name="allbranches" value="[% allbranches | html %]" />
Lines 96-103 Link Here
96
                   [% UNLESS TransferWhenCancelAllWaitingHolds %]
107
                   [% UNLESS TransferWhenCancelAllWaitingHolds %]
97
                        Only items that need not be transferred will be cancelled (TransferWhenCancelAllWaitingHolds syspref)
108
                        Only items that need not be transferred will be cancelled (TransferWhenCancelAllWaitingHolds syspref)
98
                   [% END %]
109
                   [% END %]
110
99
                </span>
111
                </span>
100
               [% INCLUDE waiting_holds.inc table_name='holdso' reserveloop=overloop tab='holdsover' %]
112
               [% INCLUDE waiting_holds.inc select_column='1' table_name='holdso' reserveloop=overloop tab='holdsover' %]
101
            [% ELSE %]
113
            [% ELSE %]
102
                <div class="dialog message">No holds found.</div>
114
                <div class="dialog message">No holds found.</div>
103
            [% END %]
115
            [% END %]
Lines 119-143 Link Here
119
        </div> <!-- /.col-sm-12 -->
131
        </div> <!-- /.col-sm-12 -->
120
    </div> <!-- /.row -->
132
    </div> <!-- /.row -->
121
133
134
    <div id="cancelModal" class="modal" tabindex="-1" role="dialog" aria-hidden="true">
135
        <div class="modal-dialog" role="document">
136
            <div class="modal-content">
137
                <div class="modal-header">
138
                    <button type="button" class="closebtn" data-dismiss="modal" aria-hidden="true">×</button>
139
                    <h3>Confirm deletion</h3>
140
                </div>
141
142
                <div class="modal-body">
143
                    <p>Are you sure you want to cancel this hold?</p>
144
145
                    <fieldset class="action">
146
                        [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
147
                        [% IF hold_cancellation %]
148
                            <label for="cancellation-reason">Cancellation reason: </label>
149
                            <select class="cancellation-reason" name="modal-cancellation-reason" id="modal-cancellation-reason">
150
                                <option value="">No reason given</option>
151
                                [% FOREACH reason IN hold_cancellation %]
152
                                    <option value="[% reason.authorised_value | html %]">[% reason.lib | html %]</option>
153
                                [% END %]
154
                            </select>
155
                        [% END %]
156
                    </fieldset>
157
                </div>
158
159
                <div class="modal-footer">
160
                    <button id="cancelModalConfirmBtn" type="button" class="btn btn-danger">Confirm cancellation</button>
161
                    <a href="#" data-dismiss="modal">Cancel</a>
162
                </div>
163
            </div>
164
        </div>
165
    </div>
166
122
[% MACRO jsinclude BLOCK %]
167
[% MACRO jsinclude BLOCK %]
123
    [% INCLUDE 'datatables.inc' %]
168
    [% INCLUDE 'datatables.inc' %]
124
    [% INCLUDE 'columns_settings.inc' %]
169
    [% INCLUDE 'columns_settings.inc' %]
125
    <script>
170
    <script>
171
        var MSG_CANCEL_SELECTED = _("Cancel selected (%s)");
126
        var holdst_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdst', 'json' ) | $raw %];
172
        var holdst_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdst', 'json' ) | $raw %];
127
        var holdso_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdso', 'json' ) | $raw %];
173
        var holdso_columns_settings = [% TablesSettings.GetColumns( 'circ', 'holds_awaiting_pickup', 'holdso', 'json' ) | $raw %];
128
174
129
        $(document).ready(function() {
175
        $(document).ready(function() {
130
176
131
            KohaTable("holdst", {
177
            KohaTable("holdst", {
132
                "sPaginationType": "full"
178
                "sPaginationType": "full",
179
                "order": [[1, 'asc']]
133
            }, holdst_columns_settings);
180
            }, holdst_columns_settings);
134
181
135
            KohaTable("holdso", {
182
            KohaTable("holdso", {
136
                "sPaginationType": "full"
183
                "sPaginationType": "full",
184
                "order": [[1, 'asc']]
137
            }, holdso_columns_settings);
185
            }, holdso_columns_settings);
138
186
139
            $('#resultlist').tabs();
187
            $('#resultlist').tabs();
140
188
189
            let cancel_link;
190
191
            $("#cancelModalConfirmBtn").on("click",function(e) {
192
                var ids = cancel_link.data('ids');
193
                localStorage.selectedWaitingHolds = JSON.stringify(JSON.parse(localStorage.selectedWaitingHolds).filter(id => !ids.includes(id)));
194
                let link = `waitingreserves.pl?cancelBulk=1&amp;ids=${ids.join(',')}`;
195
                let reason = $("#modal-cancellation-reason").val();
196
                if ( reason ) {
197
                    link += "&amp;cancellation-reason=" + reason
198
                }
199
                window.location.href = link;
200
                return false;
201
            });
202
203
            if(!localStorage.selectedWaitingHolds || document.referrer.replace(/\?.*/, '') !== document.location.origin+document.location.pathname) {
204
                localStorage.selectedWaitingHolds = '[]';
205
            }
206
207
            try {
208
                JSON.parse(localStorage.selectedWaitingHolds);
209
            } catch(e) {
210
                localStorage.selectedWaitingHolds = '[]';
211
            }
212
213
            $('.holds_table .select_hold').each(function() {
214
                if(JSON.parse(localStorage.selectedWaitingHolds).includes($(this).data('id'))) {
215
                    $(this).prop('checked', true);
216
                }
217
            });
218
219
            $('.holds_table').each(function() {
220
              var table = $(this);
221
              var parent = table.parents('.ui-tabs-panel');
222
223
              $('.holds_table .select_hold_all', parent).each(function() {
224
                  var count = $('.select_hold:not(:checked)', table).length;
225
                  $('.select_hold_all', table).prop('checked', !count);
226
              });
227
228
              $('.cancel_selected_holds', parent).html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked', parent).length));
229
230
              $('.holds_table .select_hold_all', parent).click(function() {
231
                  var count = $('.select_hold:checked', table).length;
232
                  $('.select_hold', table).prop('checked', !count);
233
                  $(this).prop('checked', !count);
234
                  $('.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));
235
                  localStorage.selectedWaitingHolds = JSON.stringify($('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')));
236
              });
237
238
              $('.holds_table .select_hold', parent).click(function() {
239
                  var count = $('.select_hold:not(:checked)', table).length;
240
                  $('.select_hold_all', table).prop('checked', !count);
241
                  $('.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));
242
                  localStorage.selectedWaitingHolds = JSON.stringify($('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')));
243
              });
244
245
              $('.cancel_selected_holds', parent).click(function(e) {
246
                  e.preventDefault();
247
                  if($('.select_hold:checked', table).length) {
248
                      cancel_link = $(this);
249
                      $('#cancelModal').modal();
250
                  }
251
                  return false;
252
              });
253
            });
254
255
141
        });
256
        });
142
    </script>
257
    </script>
143
[% END %]
258
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/reserve/request.tt (-5 / +69 lines)
Lines 808-816 Link Here
808
                            <input type = "hidden" name="biblionumbers" value="[% biblionumbers | html %]"/>
808
                            <input type = "hidden" name="biblionumbers" value="[% biblionumbers | html %]"/>
809
                        [% END %]
809
                        [% END %]
810
810
811
                        [% IF enqueued %]
812
                            <div class="dialog message">
813
                                <p>The job has been enqueued! It will be processed as soon as possible.</p>
814
                                <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>
815
                            </div>
816
                        [% END %]
817
811
                        <h2>Existing holds</h2>
818
                        <h2>Existing holds</h2>
812
                        <div id="toolbar" class="btn-toolbar">
819
                        <div id="toolbar" class="btn-toolbar">
813
                            <input type="submit" name="submit" value="Update hold(s)" />
820
                            <input type="submit" name="submit" value="Update hold(s)" /> <button class="cancel_selected_holds" data-bulk="true"></button>
814
                        <fieldset id="cancellation-reason-fieldset" class="action">
821
                        <fieldset id="cancellation-reason-fieldset" class="action">
815
                            [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
822
                            [% SET hold_cancellation = AuthorisedValues.GetAuthValueDropbox('HOLD_CANCELLATION') %]
816
                            [% IF hold_cancellation %]
823
                            [% IF hold_cancellation %]
Lines 1015-1020 Link Here
1015
            cannotBeTransferred: _("Cannot be transferred to pickup library"),
1022
            cannotBeTransferred: _("Cannot be transferred to pickup library"),
1016
            pickupNotInHoldGroup: _("Only pickup locations within the same hold group are allowed")
1023
            pickupNotInHoldGroup: _("Only pickup locations within the same hold group are allowed")
1017
        }
1024
        }
1025
1026
        var MSG_CANCEL_SELECTED = _("Cancel selected (%s)");
1018
        columns_settings_borrowers_table = [% TablesSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %];
1027
        columns_settings_borrowers_table = [% TablesSettings.GetColumns( 'circ', 'circulation', 'table_borrowers', 'json' ) | $raw %];
1019
        $.fn.select2.defaults.set("width", "100%" );
1028
        $.fn.select2.defaults.set("width", "100%" );
1020
        $.fn.select2.defaults.set("dropdownAutoWidth", true );
1029
        $.fn.select2.defaults.set("dropdownAutoWidth", true );
Lines 1339-1349 Link Here
1339
                return false;
1348
                return false;
1340
            });
1349
            });
1341
            $("#cancelModalConfirmBtn").on("click",function(e) {
1350
            $("#cancelModalConfirmBtn").on("click",function(e) {
1342
                let borrowernumber = cancel_link.data('borrowernumber');
1351
                let link;
1343
                let biblionumber = cancel_link.data('biblionumber');
1352
                if(cancel_link.data('bulk')) {
1344
                let reserve_id = cancel_link.data('id');
1353
                    [% IF biblionumbers %]
1354
                        link = `request.pl?biblionumbers=[% biblionumbers | url %]&amp;action=cancelBulk&amp;ids=${$('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(',')}`;
1355
                    [% ELSE %]
1356
                        link = `request.pl?biblionumber=[% biblionumber | url %]&amp;action=cancelBulk&amp;ids=${$('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id')).join(',')}`;
1357
                    [% END %]
1358
                } else {
1359
                    let borrowernumber = cancel_link.data('borrowernumber');
1360
                    let biblionumber = cancel_link.data('biblionumber');
1361
                    let reserve_id = cancel_link.data('id');
1362
                    link = `request.pl?action=cancel&amp;borrowernumber=${ borrowernumber }&amp;biblionumber=${ biblionumber }&amp;reserve_id=${ reserve_id }`;
1363
                }
1345
                let reason = $("#modal-cancellation-reason").val();
1364
                let reason = $("#modal-cancellation-reason").val();
1346
                let link = `request.pl?action=cancel&amp;borrowernumber=${ borrowernumber }&amp;biblionumber=${ biblionumber }&amp;reserve_id=${ reserve_id }`;
1347
                if ( reason ) {
1365
                if ( reason ) {
1348
                    link += "&amp;cancellation-reason=" + reason
1366
                    link += "&amp;cancellation-reason=" + reason
1349
                }
1367
                }
Lines 1391-1396 Link Here
1391
                stickTo: "#existing_holds",
1409
                stickTo: "#existing_holds",
1392
                stickyClass: "floating"
1410
                stickyClass: "floating"
1393
            });
1411
            });
1412
1413
            if(!localStorage.selectedHolds  || document.referrer.replace(/\?.*/, '') !== document.location.origin+document.location.pathname) {
1414
                localStorage.selectedHolds = [];
1415
            }
1416
1417
            $('.holds_table .select_hold').each(function() {
1418
                if(localStorage.selectedHolds.includes($(this).data('id'))) {
1419
                    $(this).prop('checked', true);
1420
                }
1421
            });
1422
1423
            $('.holds_table .select_hold_all').each(function() {
1424
                var table = $(this).parents('.holds_table');
1425
                var count = $('.select_hold:not(:checked)', table).length;
1426
                $('.select_hold_all', table).prop('checked', !count);
1427
            });
1428
1429
            $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length));
1430
1431
            $('.holds_table .select_hold_all').click(function() {
1432
                var table = $(this).parents('.holds_table');
1433
                var count = $('.select_hold:checked', table).length;
1434
                $('.select_hold', table).prop('checked', !count);
1435
                $(this).prop('checked', !count);
1436
                $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length));
1437
                localStorage.selectedHolds = $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id'));
1438
            });
1439
1440
            $('.holds_table .select_hold').click(function() {
1441
                var table = $(this).parents('.holds_table');
1442
                var count = $('.select_hold:not(:checked)', table).length;
1443
                $('.select_hold_all', table).prop('checked', !count);
1444
                $('.cancel_selected_holds').html(MSG_CANCEL_SELECTED.format($('.holds_table .select_hold:checked').length));
1445
                localStorage.selectedHolds = $('.holds_table .select_hold:checked').toArray().map(el => $(el).data('id'));
1446
            });
1447
1448
            $('.cancel_selected_holds').click(function(e) {
1449
                e.preventDefault();
1450
                if($('.holds_table .select_hold:checked').length) {
1451
                    cancel_link = $(this);
1452
                    delete localStorage.selectedHolds;
1453
                    $('#cancelModal').modal();
1454
                }
1455
                return false;
1456
            });
1457
1394
        });
1458
        });
1395
    </script>
1459
    </script>
1396
[% END %]
1460
[% END %]
(-)a/misc/background_jobs_worker.pl (-1 / +1 lines)
Lines 28-34 try { Link Here
28
    warn sprintf "Cannot connect to the message broker, the jobs will be processed anyway (%s)", $_;
28
    warn sprintf "Cannot connect to the message broker, the jobs will be processed anyway (%s)", $_;
29
};
29
};
30
30
31
my @job_types = qw( batch_biblio_record_modification batch_authority_record_modification );
31
my @job_types = qw( batch_biblio_record_modification batch_authority_record_modification batch_hold_cancel );
32
32
33
if ( $conn ) {
33
if ( $conn ) {
34
    # FIXME cf note in Koha::BackgroundJob about $namespace
34
    # FIXME cf note in Koha::BackgroundJob about $namespace
(-)a/reserve/request.pl (-1 / +14 lines)
Lines 54-59 use Koha::ItemTypes; Link Here
54
use Koha::Libraries;
54
use Koha::Libraries;
55
use Koha::Patrons;
55
use Koha::Patrons;
56
use Koha::Clubs;
56
use Koha::Clubs;
57
use Koha::BackgroundJob::BatchCancelHold;
57
58
58
my $dbh = C4::Context->dbh;
59
my $dbh = C4::Context->dbh;
59
my $input = CGI->new;
60
my $input = CGI->new;
Lines 115-120 if ( $action eq 'move' ) { Link Here
115
  my $reserve_id = $input->param('reserve_id');
116
  my $reserve_id = $input->param('reserve_id');
116
  my $suspend_until  = $input->param('suspend_until');
117
  my $suspend_until  = $input->param('suspend_until');
117
  ToggleSuspend( $reserve_id, $suspend_until );
118
  ToggleSuspend( $reserve_id, $suspend_until );
119
} elsif ( $action eq 'cancelBulk') {
120
  my $cancellation_reason = $input->param("cancellation-reason");
121
  my @hold_ids = split ',', $input->param("ids");
122
  my $params = {
123
      reason       => $cancellation_reason,
124
      hold_ids  => \@hold_ids,
125
  };
126
  my $job_id = Koha::BackgroundJob::BatchCancelHold->new->enqueue($params);
127
128
  $template->param(
129
    enqueued => 1,
130
    job_id => $job_id
131
  )
118
}
132
}
119
133
120
if ($findborrower) {
134
if ($findborrower) {
121
- 

Return to bug 23678