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

(-)a/Koha/BackgroundJob.pm (-1 / +7 lines)
Lines 26-33 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::BatchUpdateItem;
29
use Koha::BackgroundJob::BatchDeleteBiblio;
30
use Koha::BackgroundJob::BatchDeleteBiblio;
30
use Koha::BackgroundJob::BatchDeleteAuthority;
31
use Koha::BackgroundJob::BatchDeleteAuthority;
32
use Koha::BackgroundJob::BatchDeleteItem;
31
33
32
use base qw( Koha::Object );
34
use base qw( Koha::Object );
33
35
Lines 157-167 sub process { Link Here
157
      ? Koha::BackgroundJob::BatchUpdateBiblio->process($args)
159
      ? Koha::BackgroundJob::BatchUpdateBiblio->process($args)
158
      : $job_type eq 'batch_authority_record_modification'
160
      : $job_type eq 'batch_authority_record_modification'
159
      ? Koha::BackgroundJob::BatchUpdateAuthority->process($args)
161
      ? Koha::BackgroundJob::BatchUpdateAuthority->process($args)
162
      : $job_type eq 'batch_item_record_modification'
163
      ? Koha::BackgroundJob::BatchUpdateItem->process($args)
160
      : $job_type eq 'batch_biblio_record_deletion'
164
      : $job_type eq 'batch_biblio_record_deletion'
161
      ? Koha::BackgroundJob::BatchDeleteBiblio->process($args)
165
      ? Koha::BackgroundJob::BatchDeleteBiblio->process($args)
162
      : $job_type eq 'batch_authority_record_deletion'
166
      : $job_type eq 'batch_authority_record_deletion'
163
      ? Koha::BackgroundJob::BatchDeleteAuthority->process($args)
167
      ? Koha::BackgroundJob::BatchDeleteAuthority->process($args)
164
      : Koha::Exceptions::Exception->throw('->process called without valid job_type');
168
      : $job_type eq 'batch_item_record_deletion'
169
      ? Koha::BackgroundJob::BatchDeleteItem->process($args)
170
      : Koha::Exceptions::Exception->throw('->process called without valid job_type')
165
}
171
}
166
172
167
=head3 job_type
173
=head3 job_type
(-)a/Koha/BackgroundJob/BatchDeleteItem.pm (+140 lines)
Line 0 Link Here
1
package Koha::BackgroundJob::BatchDeleteItem;
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
9
use base 'Koha::BackgroundJob';
10
11
sub job_type {
12
    return 'batch_item_record_deletion';
13
}
14
15
sub process {
16
    my ( $self, $args ) = @_;
17
18
    my $job_type = $args->{job_type};
19
20
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
21
22
    if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) {
23
        return;
24
    }
25
26
    # FIXME If the job has already been started, but started again (worker has been restart for instance)
27
    # Then we will start from scratch and so double delete the same records
28
29
    my $job_progress = 0;
30
    $job->started_on(dt_from_string)
31
        ->progress($job_progress)
32
        ->status('started')
33
        ->store;
34
35
    my @record_ids = @{ $args->{record_ids} };
36
37
    my $report = {
38
        total_records => scalar @record_ids,
39
        total_success => 0,
40
    };
41
    my @messages;
42
    my $schema = Koha::Database->new->schema;
43
    RECORD_IDS: for my $record_id ( sort { $a <=> $b } @record_ids ) {
44
45
        last if $job->get_from_storage->status eq 'cancelled';
46
47
        try {
48
49
            my $schema = Koha::Database->new->schema;
50
            $schema->txn_do(
51
                sub {
52
                    my $item = Koha::Items->find($record_id);
53
                    next unless $item;
54
55
                    my $biblionumber = $item->biblionumber;
56
                    my $return = $item->safe_delete;
57
                    if ( ref( $return ) ) {
58
                        $deleted_items++;
59
                        push @$upd_biblionumbers, $biblionumber;
60
                    }
61
                    else {
62
                        push @messages, {
63
                            type => 'error',
64
                            code => 'item_not_deleted',
65
                            itemnumber => $item->itemnumber
66
                            biblionumber => $biblionumber,
67
                            barcode => $item->barcode,
68
                            title => $item->biblio->title,
69
                            reason => $return,
70
                        }
71
                    }
72
73
                    # If there are no items left, delete the biblio
74
                    if ($del_records) {
75
                        my $itemscount = Koha::Biblios->find( $biblionumber )->items->count;
76
                        if ( $itemscount == 0 ) {
77
                            my $error = C4::Biblio::DelBiblio( $biblionumber, { skip_record_index => 1 } );
78
                            unless ($error) {
79
                                push @$del_biblionumbers, $biblionumber;
80
                                if ( $src eq 'CATALOGUING' ) {
81
                                    # We are coming catalogue/detail.pl, there were items from a single bib record
82
                                    $template->param( biblio_deleted => 1 );
83
                                }
84
                            }
85
                        }
86
                    }
87
                }
88
            );
89
        }
90
        catch {
91
92
            warn $_;
93
94
            if ( $_->isa('Koha::Exceptions::Exception') ) {
95
                $template->param( deletion_failed => 1 );
96
            }
97
            die "Something terrible has happened!"
98
                if ($_ =~ /Rollback failed/); # Rollback failed
99
        };
100
101
        $report->{total_success}++;
102
        $schema->storage->txn_commit;
103
        $job->progress( ++$job_progress )->store;
104
    }
