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

(-)a/Koha/BackgroundJob.pm (+3 lines)
Lines 10-15 use Koha::DateUtils qw( dt_from_string ); Link Here
10
use Koha::Exceptions;
10
use Koha::Exceptions;
11
use Koha::BackgroundJob::BatchUpdateBiblio;
11
use Koha::BackgroundJob::BatchUpdateBiblio;
12
use Koha::BackgroundJob::BatchUpdateAuthority;
12
use Koha::BackgroundJob::BatchUpdateAuthority;
13
use Koha::BackgroundJob::BatchDeleteBiblio;
13
14
14
use Scalar::Util qw( weaken );
15
use Scalar::Util qw( weaken );
15
16
Lines 71-76 sub process { Link Here
71
      ? Koha::BackgroundJob::BatchUpdateBiblio->process($args)
72
      ? Koha::BackgroundJob::BatchUpdateBiblio->process($args)
72
      : $job_type eq 'batch_authority_record_modification'
73
      : $job_type eq 'batch_authority_record_modification'
73
      ? Koha::BackgroundJob::BatchUpdateAuthority->process($args)
74
      ? Koha::BackgroundJob::BatchUpdateAuthority->process($args)
75
      : $job_type eq 'batch_biblio_record_deletion'
76
      ? Koha::BackgroundJob::BatchDeleteBiblio->process($args)
74
      : Koha::Exceptions::Exception->throw('->process called without valid job_type');
77
      : Koha::Exceptions::Exception->throw('->process called without valid job_type');
75
}
78
}
76
79
(-)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 85-90 Link Here
85
                            </div>
85
                            </div>
86
                        [% END %]
86
                        [% END %]
87
                    [% END %]
87
                    [% END %]
88
                [% CASE 'batch_biblio_record_deletion' %]
89
                    [% SET report = job.report %]
90
                    [% IF report %]
91
                        [% IF report.total_records == report.total_success %]
92
                            <div class="dialog message">
93
                                All records have been deleted successfully!
94
                            </div>
95
                        [% ELSIF report.total_success == 0 %]
96
                            <div class="dialog message">
97
                                No record has been deleted. An error occurred.
98
                            </div>
99
                        [% ELSE %]
100
                            <div class="dialog message">
101
                                [% report.total_success | html %] / [% report.total_records | html %] records have been deleted successfully but some errors occurred.
102
                            </div>
103
                        [% END %]
104
                    [% END %]
88
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
105
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
89
                [% END %]
106
                [% END %]
90
            </li>
107
            </li>
Lines 126-131 Link Here
126
                            [% END %]
143
                            [% END %]
127
                        </div>
144
                        </div>
128
                    [% END %]
145
                    [% END %]
146
                [% CASE 'batch_biblio_record_deletion' %]
147
                    [% FOR m IN job.messages %]
148
                        <div class="dialog message">
149
                            [% IF m.type == 'success' %]
150
                                <i class="fa fa-check success"></i>
151
                            [% ELSIF m.type == 'warning' %]
152
                                <i class="fa fa-warning warn"></i>
153
                            [% ELSIF m.type == 'error' %]
154
                                <i class="fa fa-exclamation error"></i>
155
                            [% END %]
156
                            [% SWITCH m.code %]
157
                            [% CASE 'biblio_not_exists' %]
158
                                The biblionumber [% m.biblionumber | html %] does not exist in the database.
159
                            [% CASE 'item_issued' %]
160
                                At least one item is checked out on bibliographic record [% m.biblionumber | html %].
161
                            [% CASE 'reserve_not_cancelled' %]
162
                                Bibliographic record [% m.biblionumber | html %] was not deleted. A hold could not be canceled (reserve_id [% m.reserve_id | html %]).
163
                            [% CASE 'item_not_deleted' %]
164
                                The bibliographic record [% m.biblionumber | html %] was not deleted. An error was encountered when deleting an item (itemnumber [% m.itemnumber | html %]).
165
                            [% CASE 'biblio_not_deleted' %]
166
                                Bibliographic record [% m.biblionumber | html %] was not deleted. An error occurred.
167
                            [% CASE 'biblio_deleted' %]
168
                                Bibliographic record [% m.biblionumber | html %] has been deleted successfully.
169
                            [% END %]
170
                        </div>
171
                    [% END %]
172
129
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
173
                [% CASE %]Job type "[% job.type | html %]" not handled in the template
130
                [% END %]
174
                [% END %]
131
            </li>
175
            </li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batch_delete_records.tt (-10 / +8 lines)
