View | Details | Raw Unified | Return to bug 26080
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::BatchDeleteBiblio;
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_biblio_record_deletion'
160
      ? Koha::BackgroundJob::BatchDeleteBiblio->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/BatchDeleteBiblio.pm (+158 lines)
Line 0 Link Here
1
package Koha::BackgroundJob::BatchDeleteBiblio;
2
3
use Modern::Perl;
4
use JSON qw( encode_json decode_json );
5
6
use Koha::BackgroundJobs;
7
use Koha::DateUtils qw( dt_from_string );
8
use C4::Biblio;
9
10
use base 'Koha::BackgroundJob';
11
12
sub job_type {
13
    return 'batch_biblio_record_deletion';
14
}
15
16
sub process {
17
    my ( $self, $args ) = @_;
18
19
    my $job_type = $args->{job_type};
20
21
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
22
23
    if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) {
24
        return;
25
    }
26
27
    # FIXME If the job has already been started, but started again (worker has been restart for instance)
28
    # Then we will start from scratch and so double delete the same records
29
30
    my $job_progress = 0;
31
    $job->started_on(dt_from_string)
32
        ->progress($job_progress)
33
        ->status('started')
34
        ->store;
35
36
    my $mmtid = $args->{mmtid};
37
    my @record_ids = @{ $args->{record_ids} };
38
39
    my $report = {
40
        total_records => scalar @record_ids,
41
        total_success => 0,
42
    };
43
    my @messages;
44
    my $schema = Koha::Database->new->schema;
45
    RECORD_IDS: for my $record_id ( sort { $a <=> $b } @record_ids ) {
46
47
        last if $job->get_from_storage->status eq 'cancelled';
48
49
        next unless $record_id;
50
51
        $schema->storage->txn_begin;
52
53
        my $biblionumber = $record_id;
54
        # First, checking if issues exist.
55
        # If yes, nothing to do
56
        my $biblio = Koha::Biblios->find( $biblionumber );
57
58
        # TODO Replace with $biblio->get_issues->count
59
        if ( C4::Biblio::CountItemsIssued( $biblionumber ) ) {
60
            push @messages, {
61
                type => 'warning',
62
                code => 'item_issued',
63
                biblionumber => $biblionumber,
64
            };
65
            $schema->storage->txn_rollback;
66
            $job->progress( ++$job_progress )->store;
67
            next;
68
        }
69
70
        # Cancel reserves
71
        my $holds = $biblio->holds;
72
        while ( my $hold = $holds->next ) {
73
            eval{
74
                $hold->cancel;
75
            };
76
            if ( $@ ) {
77
                push @messages, {
78
                    type => 'error',
79
                    code => 'reserve_not_cancelled',
80
                    biblionumber => $biblionumber,
81
                    reserve_id => $hold->reserve_id,
82
                    error => $@,
83
                };
84
                $schema->storage->txn_rollback;
85
                $job->progress( ++$job_progress )->store;
86
                next RECORD_IDS;
87
            }
88
        }
89
90
        # Delete items
91
        my $items = Koha::Items->search({ biblionumber => $biblionumber });
92
        while ( my $item = $items->next ) {
93
            my $error = $item->safe_delete;
94
            if(ref($error) ne 'Koha::Item'){
95
                push @messages, {
96
                    type => 'error',
97
                    code => 'item_not_deleted',
98
                    biblionumber => $biblionumber,
99
                    itemnumber => $item->itemnumber,
100
                    error => $error,
101
                };
102
                $schema->storage->txn_rollback;
103
                $job->progress( ++$job_progress )->store;
104
                next RECORD_IDS;
105
            }
106
        }
107
108
        # Finally, delete the biblio
109
        my $error = eval {
110
            C4::Biblio::DelBiblio( $biblionumber );
111
        };
112
        if ( $error or $@ ) {
113
            push @messages, {
114
                type => 'error',
115
                code => 'biblio_not_deleted',
116
                biblionumber => $biblionumber,
117
                error => ($@ ? $@ : $error),
118
            };
119
            $schema->storage->txn_rollback;
120
            $job->progress( ++$job_progress )->store;
121
            next;
122
        }
123
124
        push @messages, {
125
            type => 'success',
126
            code => 'biblio_deleted',
127
            biblionumber => $biblionumber,
128
        };
129
        $report->{total_success}++;
130
        $schema->storage->txn_commit;
131
        $job->progress( ++$job_progress )->store;
132
    }
133
134
    my $job_data = decode_json $job->data;
135
    $job_data->{messages} = \@messages;
136
    $job_data->{report} = $report;