105
106
    $upd_biblionumbers = [ uniq @$upd_biblionumbers ]; # Only update each bib once
107
108
    # Don't send specialUpdate for records we are going to delete
109
    my %del_bib_hash = map{ $_ => undef } @$del_biblionumbers;
110
    @$upd_biblionumbers = grep( ! exists( $del_bib_hash{$_} ), @$upd_biblionumbers );
111
112
    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
113
    $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
114
    $indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers;
115
116
    my $job_data = decode_json $job->data;
117
    $job_data->{messages} = \@messages;
118
    $job_data->{report} = $report;
119
120
    $job->ended_on(dt_from_string)
121
        ->data(encode_json $job_data);
122
    $job->status('finished') if $job->status ne 'cancelled';
123
    $job->store;
124
}
125
126
sub enqueue {
127
    my ( $self, $args) = @_;
128
129
    # TODO Raise exception instead
130
    return unless exists $args->{record_ids};
131
132
    my @record_ids = @{ $args->{record_ids} };
133
134
    $self->SUPER::enqueue({
135
        job_size => scalar @record_ids,
136
        job_args => {record_ids => \@record_ids,}
137
    });
138
}
139
140
1;
(-)a/Koha/BackgroundJob/BatchUpdateItem.pm (+313 lines)
Line 0 Link Here
1
package Koha::BackgroundJob::BatchUpdateItem;
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
use Modern::Perl;
19
use JSON qw( encode_json decode_json );
20
use List::MoreUtils qw( uniq );
21
use Try::Tiny;
22
23
use Koha::BackgroundJobs;
24
use Koha::DateUtils qw( dt_from_string );
25
use Koha::SearchEngine::Indexer;
26
27
use C4::Biblio;
28
use C4::Items;
29
use MARC::Record;
30
use MARC::Field;
31
32
use base 'Koha::BackgroundJob';
33
34
=head1 NAME
35
36
Koha::BackgroundJob::BatchUpdateItem - Batch update item records
37
38
This is a subclass of Koha::BackgroundJob.
39
40
=head1 API
41
42
=head2 Class methods
43
44
=head3 job_type
45
46
Define the job type of this job: batch_item_record_modification
47
48
=cut
49
50
sub job_type {
51
    return 'batch_item_record_modification';
52
}
53
54
=head3 process
55
56
Process the modification.
57
58
=cut
59
60
sub process {
61
    my ( $self, $args ) = @_;
62
63
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
64
65
    if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) {
66
        return;
67
    }
68
69
    # FIXME If the job has already been started, but started again (worker has been restart for instance)
70
    # Then we will start from scratch and so double process the same records
71
72
    my $job_progress = 0;
73
    $job->started_on(dt_from_string)
74
        ->progress($job_progress)
75
        ->status('started')
76
        ->store;
77
78
    my @record_ids = @{ $args->{record_ids} };
79
80
    my $report = {
81
        total_records => scalar @record_ids,
82
        total_success => 0,
83
    };
84
    my @messages;
85
86
    my $tags      = $args->{tags};
87
    my $subfields = $args->{subfield};
88
    my $values    = $args->{values};
89
    my $indicator = $args->{indicator};
90
    my $ind_tag   = $args->{ind_tag};
91
    my $searches  = $args->{regex_search};
92
    my $replaces  = $args->{regex_replace};
93
    my $modifiers = $args->{regex_modifiers};
94
    my $disabled  = $args->{disabled};
95
    my $exclude_from_local_holds_priority = $args->{exclude_from_local_holds_priority};
96
97
    # Is there something to modify ?
98
    # TODO : We shall use this var to warn the user in case no modification was done to the items
99
    my $values_to_modify = scalar(grep {!/^$/} @$values) || scalar(grep {!/^$/} @$searches);
