Bug 30710: Build holds queue once per biblio when batch deleting items
[koha-ffzg.git] / Koha / BackgroundJob / BatchDeleteItem.pm
1 package Koha::BackgroundJob::BatchDeleteItem;
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 =head1 NAME
19
20 Koha::BackgroundJob::BatchDeleteItem - Background job derived class to process item deletion in batch
21
22 =cut
23
24 use Modern::Perl;
25 use JSON qw( encode_json decode_json );
26 use List::MoreUtils qw( uniq );
27 use Try::Tiny;
28
29 use Koha::DateUtils qw( dt_from_string );
30 use Koha::Items;
31
32 use base 'Koha::BackgroundJob';
33
34 =head1 API
35
36 =head2 Class methods
37
38 =head3 job_type
39
40 Return the job type 'batch_item_record_deletion'.
41
42 =cut
43
44 sub job_type {
45     return 'batch_item_record_deletion';
46 }
47
48 =head3 process
49
50     Koha::BackgroundJobs->find($id)->process(
51         {
52             record_ids => \@itemnumbers,
53             deleted_biblios => 0|1,
54         }
55     );
56
57 Will delete all the items that have been passed for deletion.
58
59 When deleted_biblios is passed, if we deleted the last item of a biblio,
60 the bibliographic record will be deleted as well.
61
62 The search engine's index will be updated according to the changes made
63 to the deleted bibliographic recods.
64
65 The generated report will be:
66   {
67     deleted_itemnumbers => \@list_of_itemnumbers,
68     not_deleted_itemnumbers => \@list_of_itemnumbers,
69     deleted_biblionumbers=> \@list_of_biblionumbers,
70   }
71
72 =cut
73
74 sub process {
75     my ( $self, $args ) = @_;
76
77     if ( $self->status eq 'cancelled' ) {
78         return;
79     }
80
81     # FIXME If the job has already been started, but started again (worker has been restart for instance)
82     # Then we will start from scratch and so double delete the same records
83
84     my $job_progress = 0;
85     $self->started_on(dt_from_string)->progress($job_progress)
86       ->status('started')->store;
87
88     my @record_ids     = @{ $args->{record_ids} };
89     my $delete_biblios = $args->{delete_biblios};
90
91     my $report = {
92         total_records => scalar @record_ids,
93         total_success => 0,
94     };
95     my @messages;
96     my $schema = Koha::Database->new->schema;
97     my ( @deleted_itemnumbers, @not_deleted_itemnumbers,
98         @deleted_biblionumbers );
99
100     try {
101         my $schema = Koha::Database->new->schema;
102         $schema->txn_do(
103             sub {
104                 my (@biblionumbers);
105                 for my $record_id ( sort { $a <=> $b } @record_ids ) {
106
107                     last if $self->get_from_storage->status eq 'cancelled';
108
109                     my $item = Koha::Items->find($record_id) || next;
110
111                     my $return = $item->safe_delete({ skip_record_index => 1, skip_holds_queue => 1 });
112                     unless ( $return ) {
113
114                         # FIXME Do we need to rollback the whole transaction if a deletion failed?
115                         push @not_deleted_itemnumbers, $item->itemnumber;
116                         push @messages,
117                           {
118                             type         => 'error',
119                             code         => 'item_not_deleted',
120                             itemnumber   => $item->itemnumber,
121                             biblionumber => $item->biblionumber,
122                             barcode      => $item->barcode,
123                             title        => $item->biblio->title,
124                             reason       => @{$return->messages}[0]->message,
125                           };
126
127                         next;
128                     }
129
130                     push @deleted_itemnumbers, $item->itemnumber;
131                     push @biblionumbers,       $item->biblionumber;
132
133                     $report->{total_success}++;
134                     $self->progress( ++$job_progress )->store;
135                 }
136
137                 # If there are no items left, delete the biblio
138                 my @updated_biblionumbers;
139                 for my $biblionumber ( uniq @biblionumbers ) {
140                     my $items_count =
141                       Koha::Biblios->find($biblionumber)->items->count;
142                     if ( $delete_biblios && $items_count == 0 ) {
143                         my $error = C4::Biblio::DelBiblio( $biblionumber,
144                             { skip_record_index => 1, skip_holds_queue => 1 } );
145                         unless ($error) {
146                             push @deleted_biblionumbers, $biblionumber;
147                         }
148                     } else {
149                         push @updated_biblionumbers, $biblionumber;
150                     }
151                 }
152
153                 if (@deleted_biblionumbers) {
154                     my $indexer = Koha::SearchEngine::Indexer->new(
155                         { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
156
157                     $indexer->index_records( \@deleted_biblionumbers,
158                         'recordDelete', "biblioserver", undef );
159
160                     Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
161                         {
162                             biblio_ids => \@deleted_biblionumbers
163                         }
164                     );
165                 }
166
167                 if (@updated_biblionumbers) {
168                     my $indexer = Koha::SearchEngine::Indexer->new(
169                         { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
170
171                     $indexer->index_records( \@updated_biblionumbers,
172                         'specialUpdate', "biblioserver", undef );
173
174                     Koha::BackgroundJob::BatchUpdateBiblioHoldsQueue->new->enqueue(
175                         {
176                             biblio_ids => \@updated_biblionumbers
177                         }
178                     );
179                 }
180             }
181         );
182     }
183     catch {
184
185         warn $_;
186
187         push @messages,
188           {
189             type  => 'error',
190             code  => 'unknown',
191             error => $_,
192           };
193
194         die "Something terrible has happened!"
195           if ( $_ =~ /Rollback failed/ );    # Rollback failed
196     };
197
198     $report->{deleted_itemnumbers}     = \@deleted_itemnumbers;
199     $report->{not_deleted_itemnumbers} = \@not_deleted_itemnumbers;
200     $report->{deleted_biblionumbers}   = \@deleted_biblionumbers;
201
202     my $job_data = decode_json $self->data;
203     $job_data->{messages} = \@messages;
204     $job_data->{report}   = $report;
205
206     $self->ended_on(dt_from_string)->data( encode_json $job_data);
207     $self->status('finished') if $self->status ne 'cancelled';
208     $self->store;
209 }
210
211 =head3 enqueue
212
213     Koha::BackgroundJob::BatchDeleteItem->new->enqueue(
214         {
215             record_ids => \@itemnumbers,
216             deleted_biblios => 0|1,
217         }
218     );
219
220 Enqueue the job.
221
222 =cut
223
224 sub enqueue {
225     my ( $self, $args ) = @_;
226
227     # TODO Raise exception instead
228     return unless exists $args->{record_ids};
229
230     my @record_ids = @{ $args->{record_ids} };
231     my $delete_biblios = $args->{delete_biblios} || 0;
232
233     $self->SUPER::enqueue(
234         {
235             job_size => scalar @record_ids,
236             job_args => {
237                 record_ids     => \@record_ids,
238                 delete_biblios => $delete_biblios,
239             },
240             queue    => 'long_tasks',
241         }
242     );
243 }
244
245 1;