Lines 35-54 Link Here
35
      The biblionumber [% message.biblionumber | html %] does not exist in the database.
35
      The biblionumber [% message.biblionumber | html %] does not exist in the database.
36
    [% ELSIF message.code == 'authority_not_exists' %]
36
    [% ELSIF message.code == 'authority_not_exists' %]
37
      The authority id [% message.authid | html %] does not exist in the database.
37
      The authority id [% message.authid | html %] does not exist in the database.
38
    [% ELSIF message.code == 'item_issued' %]
39
      At least one item is checked out on bibliographic record [% message.biblionumber | html %].
40
    [% ELSIF message.code == 'reserve_not_cancelled' %]
41
      Bibliographic record [% message.biblionumber | html %] was not deleted. A hold could not be canceled (reserve_id [% message.reserve_id | html %]).
42
    [% ELSIF message.code == 'item_not_deleted' %]
43
      The bibliographic record [% message.biblionumber | html %] was not deleted. An error was encountered when deleting an item (itemnumber [% message.itemnumber | html %]).
44
    [% ELSIF message.code == 'biblio_not_deleted' %]
45
      Bibliographic record [% message.biblionumber | html %] was not deleted. An error occurred.
46
    [% ELSIF message.code == 'authority_not_deleted' %]
38
    [% ELSIF message.code == 'authority_not_deleted' %]
47
      Authority record [% message.authid | html %] was not deleted. An error occurred.
39
      Authority record [% message.authid | html %] was not deleted. An error occurred.
48
    [% ELSIF message.code == 'biblio_deleted' %]
49
      Bibliographic record [% message.biblionumber | html %] has been deleted successfully.
50
    [% ELSIF message.code == 'authority_deleted' %]
40
    [% ELSIF message.code == 'authority_deleted' %]
51
      Authority [% message.authid | html %] has been deleted successfully.
41
      Authority [% message.authid | html %] has been deleted successfully.
42
    [% ELSIF message.code == 'cannot_enqueue_job' %]
43
        Cannot enqueue this job.
52
    [% END %]
44
    [% END %]
53
    [% IF message.error %]
45
    [% IF message.error %]
54
      (The error was: [% message.error | html %], see the Koha log file for more information).
46
      (The error was: [% message.error | html %], see the Koha log file for more information).
Lines 207-212 Link Here
207
      [% report.total_success | html %] / [% report.total_records | html %] records have been deleted successfully but some errors occurred.
199
      [% report.total_success | html %] / [% report.total_records | html %] records have been deleted successfully but some errors occurred.
208
    [% END %]
200
    [% END %]
209
    <p><a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
201
    <p><a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
202
  [% ELSIF op == 'enqueued' %]
203
    <div class="dialog message">
204
      <p>The job has been enqueued! It will be processed as soon as possible.</p>
205
      <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>
206
      | <a href="/cgi-bin/koha/tools/batch_delete_records.pl" title="New batch record deletion">New batch record deletion</a></p>
207
    </div>
210
  [% ELSE %]
208
  [% ELSE %]
211
    No action defined for the template.
209
    No action defined for the template.
212
  [% END %]
210
  [% END %]