100
    my $values_to_blank  = scalar(@$disabled);
101
102
103
    #initializing values for updates
104
    my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField("items.itemnumber");
105
    my $marcitem;
106
    if ($values_to_modify) {
107
        my $xml =
108
          C4::Biblio::TransformHtmlToXml( $tags, $subfields, $values, $indicator,
109
            $ind_tag, 'ITEM' );
110
        $marcitem = MARC::Record::new_from_xml( $xml, 'UTF-8' );
111
    }
112
    if ($values_to_blank) {
113
        foreach my $disabledsubf (@$disabled) {
114
            if ( $marcitem && $marcitem->field($itemtagfield) ) {
115
                $marcitem->field($itemtagfield)->update( $disabledsubf => "" );
116
            }
117
            else {
118
                $marcitem = MARC::Record->new();
119
                $marcitem->append_fields(
120
                    MARC::Field->new(
121
                        $itemtagfield, '', '', $disabledsubf => ""
122
                    )
123
                );
124
            }
125
        }
126
    }
127
128
    my $upd_biblionumbers;
129
    RECORD_IDS: for my $itemnumber ( sort { $a <=> $b } @record_ids ) {
130
131
        last if $job->get_from_storage->status eq 'cancelled';
132
133
        try {
134
            my $schema = Koha::Database->new->schema;
135
            $schema->txn_do(
136
                sub {
137
                    my $item = Koha::Items->find($itemnumber);
138
                    next unless $item;
139
140
                    my $modified_holds_priority = 0;
141
                    if ( defined $exclude_from_local_holds_priority
142
                        && $exclude_from_local_holds_priority ne "" )
143
                    {
144
                        if ( !defined $item->exclude_from_local_holds_priority
145
                            || $item->exclude_from_local_holds_priority !=
146
                            $exclude_from_local_holds_priority )
147
                        {
148
                            $item->exclude_from_local_holds_priority(
149
                                $exclude_from_local_holds_priority)->store;
150
                            $modified_holds_priority = 1;
151
                        }
152
                    }
153
                    my $modified = 0;
154
                    if ( $values_to_modify || $values_to_blank ) {
155
                        my $localmarcitem = C4::Items::Item2Marc($item->unblessed);
156
157
                        for ( my $i = 0 ; $i < @$tags ; $i++ ) {
158
                            my $search = $searches->[$i];
159
                            next unless $search;
160
161
                            my $tag = $tags->[$i];
162
                            my $subfield = $subfields->[$i];
163
                            my $replace = $replaces->[$i];
164
165
                            my $value = $localmarcitem->field( $tag )->subfield( $subfield );
166
                            my $old_value = $value;
167
168
                            my @available_modifiers = qw( i g );
169
                            my $retained_modifiers = q||;
170
                            for my $modifier ( split //, $modifiers->[$i] ) {
171
                                $retained_modifiers .= $modifier
172
                                    if grep {/$modifier/} @available_modifiers;
173
                            }
174
                            if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
175
                                $value =~ s/$search/$replace/ig;
176
                            }
177
                            elsif ( $retained_modifiers eq 'i' ) {
178
                                $value =~ s/$search/$replace/i;
179
                            }
180
                            elsif ( $retained_modifiers eq 'g' ) {
181
                                $value =~ s/$search/$replace/g;
182
                            }
183
                            else {
184
                                $value =~ s/$search/$replace/;
185
                            }
186
187
                            my @fields_to = $localmarcitem->field($tag);
188
                            foreach my $field_to_update ( @fields_to ) {
189
                                unless ( $old_value eq $value ) {
190
                                    $modified++;
191
                                    $field_to_update->update( $subfield => $value );
192
                                }
193
                            }
194
                        }
195
196
                        $modified += UpdateMarcWith( $marcitem, $localmarcitem );
197
                        if ($modified) {
198
                            eval {
199
                                if (
200
                                    my $item = ModItemFromMarc(
201
                                        $localmarcitem,
202
                                        $item->biblionumber,
203
                                        $itemnumber,
204
                                        { skip_record_index => 1 },
205
                                    )
206
                                  )
207
                                {
208
                                    LostItem(
209
                                        $itemnumber,
210
                                        'batchmod',
211
                                        undef,
212
                                        { skip_record_index => 1 }
213
                                    ) if $item->{itemlost}
214
                                      and not $item->itemlost;
215
                                }
216
                            };
217
                            push @$upd_biblionumbers, $item->biblionumber;
218
                        }
219
                    }
220
                }
221
            );
222
223
            $report->{modified_items}++ if $modified || $modified_holds_priority;
224
            $report->{modified_fields} += $modified + $modified_holds_priority;
225
226
            push @messages, {
227
                type => 'success',
228
                code => 'item_modified',
229
                itemnumber => $item->itemnumber,
230
                biblionumber => $item->biblionumber,
231
            };
232
            $report->{total_success}++;
233
234
        } catch {
235
            warn $_;
236
            push @messages, {
237
                type => 'error',
238
                code => 'item_not_modified',
239
                itemnumber => $item->itemnumber,
240
                biblionumber => $item->biblionumber,
241
            };
242
243
        };
