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 (+44 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>
(-)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;
27
use C4::Auth;
27
use C4::Output;
28
use C4::Output;
Lines 32-37 use Koha::Virtualshelves; Link Here
32
use Koha::Authorities;
33
use Koha::Authorities;
33
use Koha::Biblios;
34
use Koha::Biblios;
34
use Koha::Items;
35
use Koha::Items;
36
use Koha::BackgroundJob::BatchDeleteBiblio;
35
37
36
my $input = CGI->new;
38
my $input = CGI->new;
37
my $op = $input->param('op') // q|form|;
39
my $op = $input->param('op') // q|form|;
Lines 124-245 if ( $op eq 'form' ) { Link Here
124
} elsif ( $op eq 'delete' ) {
126
} elsif ( $op eq 'delete' ) {
125
    # We want to delete selected records!
127
    # We want to delete selected records!
126
    my @record_ids = $input->multi_param('record_id');
128
    my @record_ids = $input->multi_param('record_id');
127
    my $schema = Koha::Database->new->schema;
128
129
129
    my $error;
130
    try {
130
    my $report = {
131
        my $params = {
131
        total_records => 0,
132
            record_ids  => \@record_ids,
132
        total_success => 0,
133
        };
134
135
        my $job_id =
136
          $recordtype eq 'biblio'
137
          ? Koha::BackgroundJob::BatchDeleteBiblio->new->enqueue($params)
138
          : Koha::BackgroundJob::BatchDeleteAuthority->new->enqueue($params);
139
140
        $template->param(
141
            op => 'enqueued',
142
            job_id => $job_id,
143
        );
144
    } catch {
145
        push @messages, {
146
            type => 'error',
147
            code => 'cannot_enqueue_job',
148
            error => $_,
149
        };
150
        $template->param( view => 'errors' );
133
    };
151
    };
134
    RECORD_IDS: for my $record_id ( sort { $a <=> $b } @record_ids ) {
135
        $report->{total_records}++;
136
        next unless $record_id;
137
        $schema->storage->txn_begin;
138
139
        if ( $recordtype eq 'biblio' ) {
140
            # Biblios
141
            my $biblionumber = $record_id;
142
            # First, checking if issues exist.
143
            # If yes, nothing to do
144
            my $biblio = Koha::Biblios->find( $biblionumber );
145
146
            # TODO Replace with $biblio->get_issues->count
147
            if ( C4::Biblio::CountItemsIssued( $biblionumber ) ) {
148
                push @messages, {
149
                    type => 'warning',
150
                    code => 'item_issued',
151
                    biblionumber => $biblionumber,
152
                };
153
                $schema->storage->txn_rollback;
154
                next;
155
            }
156
157
            # Cancel reserves
158
            my $holds = $biblio->holds;
159
            while ( my $hold = $holds->next ) {
160
                eval{
161
                    $hold->cancel;
162
                };
163
                if ( $@ ) {
164
                    push @messages, {
165
                        type => 'error',
166
                        code => 'reserve_not_cancelled',
167
                        biblionumber => $biblionumber,
168
                        reserve_id => $hold->reserve_id,
169
                        error => $@,
170
                    };
171
                    $schema->storage->txn_rollback;
172
                    next RECORD_IDS;
173
                }
174
            }
175
176
            # Delete items
177
            my $items = Koha::Items->search({ biblionumber => $biblionumber });
178
            while ( my $item = $items->next ) {
179
                my $deleted_item = eval { $item->safe_delete };
180
                if ( !ref($deleted_item) or $@ ) {
181
                    push @messages, {
182
                        type => 'error',
183
                        code => 'item_not_deleted',
184
                        biblionumber => $biblionumber,
185
                        itemnumber => $item->itemnumber,
186
                        error => ($@ ? $@ : $error),
187
                    };
188
                    $schema->storage->txn_rollback;
189
                    next RECORD_IDS;
190
                }
191
            }
192
193
            # Finally, delete the biblio
194
            my $error = eval {
195
                C4::Biblio::DelBiblio( $biblionumber );
196
            };
197
            if ( $error or $@ ) {
198
                push @messages, {
199
                    type => 'error',
200
                    code => 'biblio_not_deleted',
201
                    biblionumber => $biblionumber,
202
                    error => ($@ ? $@ : $error),
203
                };
204
                $schema->storage->txn_rollback;
205
                next;
206
            }
207
208
            push @messages, {
209
                type => 'success',
210
                code => 'biblio_deleted',
211
                biblionumber => $biblionumber,
212
            };
213
            $report->{total_success}++;
214
            $schema->storage->txn_commit;
215
        } else {
216
            # Authorities
217
            my $authid = $record_id;
218
            eval { C4::AuthoritiesMarc::DelAuthority({ authid => $authid }) };
219
            if ( $@ ) {
220
                push @messages, {
221
                    type => 'error',
222
                    code => 'authority_not_deleted',
223
                    authid => $authid,
224
                    error => ($@ ? $@ : 0),
225
                };
226
                $schema->storage->txn_rollback;
227
                next;
228
            } else {
229
                push @messages, {
230
                    type => 'success',
231
                    code => 'authority_deleted',
232
                    authid => $authid,
233
                };
234
                $report->{total_success}++;
235
                $schema->storage->txn_commit;
236
            }
237
        }
238
    }
239
    $template->param(
240
        op => 'report',
241
        report => $report,
242
    );
243
}
152
}
244
153
245
$template->param(
154
$template->param(
246
- 

Return to bug 26080