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 (+142 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;
79
        my $error = eval {
80
            $hold = Koha::Holds->find($hold_id);
81
            $hold->cancel({cancellation_reason => $args->{reason}});
82
        };
83
        my $patron = $hold->patron;
84
        my $biblio = $hold->biblio;
85
        if ( $error and $error != $hold or $@ ) {
86
            push @messages, {
87
                type => 'error',
88
                code => 'hold_not_cancelled',
89
                patron_id => $patron->borrowernumber,
90
                patron_name => ($patron->firstname?$patron->firstname.', ':'').$patron->surname,
91
                biblio_id => $biblio->biblionumber,
92
                biblio_title => $biblio->title,
93
                hold_id => $hold_id,
94
                error => ($@ ? $@ : 0),
95
            };
96
        } else {
97
            push @messages, {
98
                type => 'success',
99
                code => 'hold_cancelled',
100
                patron_id => $patron->borrowernumber,
101
                patron_name => ($patron->firstname?$patron->firstname.', ':'').$patron->surname,
102
                biblio_id => $biblio->biblionumber,
103
                biblio_title => $biblio->title,
104
                hold_id => $hold_id,
105
            };
106
            $report->{total_success}++;
107
        }
108
        $job->progress( ++$job_progress )->store;
109
    }
110
111
    my $job_data = decode_json $job->data;
112
    $job_data->{messages} = \@messages;
113
    $job_data->{report} = $report;
114
115
    $job->ended_on(dt_from_string)
116
        ->data(encode_json $job_data);
117
    $job->status('finished') if $job->status ne 'cancelled';
118
    $job->store;
119
120
}
121
122
=head3 enqueue
123
124
Enqueue the new job
125
126
=cut
127
128
sub enqueue {
129
    my ( $self, $args) = @_;
130
131
    # TODO Raise exception instead
132
    return unless exists $args->{hold_ids};
133
134
    my @hold_ids = @{ $args->{hold_ids} };
135
136
    $self->SUPER::enqueue({
137
        job_size => scalar @hold_ids,
138
        job_args => {hold_ids => \@hold_ids, reason => $args->{reason}}
139
    });
140
}
141
142
1;
(-)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/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
                                Hold on <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% m.biblio_id | uri %]">[% m.biblio_title | html %]</a> for <a href="/cgi-bin/koha/members/moremember.pl?borrowernumber=[% m.patron_id | uri %]">[% m.patron_name %]</a> has not been cancelled. An error occurred on modifying it.[% IF m.error %] ([% m.error | html %])[% END %].
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/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) {
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