244
245
        $job->progress( ++$job_progress )->store;
246
    }
247
248
    $upd_biblionumbers = [ uniq @$upd_biblionumbers ]; # Only update each bib once
249
    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
250
    $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
251
252
    my $job_data = decode_json $job->data;
253
    $job_data->{messages} = \@messages;
254
    $job_data->{report} = $report;
255
256
    $job->ended_on(dt_from_string)
257
        ->data(encode_json $job_data);
258
    $job->status('finished') if $job->status ne 'cancelled';
259
    $job->store;
260
}
261
262
=head3 enqueue
263
264
Enqueue the new job
265
266
=cut
267
268
sub enqueue {
269
    my ( $self, $args) = @_;
270
271
    # TODO Raise exception instead
272
    return unless exists $args->{record_ids};
273
274
    my @record_ids = @{ $args->{record_ids} };
275
276
    $self->SUPER::enqueue({
277
        job_size => scalar @record_ids,
278
        job_args => {%$args},
279
    });
280
}
281
282
#BE WARN : it is not the general case
283
# This function can be OK in the item marc record special case
284
# Where subfield is not repeated
285
# And where we are sure that field should correspond
286
# And $tag>10
287
sub UpdateMarcWith {
288
  my ($marcfrom,$marcto)=@_;
289
    my (  $itemtag,   $itemtagsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
290
    my $fieldfrom=$marcfrom->field($itemtag);
291
    my @fields_to=$marcto->field($itemtag);
292
    my $modified = 0;
293
294
    return $modified unless $fieldfrom;
295
296
    foreach my $subfield ( $fieldfrom->subfields() ) {
297
        foreach my $field_to_update ( @fields_to ) {
298
            if ( $subfield->[1] ) {
299
                unless ( $field_to_update->subfield($subfield->[0]) eq $subfield->[1] ) {
300
                    $modified++;
301
                    $field_to_update->update( $subfield->[0] => $subfield->[1] );
302
                }
303
            }
304
            else {
305
                $modified++;
306
                $field_to_update->delete_subfield( code => $subfield->[0] );
307
            }
308
        }
309
    }
310
    return $modified;
311
}
312
313
1;
(-)a/debian/templates/apache-shared-intranet-plack.conf (-1 lines)
Lines 13-19 Link Here
13
        # don't break under plack/starman
13
        # don't break under plack/starman
14
        ProxyPass "/cgi-bin/koha/offline_circ/process_koc.pl" "!"
14
        ProxyPass "/cgi-bin/koha/offline_circ/process_koc.pl" "!"
15
        ProxyPass "/cgi-bin/koha/tools/background-job-progress.pl" "!"
15
        ProxyPass "/cgi-bin/koha/tools/background-job-progress.pl" "!"
16
        ProxyPass "/cgi-bin/koha/tools/batchMod.pl" "!"
17
        ProxyPass "/cgi-bin/koha/tools/export.pl" "!"
16
        ProxyPass "/cgi-bin/koha/tools/export.pl" "!"
18
        ProxyPass "/cgi-bin/koha/tools/manage-marc-import.pl" "!"
17
        ProxyPass "/cgi-bin/koha/tools/manage-marc-import.pl" "!"
19
        ProxyPass "/cgi-bin/koha/tools/stage-marc-import.pl" "!"
18
        ProxyPass "/cgi-bin/koha/tools/stage-marc-import.pl" "!"
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_item_record_modification.inc (+64 lines)
Line 0 Link Here
1
[% BLOCK report %]
2
    [% SET report = job.report %]
3
    [% IF report %]
4
        [% IF report.total_records == report.total_success %]
5
            <div class="dialog message">
6
                All records have successfully been modified! <a href="/cgi-bin/koha/tools/batch_record_modification.pl" title="New batch record modification">New batch record modification</a>
7
                [% IF lists.count %]
8
                    <br />
9
                    Add modified records to the following list:
10
                    <select name="add_bibs_to_list" id="add_bibs_to_list">
11
                        <option value="">Select a list</option>
12
                        [% FOREACH list IN lists %]
13
                            <option class="shelf" value="[% list.shelfnumber | html %]">[% list.shelfname | html %]</option>
14
                        [% END %]
15
                    </select>
16
                [% END %]
17
            </div>
18
        [% ELSE %]
19
            <div class="dialog message">
20
                [% report.total_success | html %] / [% report.total_records | html %] records have successfully been modified. Some errors occurred.
21
                [% IF job.status == 'cancelled' %]The job has been cancelled before it finished.[% END %]
22
                <a href="/cgi-bin/koha/tools/batch_record_modification.pl" title="New batch record modification">New batch record modification</a>
23
            </div>
24
        [% END %]
25
    [% END %]
26
[% END %]
27
28
[% BLOCK detail %]
29
    [% FOR m IN job.messages %]
30
        <div class="dialog message">
31
            [% IF m.type == 'success' %]
32
                <i class="fa fa-check success"></i>
33
            [% ELSIF m.type == 'warning' %]
34
                <i class="fa fa-warning warn"></i>
35
            [% ELSIF m.type == 'error' %]
36
                <i class="fa fa-exclamation error"></i>
37
            [% END %]
38
            [% SWITCH m.code %]
39
            [% CASE 'biblio_not_modified' %]
40
                Bibliographic record <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% m.biblionumber | uri %]">[% m.biblionumber | html %]</a> has not been modified. An error occurred on modifying it.[% IF m.error %] ([% m.error | html %])[% END %].