137
138
    $job->ended_on(dt_from_string)
139
        ->data(encode_json $job_data);
140
    $job->status('finished') if $job->status ne 'cancelled';
141
    $job->store;
142
}
143
144
sub enqueue {
145
    my ( $self, $args) = @_;
146
147
    # TODO Raise exception instead
148
    return unless exists $args->{record_ids};
149
150
    my @record_ids = @{ $args->{record_ids} };
151
152
    $self->SUPER::enqueue({
153
        job_size => scalar @record_ids,
154
        job_args => {record_ids => \@record_ids,}
155
    });
156
}
157
158
1;
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt (+45 lines)
Lines 108-113 Administration &rsaquo; Koha Link Here
108
                            </div>
108
                            </div>
109
                        [% END %]
109
                        [% END %]
110
                    [% END %]
110
                    [% END %]
111
                [% CASE 'batch_biblio_record_deletion' %]
112
                    [% SET report = job.report %]
113
                    [% IF report %]
114
                        [% IF report.total_records == report.total_success %]
115
                            <div class="dialog message">
116
                                All records have been deleted successfully!
117
                            </div>
118
                        [% ELSIF report.total_success == 0 %]
119
                            <div class="dialog message">
120
                                No record has been deleted. An error occurred.
121
                            </div>
122
                        [% ELSE %]
123
                            <div class="dialog message">
124
                                [% report.total_success | html %] / [% report.total_records | html %] records have been deleted successfully but some errors occurred.
125
                            </div>
126
                        [% END %]
127
                    [% END %]
111
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
128
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
112
                [% END %]
129
                [% END %]
113
            </li>
130
            </li>
Lines 149-154 Administration &rsaquo; Koha Link Here
149
                            [% END %]
166
                            [% END %]
150
                        </div>
167
                        </div>
151
                    [% END %]
168
                    [% END %]
169
                [% CASE 'batch_biblio_record_deletion' %]
170
                    [% FOR m IN job.messages %]
171
                        <div class="dialog message">
172
                            [% IF m.type == 'success' %]
173
                                <i class="fa fa-check success"></i>
174
                            [% ELSIF m.type == 'warning' %]
175
                                <i class="fa fa-warning warn"></i>
176
                            [% ELSIF m.type == 'error' %]
177
                                <i class="fa fa-exclamation error"></i>
178
                            [% END %]
179
                            [% SWITCH m.code %]
180
                            [% CASE 'biblio_not_exists' %]
181
                                The biblionumber [% m.biblionumber | html %] does not exist in the database.
182
                            [% CASE 'item_issued' %]
183
                                At least one item is checked out on bibliographic record [% m.biblionumber | html %].
184
                            [% CASE 'reserve_not_cancelled' %]
185
                                Bibliographic record [% m.biblionumber | html %] was not deleted. A hold could not be canceled (reserve_id [% m.reserve_id | html %]).
186
                            [% CASE 'item_not_deleted' %]
187
                                The bibliographic record [% m.biblionumber | html %] was not deleted. An error was encountered when deleting an item (itemnumber [% m.itemnumber | html %]).
188
                            [% CASE 'biblio_not_deleted' %]
189
                                Bibliographic record [% m.biblionumber | html %] was not deleted. An error occurred.
190
                            [% CASE 'biblio_deleted' %]
191
                                Bibliographic record [% m.biblionumber | html %] has been deleted successfully.
192
                            [% END %]
193
                        </div>
194
                    [% END %]
195
152
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
196
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
153
                [% END %]
197
                [% END %]
154
            </li>
198
            </li>
Lines 187-192 Administration &rsaquo; Koha Link Here
187
                    <td>
231
                    <td>
188
                        [% SWITCH job.type %]
232
                        [% SWITCH job.type %]
189
                        [% CASE 'batch_biblio_record_modification' %]Batch bibliographic record modification
233
                        [% CASE 'batch_biblio_record_modification' %]Batch bibliographic record modification
234
                        [% CASE 'batch_biblio_record_deletion' %]Batch bibliographic record record deletion
190
                        [% CASE 'batch_authority_record_modification' %]Batch authority record modification
235
                        [% CASE 'batch_authority_record_modification' %]Batch authority record modification
191
                        [% CASE %][% job.type | html %]
236
                        [% CASE %][% job.type | html %]
192
                        [% END %]
