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