41
            [% CASE 'biblio_modified' %]
42
                Bibliographic record <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% m.biblionumber | uri %]">[% m.biblionumber | html %]</a> has successfully been modified.
43
            [% END %]
44
        </div>
45
    [% END %]
46
[% END %]
47
48
[% BLOCK js %]
49
    $("#add_bibs_to_list").change(function(){
50
        var selected = $("#add_bibs_to_list").find("option:selected");
51
        if ( selected.attr("class") == "shelf" ){
52
            var shelfnumber = selected.attr("value");
53
            var bibs = new Array();
54
            [% FOREACH message IN job.messages %]
55
                [% IF message.code == 'biblio_modified' %]
56
                    bibs.push("biblionumber="+[% message.biblionumber | html %]);
57
                [% END %]
58
            [% END %]
59
            var bibstring = bibs.join("&");
60
            window.open('/cgi-bin/koha/virtualshelves/addbybiblionumber.pl?shelfnumber='+shelfnumber+'&confirm=1&'+bibstring, 'popup', 'width=500,height=500,toolbar=false,scrollbars=yes,resizeable=yes');
61
            return false;
62
        }
63
    });
64
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batchMod-edit.tt (-11 / +29 lines)
Lines 31-46 Link Here
31
31
32
    <div class="main container-fluid">
32
    <div class="main container-fluid">
33
33
34
        [% IF ( show ) %]
34
        <h1>Batch item modification</h1>
35
            <h1>Batch item modification</h1>
35
        [% IF op == 'enqueued' %]
36
        [% ELSE %]
37
            <h1>Batch item modification results</h1>
38
            <div class="dialog message">
36
            <div class="dialog message">
39
                [% IF (modified_items) %]
37
              <p>The job has been enqueued! It will be processed as soon as possible.</p>
40
                    [% modified_items | html %] item(s) modified (with [% modified_fields | html %] field(s) modified).
38
              <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>
41
                [% ELSE %]
39
              | <a href="/cgi-bin/koha/tools/batchMod.pl" title="New batch item modification">New batch item modification</a></p>
42
                    No items modified.
40
            </div>
43
                [% END %]
41
        [% END %]
42
44
                <fieldset class="action">
43
                <fieldset class="action">