(-)a/misc/background_jobs_worker.pl (-1 / +5 lines)
Lines 22-28 use Koha::BackgroundJobs; Link Here
22
22
23
my $conn = Koha::BackgroundJob->connect;
23
my $conn = Koha::BackgroundJob->connect;
24
24
25
my @job_types = qw( batch_biblio_record_modification batch_authority_record_modification );
25
my @job_types = qw(
26
    batch_biblio_record_modification
27
    batch_authority_record_modification
28
    batch_biblio_record_deletion
29
);
26
30
27
# FIXME cf note in Koha::BackgroundJob about $namespace
31
# FIXME cf note in Koha::BackgroundJob about $namespace
28
my $namespace = C4::Context->config('memcached_namespace');
32
my $namespace = C4::Context->config('memcached_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 = new CGI;
38
my $input = new CGI;
37
my $op = $input->param('op') // q|form|;
39
my $op = $input->param('op') // q|form|;
Lines 125-246 if ( $op eq 'form' ) { Link Here
125
} elsif ( $op eq 'delete' ) {
127
} elsif ( $op eq 'delete' ) {
126
    # We want to delete selected records!
128
    # We want to delete selected records!
127
    my @record_ids = $input->multi_param('record_id');
129
    my @record_ids = $input->multi_param('record_id');
128
    my $schema = Koha::Database->new->schema;
129
130
130
    my $error;
131
    try {
131
    my $report = {
132
        my $params = {
132
        total_records => 0,
133
            record_ids  => \@record_ids,
133
        total_success => 0,
134
        };
135
136
        my $job_id =
137
          $recordtype eq 'biblio'
138
          ? Koha::BackgroundJob::BatchDeleteBiblio->new->enqueue($params)
139
          : Koha::BackgroundJob::BatchDeleteAuthority->new->enqueue($params);
140
141
        $template->param(
142
            op => 'enqueued',
143
            job_id => $job_id,
144
        );
145
    } catch {
146
        push @messages, {
147
            type => 'error',
148
            code => 'cannot_enqueue_job',
149
            error => $_,
150
        };
151
        $template->param( view => 'errors' );
134
    };
152
    };
135
    RECORD_IDS: for my $record_id ( sort { $a <=> $b } @record_ids ) {
136
        $report->{total_records}++;
137
        next unless $record_id;
138
        $schema->storage->txn_begin;
139
140
        if ( $recordtype eq 'biblio' ) {
141
            # Biblios
142
            my $biblionumber = $record_id;
143
            # First, checking if issues exist.
144
            # If yes, nothing to do
145
            my $biblio = Koha::Biblios->find( $biblionumber );
146
147
            # TODO Replace with $biblio->get_issues->count
148
            if ( C4::Biblio::CountItemsIssued( $biblionumber ) ) {
149
                push @messages, {
150
                    type => 'warning',
151
                    code => 'item_issued',
152
                    biblionumber => $biblionumber,
153
                };
154
                $schema->storage->txn_rollback;
155
                next;
156
            }
157
158
            # Cancel reserves
159
            my $holds = $biblio->holds;
160
            while ( my $hold = $holds->next ) {
161
                eval{
162
                    $hold->cancel;
163
                };
164
                if ( $@ ) {
165
                    push @messages, {
166
                        type => 'error',
167
                        code => 'reserve_not_cancelled',
168
                        biblionumber => $biblionumber,
169
                        reserve_id => $hold->reserve_id,
170
                        error => $@,
171
                    };
172
                    $schema->storage->txn_rollback;
173
                    next RECORD_IDS;
174
                }
175
            }
176
177
            # Delete items
178
            my $items = Koha::Items->search({ biblionumber => $biblionumber });
179
            while ( my $item = $items->next ) {
180
                my $deleted_item = eval { $item->safe_delete };
181
                if ( !ref($deleted_item) or $@ ) {
182
                    push @messages, {
183
                        type => 'error',
184
                        code => 'item_not_deleted',
185
                        biblionumber => $biblionumber,
186
                        itemnumber => $item->itemnumber,
187
                        error => ($@ ? $@ : $error),
188
                    };
189
                    $schema->storage->txn_rollback;
190
                    next RECORD_IDS;
191
                }
192
            }
193
194
            # Finally, delete the biblio
195
            my $error = eval {
196
                C4::Biblio::DelBiblio( $biblionumber );
197
            };
198
            if ( $error or $@ ) {
199
                push @messages, {
200
                    type => 'error',
201
                    code => 'biblio_not_deleted',
202
                    biblionumber => $biblionumber,
203
                    error => ($@ ? $@ : $error),
204
                };
205
                $schema->storage->txn_rollback;
206
                next;
207
            }
208
209
            push @messages, {
210
                type => 'success',
211
                code => 'biblio_deleted',
212
                biblionumber => $biblionumber,
213
            };
214
            $report->{total_success}++;
215
            $schema->storage->txn_commit;
216
        } else {
217
            # Authorities
218
            my $authid = $record_id;
219
            eval { C4::AuthoritiesMarc::DelAuthority({ authid => $authid }) };
220
            if ( $@ ) {
221
                push @messages, {
222
                    type => 'error',
223
                    code => 'authority_not_deleted',
224
                    authid => $authid,
225
                    error => ($@ ? $@ : 0),
226
                };
227
                $schema->storage->txn_rollback;
228
                next;
229
            } else {
230
                push @messages, {
231
                    type => 'success',
232
                    code => 'authority_deleted',
233
                    authid => $authid,
234
                };
235
                $report->{total_success}++;
236
                $schema->storage->txn_commit;
237
            }
238
        }
239
    }
240
    $template->param(
241
        op => 'report',
242
        report => $report,
243
    );
244
}
153
}
245
154
246
$template->param(
155
$template->param(
247
- 

Return to bug 26080