237
                        [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_delete_records.tt (-10 / +8 lines)
Lines 43-62 Link Here
43
      The biblionumber [% message.biblionumber | html %] does not exist in the database.
43
      The biblionumber [% message.biblionumber | html %] does not exist in the database.
44
    [% ELSIF message.code == 'authority_not_exists' %]
44
    [% ELSIF message.code == 'authority_not_exists' %]
45
      The authority id [% message.authid | html %] does not exist in the database.
45
      The authority id [% message.authid | html %] does not exist in the database.
46
    [% ELSIF message.code == 'item_issued' %]
47
      At least one item is checked out on bibliographic record [% message.biblionumber | html %].
48
    [% ELSIF message.code == 'reserve_not_cancelled' %]
49
      Bibliographic record [% message.biblionumber | html %] was not deleted. A hold could not be canceled (reserve_id [% message.reserve_id | html %]).
50
    [% ELSIF message.code == 'item_not_deleted' %]
51
      The bibliographic record [% message.biblionumber | html %] was not deleted. An error was encountered when deleting an item (itemnumber [% message.itemnumber | html %]).
52
    [% ELSIF message.code == 'biblio_not_deleted' %]
53
      Bibliographic record [% message.biblionumber | html %] was not deleted. An error occurred.
54
    [% ELSIF message.code == 'authority_not_deleted' %]
46
    [% ELSIF message.code == 'authority_not_deleted' %]
55
      Authority record [% message.authid | html %] was not deleted. An error occurred.
47
      Authority record [% message.authid | html %] was not deleted. An error occurred.
56
    [% ELSIF message.code == 'biblio_deleted' %]
57
      Bibliographic record [% message.biblionumber | html %] has been deleted successfully.
58
    [% ELSIF message.code == 'authority_deleted' %]
48
    [% ELSIF message.code == 'authority_deleted' %]
59
      Authority [% message.authid | html %] has been deleted successfully.
49
      Authority [% message.authid | html %] has been deleted successfully.
50
    [% ELSIF message.code == 'cannot_enqueue_job' %]
51
        Cannot enqueue this job.
60
    [% END %]
52
    [% END %]
61
    [% IF message.error %]
53
    [% IF message.error %]
62
      (The error was: [% message.error | html %], see the Koha log file for more information).
54
      (The error was: [% message.error | html %], see the Koha log file for more information).
Lines 215-220 Link Here
215
      [% report.total_success | html %] / [% report.total_records | html %] records have been deleted successfully but some errors occurred.
207
      [% report.total_success | html %] / [% report.total_records | html %] records have been deleted successfully but some errors occurred.
216
    [% END %]
208
    [% END %]
217
    <p><a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
209
    <p><a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
210
  [% ELSIF op == 'enqueued' %]
211
    <div class="dialog message">
212
      <p>The job has been enqueued! It will be processed as soon as possible.</p>
213
      <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>
214
      | <a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
215
    </div>
218
  [% ELSE %]
216
  [% ELSE %]
219
    No action defined for the template.
217
    No action defined for the template.
220
  [% END %]
218
  [% END %]
(-)a/misc/background_jobs_worker.pl (-1 / +5 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(
32
    batch_biblio_record_modification
33
    batch_authority_record_modification
34
    batch_biblio_record_deletion
35
);
32
36
33
if ( $conn ) {
37
if ( $conn ) {
34
    # FIXME cf note in Koha::BackgroundJob about $namespace
38
    # FIXME cf note in Koha::BackgroundJob about $namespace
(-)a/tools/batch_delete_records.pl (-115 / +23 lines)
Lines 22-27 use Modern::Perl; Link Here
22
22
23
use CGI;
23
use CGI;
24
use List::MoreUtils qw( uniq );
24
use List::MoreUtils qw( uniq );
25
use Try::Tiny;
25
26
26
use C4::Auth qw( get_template_and_user );
27
use C4::Auth qw( get_template_and_user );
27
use C4::Output qw( output_html_with_http_headers );
28
use C4::Output qw( output_html_with_http_headers );
Lines 33-38 use Koha::Virtualshelves; Link Here
33
use Koha::Authorities;
34
use Koha::Authorities;
34
use Koha::Biblios;
35
use Koha::Biblios;
35
use Koha::Items;
36
use Koha::Items;
37
use Koha::BackgroundJob::BatchDeleteBiblio;
36
38
37
my $input = CGI->new;
39
my $input = CGI->new;
38
my $op = $input->param('op') // q|form|;
40
my $op = $input->param('op') // q|form|;
Lines 131-252 if ( $op eq 'form' ) { Link Here
131
} elsif ( $op eq 'delete' ) {
133
} elsif ( $op eq 'delete' ) {
132
    # We want to delete selected records!
134
    # We want to delete selected records!
133
    my @record_ids = $input->multi_param('record_id');
135
    my @record_ids = $input->multi_param('record_id');
134
    my $schema = Koha::Database->new->schema;
135
136
136
    my $error;
137
    try {
137
    my $report = {
138
        my $params = {
138
        total_records => 0,
139
            record_ids  => \@record_ids,
139
        total_success => 0,
140
        };
141
142
        my $job_id =
143
          $recordtype eq 'biblio'
144
          ? Koha::BackgroundJob::BatchDeleteBiblio->new->enqueue($params)
145
          : Koha::BackgroundJob::BatchDeleteAuthority->new->enqueue($params);
146
147
        $template->param(
148
            op => 'enqueued',
149
            job_id => $job_id,
150
        );
151
    } catch {
152
        push @messages, {
153
            type => 'error',
154
            code => 'cannot_enqueue_job',
155
            error => $_,
156
        };
157
        $template->param( view => 'errors' );
140
    };
158
    };
141
    RECORD_IDS: for my $record_id ( sort { $a <=> $b } @record_ids ) {
142
        $report->{total_records}++;
143
        next unless $record_id;
144
        $schema->storage->txn_begin;
145
146
        if ( $recordtype eq 'biblio' ) {
147
            # Biblios
148
            my $biblionumber = $record_id;
149
            # First, checking if issues exist.
150
            # If yes, nothing to do
151
            my $biblio = Koha::Biblios->find( $biblionumber );
152
153
            # TODO Replace with $biblio->get_issues->count
154
            if ( C4::Biblio::CountItemsIssued( $biblionumber ) ) {
155
                push @messages, {
156
                    type => 'warning',
157
                    code => 'item_issued',
158
                    biblionumber => $biblionumber,
159
                };
160
                $schema->storage->txn_rollback;
161
                next;
162
            }
163
164
            # Cancel reserves
165
            my $holds = $biblio->holds;
166
            while ( my $hold = $holds->next ) {
167
                eval{
168
                    $hold->cancel;
169
                };
170
                if ( $@ ) {
171
                    push @messages, {
172
                        type => 'error',
173
                        code => 'reserve_not_cancelled',
174
                        biblionumber => $biblionumber,
175
                        reserve_id => $hold->reserve_id,
176
                        error => $@,
177
                    };
178
                    $schema->storage->txn_rollback;
179
                    next RECORD_IDS;
180
                }
181
            }
182
183
            # Delete items
184
            my $items = Koha::Items->search({ biblionumber => $biblionumber });
185
            while ( my $item = $items->next ) {
186
                my $deleted_item = eval { $item->safe_delete };
187
                if ( !ref($deleted_item) or $@ ) {
188
                    push @messages, {
189
                        type => 'error',
190
                        code => 'item_not_deleted',
191
                        biblionumber => $biblionumber,
192
                        itemnumber => $item->itemnumber,
193
                        error => ($@ ? $@ : $error),
194
                    };
195
                    $schema->storage->txn_rollback;
196
                    next RECORD_IDS;
197
                }
198
            }
199
200
            # Finally, delete the biblio
201
            my $error = eval {
202
                C4::Biblio::DelBiblio( $biblionumber );
203
            };
204
            if ( $error or $@ ) {
205
                push @messages, {
206
                    type => 'error',
207
                    code => 'biblio_not_deleted',
208
                    biblionumber => $biblionumber,
209
                    error => ($@ ? $@ : $error),
210
                };
211
                $schema->storage->txn_rollback;
212
                next;
213
            }
214
215
            push @messages, {
216
                type => 'success',
217
                code => 'biblio_deleted',
218
                biblionumber => $biblionumber,
219
            };
220
            $report->{total_success}++;
221
            $schema->storage->txn_commit;
222
        } else {
223
            # Authorities
224
            my $authid = $record_id;
225
            eval { C4::AuthoritiesMarc::DelAuthority({ authid => $authid }) };
226
            if ( $@ ) {
227
                push @messages, {
228
                    type => 'error',
229
                    code => 'authority_not_deleted',
230
                    authid => $authid,
231
                    error => ($@ ? $@ : 0),
232
                };
233
                $schema->storage->txn_rollback;
234
                next;
235
            } else {
236
                push @messages, {
237
                    type => 'success',
238
                    code => 'authority_deleted',
239
                    authid => $authid,
240
                };
241
                $report->{total_success}++;
242
                $schema->storage->txn_commit;
243
            }
244
        }
245
    }
246
    $template->param(
247
        op => 'report',
248
        report => $report,
249
    );
250
}
159
}
251
160
252
$template->param(
161
$template->param(
253
- 

Return to bug 26080