45
                    [% IF src == 'CATALOGUING' # from catalogue/detail.pl > Edit items in a batch%]
44
                    [% IF src == 'CATALOGUING' # from catalogue/detail.pl > Edit items in a batch%]
46
                        [% IF searchid %]
45
                        [% IF searchid %]
Lines 55-61 Link Here
55
                    [% END %]
54
                    [% END %]
56
                </fieldset>
55
                </fieldset>
57
            </div> <!-- /.dialog.message -->
56
            </div> <!-- /.dialog.message -->
58
        [% END # /IF show %]
57
58
        [% FOREACH message IN messages %]
59
          [% IF message.type == 'success' %]
60
            <div class="dialog message">
61
          [% ELSIF message.type == 'warning' %]
62
            <div class="dialog alert">
63
          [% ELSIF message.type == 'error' %]
64
            <div class="dialog alert" style="margin:auto;">
65
          [% END %]
66
          [% IF message.code == 'cannot_enqueue_job' %]
67
              Cannot enqueue this job.
68
          [% END %]
69
          [% IF message.error %]
70
            (The error was: [% message.error | html %], see the Koha log file for more information).
71
          [% END %]
72
          </div>
73
        [% END %]
74
59
75
60
        [% IF ( barcode_not_unique ) %]
76
        [% IF ( barcode_not_unique ) %]
61
            <div class="dialog alert">
77
            <div class="dialog alert">
Lines 151-156 Link Here
151
                        [% END %]
167
                        [% END %]
152
                    </p> <!-- /#selections -->
168
                    </p> <!-- /#selections -->
153
169
170
[#% TODO We need to refactor the pl to make this reusable from batchMod and BatchUpdateItem %]
171
                    [% PROCESS item_table headers => item_header_loop, items => item_loop %]
154
                    <table id="itemst">
172
                    <table id="itemst">
155
                        <thead>
173
                        <thead>
156
                            <tr>
174
                            <tr>
Lines 359-365 Link Here
359
                        <a class="btn btn-default" href="/cgi-bin/koha/tools/batchMod.pl"><i class="fa fa-check-square-o"></i> Return to batch item modification</a>
377
                        <a class="btn btn-default" href="/cgi-bin/koha/tools/batchMod.pl"><i class="fa fa-check-square-o"></i> Return to batch item modification</a>
360
                    [% END %]
378
                    [% END %]
361
                </fieldset> <!-- /.action -->
379
                </fieldset> <!-- /.action -->
362
            [% END #/IF show %]
380
            [% END %]
363
        </form>
381
        </form>
364
382
365
    [% MACRO jsinclude BLOCK %]
383
    [% MACRO jsinclude BLOCK %]
(-)a/misc/background_jobs_worker.pl (+2 lines)
Lines 31-38 try { Link Here
31
my @job_types = qw(
31
my @job_types = qw(
32
    batch_biblio_record_modification
32
    batch_biblio_record_modification
33
    batch_authority_record_modification
33
    batch_authority_record_modification
34
    batch_item_record_modification
34
    batch_biblio_record_deletion
35
    batch_biblio_record_deletion
35
    batch_authority_record_deletion
36
    batch_authority_record_deletion
37
    batch_item_record_deletion
36
);
38
);
37
39
38
if ( $conn ) {
40
if ( $conn ) {
(-)a/tools/batchMod.pl (-195 / +35 lines)
Lines 45-50 use Koha::Items; Link Here
45
use Koha::ItemTypes;
45
use Koha::ItemTypes;
46
use Koha::Patrons;
46
use Koha::Patrons;
47
use Koha::SearchEngine::Indexer;
47
use Koha::SearchEngine::Indexer;
48
use Koha::BackgroundJob::BatchDeleteItem;
49
use Koha::BackgroundJob::BatchUpdateItem;
48
50
49
my $input = CGI->new;
51
my $input = CGI->new;
50
my $dbh = C4::Context->dbh;
52
my $dbh = C4::Context->dbh;
Lines 102-175 my $modified_fields = 0; # Numbers of modified fields Link Here
102
my %cookies = parse CGI::Cookie($cookie);
104
my %cookies = parse CGI::Cookie($cookie);
103
my $sessionID = $cookies{'CGISESSID'}->value;
105
my $sessionID = $cookies{'CGISESSID'}->value;
104
106
107
my @messages;
105
108
106
#--- ----------------------------------------------------------------------------
109
#--- ----------------------------------------------------------------------------
107
if ($op eq "action") {
110
if ($op eq "action") {
108
#-------------------------------------------------------------------------------
111
#-------------------------------------------------------------------------------
109
    my @tags      = $input->multi_param('tag');
110
    my @subfields = $input->multi_param('subfield');
111
    my @values    = $input->multi_param('field_value');
112
    my @searches  = $input->multi_param('regex_search');
113
    my @replaces  = $input->multi_param('regex_replace');
114
    my @modifiers = $input->multi_param('regex_modifiers');
115
    my @disabled  = $input->multi_param('disable_input');
116
    # build indicator hash.
117
    my @ind_tag   = $input->multi_param('ind_tag');
118
    my @indicator = $input->multi_param('indicator');
119
120
    # Is there something to modify ?
121
    # TODO : We shall use this var to warn the user in case no modification was done to the items
122
    my $values_to_modify = scalar(grep {!/^$/} @values) || scalar(grep {!/^$/} @searches);
123
    my $values_to_blank  = scalar(@disabled);
124
125
    my $marcitem;
126
127
    # Once the job is done
128
    if ($completedJobID) {
129
	# If we have a reasonable amount of items, we display them
130
    my $max_items = $del ? C4::Context->preference("MaxItemsToDisplayForBatchDel") : C4::Context->preference("MaxItemsToDisplayForBatchMod");
131
    if (scalar(@itemnumbers) <= $max_items ){
132
        if (scalar(@itemnumbers) <= 1000 ) {
133
            $items_display_hashref=BuildItemsData(@itemnumbers);
134
        } else {
135
            # Else, we only display the barcode
136
            my @simple_items_display = map {
137
                my $itemnumber = $_;
138
                my $item = Koha::Items->find($itemnumber);
139
                {
140
                    itemnumber   => $itemnumber,
141
                    barcode      => $item ? ( $item->barcode // q{} ) : q{},
142
                    biblionumber => $item ? $item->biblio->biblionumber : q{},
143
                };
144
            } @itemnumbers;
145
            $template->param("simple_items_display" => \@simple_items_display);
146
        }
147
    } else {
148
        $template->param( "too_many_items_display" => scalar(@itemnumbers) );
149
        $template->param( "job_completed" => 1 );
150
    }
151
152
    } else {
153
    # While the job is getting done
154
155
	#initializing values for updates
156
    my (  $itemtagfield,   $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
157
	if ($values_to_modify){
158
	    my $xml = TransformHtmlToXml(\@tags,\@subfields,\@values,\@indicator,\@ind_tag, 'ITEM');
159
	    $marcitem = MARC::Record::new_from_xml($xml, 'UTF-8');
160
        }
161
        if ($values_to_blank){
162
	    foreach my $disabledsubf (@disabled){
163
		if ($marcitem && $marcitem->field($itemtagfield)){
164
		    $marcitem->field($itemtagfield)->update( $disabledsubf => "" );
165
		}
166
		else {
167
		    $marcitem = MARC::Record->new();
168
		    $marcitem->append_fields( MARC::Field->new( $itemtagfield, '', '', $disabledsubf => "" ) );
169
		}
170
	    }
171
        }
172
112
113
    if ( $del ) {
173
        my $upd_biblionumbers;
114
        my $upd_biblionumbers;
174
        my $del_biblionumbers;
115
        my $del_biblionumbers;
175
        try {
116
        try {
Lines 218-301 if ($op eq "action") { Link Here
218
                                }
159
                                }
219
                            }
160
                            }
220
                        }
161
                        }
221
                        else {
222
                            my $modified_holds_priority = 0;
223
                            if ( defined $exclude_from_local_holds_priority && $exclude_from_local_holds_priority ne "" ) {
224
                                if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
225
                                $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
226
                                $modified_holds_priority = 1;
227
                            }
228
                            }
229
                            my $modified = 0;
230
                            if ( $values_to_modify || $values_to_blank ) {
231
                                my $localmarcitem = Item2Marc($itemdata);
232
233
                                for ( my $i = 0 ; $i < @tags ; $i++ ) {
234
                                    my $search = $searches[$i];
235
                                    next unless $search;
236
237
                                    my $tag = $tags[$i];
238
                                    my $subfield = $subfields[$i];
239
                                    my $replace = $replaces[$i];
240
241
                                    my $value = $localmarcitem->field( $tag )->subfield( $subfield );
242
                                    my $old_value = $value;
243
244
                                    my @available_modifiers = qw( i g );
245
                                    my $retained_modifiers = q||;
246
                                    for my $modifier ( split //, $modifiers[$i] ) {
247
                                        $retained_modifiers .= $modifier
248
                                            if grep {/$modifier/} @available_modifiers;
249
                                    }
250
                                    if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
251
                                        $value =~ s/$search/$replace/ig;
252
                                    }
253
                                    elsif ( $retained_modifiers eq 'i' ) {
254
                                        $value =~ s/$search/$replace/i;
255
                                    }
256
                                    elsif ( $retained_modifiers eq 'g' ) {
257
                                        $value =~ s/$search/$replace/g;
258
                                    }
259
                                    else {
260
                                        $value =~ s/$search/$replace/;
261
                                    }
262
263
                                    my @fields_to = $localmarcitem->field($tag);
264
                                    foreach my $field_to_update ( @fields_to ) {
265
                                        unless ( $old_value eq $value ) {
266
                                            $modified++;
267
                                            $field_to_update->update( $subfield => $value );
268
                                        }
269
                                    }
270
                                }
271
272
                                $modified += UpdateMarcWith( $marcitem, $localmarcitem );
273
                                if ($modified) {
274
                                    eval {
275
                                        if (
276
                                            my $item = ModItemFromMarc(
277
                                                $localmarcitem,
278
                                                $itemdata->{biblionumber},
279
                                                $itemnumber,
280
                                                { skip_record_index => 1 },
281
                                            )
282
                                          )
283
                                        {
284
                                            LostItem(
285
                                                $itemnumber,
286
                                                'batchmod',
287
                                                undef,
288
                                                { skip_record_index => 1 }
289
                                            ) if $item->{itemlost}
290
                                              and not $itemdata->{itemlost};
291
                                        }
292
                                    };
293
                                    push @$upd_biblionumbers, $itemdata->{'biblionumber'};
294
                                }
295
                            }
296
                            $modified_items++ if $modified || $modified_holds_priority;
297
                            $modified_fields += $modified + $modified_holds_priority;
298
                        }
299
                        $i++;
162
                        $i++;
300
                    }
163
                    }
301
                    if (@not_deleted) {
164
                    if (@not_deleted) {
Lines 321-335 if ($op eq "action") { Link Here
321
        my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
184
        my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
322
        $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
185
        $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
323
        $indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers;
186
        $indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers;
187
    } else {
188
        my $params = {
189
            tags       => [ $input->multi_param('tag') ],
190
            subfields  => [ $input->multi_param('subfield') ],
191
            values     => [ $input->multi_param('field_value') ],
192
            searches   => [ $input->multi_param('regex_search') ],
193
            replaces   => [ $input->multi_param('regex_replace') ],
194
            modifiers  => [ $input->multi_param('regex_modifiers') ],
195
            disabled   => [ $input->multi_param('disable_input') ],
196
            ind_tag    => [ $input->multi_param('ind_tag') ],
197
            indicator  => [ $input->multi_param('indicator') ],
198
            record_ids => \@itemnumbers,
199
        };
200
        try {
201
            my $job_id = Koha::BackgroundJob::BatchUpdateItem->new->enqueue($params);
202
            $nextop = 'enqueued';
203
            $template->param(
204
                job_id => $job_id,
205
            );
206
        } catch {
207
            push @messages, {
208
                type => 'error',
209
                code => 'cannot_enqueue_job',
210
                error => $_,
211
            };
212
            $template->param( view => 'errors' );
213
        };
324
    }
214
    }
325
215
326
    # Calling the template
327
    $template->param(
328
        modified_items => $modified_items,
329
        modified_fields => $modified_fields,
330
    );
331
332
}
216
}
217
218
$template->param(
219
    messages => \@messages,
220
);
333
#
221
#
334
#-------------------------------------------------------------------------------
222
#-------------------------------------------------------------------------------
335
# build screen with existing items. and "new" one
223
# build screen with existing items. and "new" one
Lines 719-768 sub BuildItemsData{ Link Here
719
607
720
	return { item_loop        => \@item_value_loop, item_header_loop => \@header_loop };
608
	return { item_loop        => \@item_value_loop, item_header_loop => \@header_loop };
721
}
609
}
722
723
#BE WARN : it is not the general case 
724
# This function can be OK in the item marc record special case
725
# Where subfield is not repeated
726
# And where we are sure that field should correspond
727
# And $tag>10
728
sub UpdateMarcWith {
729
  my ($marcfrom,$marcto)=@_;
730
    my (  $itemtag,   $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
731
    my $fieldfrom=$marcfrom->field($itemtag);
732
    my @fields_to=$marcto->field($itemtag);
733
    my $modified = 0;
734
735
    return $modified unless $fieldfrom;
736
737
    foreach my $subfield ( $fieldfrom->subfields() ) {
738
        foreach my $field_to_update ( @fields_to ) {
739
            if ( $subfield->[1] ) {
740
                unless ( $field_to_update->subfield($subfield->[0]) eq $subfield->[1] ) {
741
                    $modified++;
742
                    $field_to_update->update( $subfield->[0] => $subfield->[1] );
743
                }
744
            }
745
            else {
746
                $modified++;
747
                $field_to_update->delete_subfield( code => $subfield->[0] );
748
            }
749
        }
750
    }
751
    return $modified;
752
}
753
754
sub find_value {
755
    my ($tagfield,$insubfield,$record) = @_;
756
    my $result;
757
    my $indicator;
758
    foreach my $field ($record->field($tagfield)) {
759
        my @subfields = $field->subfields();
760
        foreach my $subfield (@subfields) {
761
            if (@$subfield[0] eq $insubfield) {
762
                $result .= @$subfield[1];
763
                $indicator = $field->indicator(1).$field->indicator(2);
764
            }
765
        }
766
    }
767
    return($indicator,$result);
768
}
769
- 

Return to bug 28445