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 198-204 sub report { Link Here
198
    my ( $self ) = @_;
200
    my ( $self ) = @_;
199
201
200
    my $data_dump = decode_json $self->data;
202
    my $data_dump = decode_json $self->data;
201
    return $data_dump->{report};
203
    return $data_dump->{report} || {};
202
}
204
}
203
205
204
=head3 additional_report
206
=head3 additional_report
Lines 235-244 sub _derived_class { Link Here
235
      ? Koha::BackgroundJob::BatchUpdateBiblio->new
237
      ? Koha::BackgroundJob::BatchUpdateBiblio->new
236
      : $job_type eq 'batch_authority_record_modification'
238
      : $job_type eq 'batch_authority_record_modification'
237
      ? Koha::BackgroundJob::BatchUpdateAuthority->new
239
      ? Koha::BackgroundJob::BatchUpdateAuthority->new
240
      : $job_type eq 'batch_item_record_modification'
241
      ? Koha::BackgroundJob::BatchUpdateItem->new
238
      : $job_type eq 'batch_biblio_record_deletion'
242
      : $job_type eq 'batch_biblio_record_deletion'
239
      ? Koha::BackgroundJob::BatchDeleteBiblio->new
243
      ? Koha::BackgroundJob::BatchDeleteBiblio->new
240
      : $job_type eq 'batch_authority_record_deletion'
244
      : $job_type eq 'batch_authority_record_deletion'
241
      ? Koha::BackgroundJob::BatchDeleteAuthority->new
245
      ? Koha::BackgroundJob::BatchDeleteAuthority->new
246
      : $job_type eq 'batch_item_record_deletion'
247
      ? Koha::BackgroundJob::BatchDeleteItem->new
242
      : Koha::Exceptions::Exception->throw($job_type . ' is not a valid job_type')
248
      : Koha::Exceptions::Exception->throw($job_type . ' is not a valid job_type')
243
}
249
}
244
250
(-)a/Koha/BackgroundJob/BatchDeleteItem.pm (+227 lines)
Line 0 Link Here
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::BackgroundJobs;
30
use Koha::DateUtils qw( dt_from_string );
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
    my $job_type = $args->{job_type};
78
79
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
80
81
    if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) {
82
        return;
83
    }
84
85
    # FIXME If the job has already been started, but started again (worker has been restart for instance)
86
    # Then we will start from scratch and so double delete the same records
87
88
    my $job_progress = 0;
89
    $job->started_on(dt_from_string)->progress($job_progress)
90
      ->status('started')->store;
91
92
    my @record_ids     = @{ $args->{record_ids} };
93
    my $delete_biblios = $args->{delete_biblios};
94
95
    my $report = {
96
        total_records => scalar @record_ids,
97
        total_success => 0,
98
    };
99
    my @messages;
100
    my $schema = Koha::Database->new->schema;
101
    my ( @deleted_itemnumbers, @not_deleted_itemnumbers,
102
        @deleted_biblionumbers );
103
104
    try {
105
        my $schema = Koha::Database->new->schema;
106
        $schema->txn_do(
107
            sub {
108
                my (@biblionumbers);
109
                for my $record_id ( sort { $a <=> $b } @record_ids ) {
110
111
                    last if $job->get_from_storage->status eq 'cancelled';
112
113
                    my $item = Koha::Items->find($record_id) || next;
114
115
                    my $return = $item->safe_delete;
116
                    unless ( ref($return) ) {
117
118
                        # FIXME Do we need to rollback the whole transaction if a deletion failed?
119
                        push @not_deleted_itemnumbers, $item->itemnumber;
120
                        push @messages,
121
                          {
122
                            type         => 'error',
123
                            code         => 'item_not_deleted',
124
                            itemnumber   => $item->itemnumber,
125
                            biblionumber => $item->biblionumber,
126
                            barcode      => $item->barcode,
127
                            title        => $item->biblio->title,
128
                            reason       => $return,
129
                          };
130
131
                        next;
132
                    }
133
134
                    push @deleted_itemnumbers, $item->itemnumber;
135
                    push @biblionumbers,       $item->biblionumber;
136
137
                    $report->{total_success}++;
138
                    $job->progress( ++$job_progress )->store;
139
                }
140
141
                # If there are no items left, delete the biblio
142
                if ( $delete_biblios && @biblionumbers ) {
143
                    for my $biblionumber ( uniq @biblionumbers ) {
144
                        my $items_count =
145
                          Koha::Biblios->find($biblionumber)->items->count;
146
                        if ( $items_count == 0 ) {
147
                            my $error = C4::Biblio::DelBiblio( $biblionumber,
148
                                { skip_record_index => 1 } );
149
                            unless ($error) {
150
                                push @deleted_biblionumbers, $biblionumber;
151
                            }
152
                        }
153
                    }
154
155
                    if (@deleted_biblionumbers) {
156
                        my $indexer = Koha::SearchEngine::Indexer->new(
157
                            { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
158
159
                        $indexer->index_records( \@deleted_biblionumbers,
160
                            'recordDelete', "biblioserver", undef );
161
                    }
162
                }
163
            }
164
        );
165
    }
166
    catch {
167
168
        warn $_;
169
170
        push @messages,
171
          {
172
            type  => 'error',
173
            code  => 'unknown',
174
            error => $_,
175
          };
176
177
        die "Something terrible has happened!"
178
          if ( $_ =~ /Rollback failed/ );    # Rollback failed
179
    };
180
181
    $report->{deleted_itemnumbers}     = \@deleted_itemnumbers;
182
    $report->{not_deleted_itemnumbers} = \@not_deleted_itemnumbers;
183
    $report->{deleted_biblionumbers}   = \@deleted_biblionumbers;
184
185
    my $job_data = decode_json $job->data;
186
    $job_data->{messages} = \@messages;
187
    $job_data->{report}   = $report;
188
189
    $job->ended_on(dt_from_string)->data( encode_json $job_data);
190
    $job->status('finished') if $job->status ne 'cancelled';
191
    $job->store;
192
}
193
194
=head3 enqueue
195
196
    Koha::BackgroundJob::BatchDeleteItem->new->enqueue(
197
        {
198
            record_ids => \@itemnumbers,
199
            deleted_biblios => 0|1,
200
        }
201
    );
202
203
Enqueue the job.
204
205
=cut
206
207
sub enqueue {
208
    my ( $self, $args ) = @_;
209
210
    # TODO Raise exception instead
211
    return unless exists $args->{record_ids};
212
213
    my @record_ids = @{ $args->{record_ids} };
214
    my $delete_biblios = @{ $args->{delete_biblios} || [] };
215
216
    $self->SUPER::enqueue(
217
        {
218
            job_size => scalar @record_ids,
219
            job_args => {
220
                record_ids     => \@record_ids,
221
                delete_biblios => $delete_biblios,
222
            }
223
        }
224
    );
225
}
226
227
1;
(-)a/Koha/BackgroundJob/BatchUpdateItem.pm (+193 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 MARC::Record;
24
use MARC::Field;
25
26
use C4::Biblio;
27
use C4::Items;
28
29
use Koha::BackgroundJobs;
30
use Koha::DateUtils qw( dt_from_string );
31
use Koha::SearchEngine::Indexer;
32
use Koha::Items;
33
use Koha::UI::Table::Builder::Items;
34
35
use base 'Koha::BackgroundJob';
36
37
=head1 NAME
38
39
Koha::BackgroundJob::BatchUpdateItem - Background job derived class to process item modification in batch
40
41
=head1 API
42
43
=head2 Class methods
44
45
=head3 job_type
46
47
Define the job type of this job: batch_item_record_modification
48
49
=cut
50
51
sub job_type {
52
    return 'batch_item_record_modification';
53
}
54
55
=head3 process
56
57
    Koha::BackgroundJobs->find($id)->process(
58
        {
59
            record_ids => \@itemnumbers,
60
            new_values => {
61
                itemnotes => $new_item_notes,
62
                k         => $k,
63
            },
64
            regex_mod => {
65
                itemnotes_nonpublic => {
66
                    search => 'foo',
67
                    replace => 'bar',
68
                    modifiers => 'gi',
69
                },
70
            },
71
            exclude_from_local_holds_priority => 1|0
72
        }
73
    );
74
75
Process the modification.
76
77
new_values allows to set a new value for given fields.
78
The key can be one of the item's column name, or one subfieldcode of a MARC subfields not linked with a Koha field.
79
80
regex_mod allows to modify existing subfield's values using a regular expression.
81
82
=cut
83
84
sub process {
85
    my ( $self, $args ) = @_;
86
87
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
88
89
    if ( !exists $args->{job_id} || !$job || $job->status eq 'cancelled' ) {
90
        return;
91
    }
92
93
    # FIXME If the job has already been started, but started again (worker has been restart for instance)
94
    # Then we will start from scratch and so double process the same records
95
96
    my $job_progress = 0;
97
    $job->started_on(dt_from_string)->progress($job_progress)
98
      ->status('started')->store;
99
100
    my @record_ids = @{ $args->{record_ids} };
101
    my $regex_mod  = $args->{regex_mod};
102
    my $new_values = $args->{new_values};
103
    my $exclude_from_local_holds_priority =
104
      $args->{exclude_from_local_holds_priority};
105
106
    my $report = {
107
        total_records            => scalar @record_ids,
108
        modified_itemitemnumbers => [],
109
        modified_fields          => 0,
110
    };
111
112
    try {
113
        my $schema = Koha::Database->new->schema;
114
        $schema->txn_do(
115
            sub {
116
                my ($results) =
117
                  Koha::Items->search( { itemnumber => \@record_ids } )
118
                  ->batch_update(
119
                    {
120
                        regex_mod  => $regex_mod,
121
                        new_values => $new_values,
122
                        exclude_from_local_holds_priority =>
123
                          $exclude_from_local_holds_priority,
124
                        callback => sub {
125
                            my ($progress) = @_;
126
                            $job->progress($progress)->store;
127
                        },
128
                    }
129
                  );
130
                $report->{modified_itemnumbers} = $results->{modified_itemnumbers};
131
                $report->{modified_fields}      = $results->{modified_fields};
132
            }
133
        );
134
    }
135
    catch {
136
        warn $_;
137
        die "Something terrible has happened!"
138
          if ( $_ =~ /Rollback failed/ );    # Rollback failed
139
    };
140
141
    my $job_data = decode_json $job->data;
142
    $job_data->{report} = $report;
143
144
    $job->ended_on(dt_from_string)->data( encode_json $job_data);
145
    $job->status('finished') if $job->status ne 'cancelled';
146
    $job->store;
147
}
148
149
=head3 enqueue
150
151
Enqueue the new job
152
153
=cut
154
155
sub enqueue {
156
    my ( $self, $args ) = @_;
157
158
    # TODO Raise exception instead
159
    return unless exists $args->{record_ids};
160
161
    my @record_ids = @{ $args->{record_ids} };
162
163
    $self->SUPER::enqueue(
164
        {
165
            job_size => scalar @record_ids,
166
            job_args => {%$args},
167
        }
168
    );
169
}
170
171
=head3 additional_report
172
173
Sent the infos to generate the table containing the details of the modified items.
174
175
=cut
176
177
sub additional_report {
178
    my ( $self, $args ) = @_;
179
180
    my $job = Koha::BackgroundJobs->find( $args->{job_id} );
181
182
    my $itemnumbers = $job->report->{modified_itemnumbers};
183
    my $items_table =
184
      Koha::UI::Table::Builder::Items->new( { itemnumbers => $itemnumbers } )
185
      ->build_table;
186
187
    return {
188
        items            => $items_table->{items},
189
        item_header_loop => $items_table->{headers},
190
    };
191
}
192
193
1;
(-)a/Koha/Item.pm (-23 / +60 lines)
Lines 26-31 use Koha::Database; Link Here
26
use Koha::DateUtils qw( dt_from_string output_pref );
26
use Koha::DateUtils qw( dt_from_string output_pref );
27
27
28
use C4::Context;
28
use C4::Context;
29
use C4::Biblio qw( GetMarcStructure GetMarcSubfieldStructure GetMarcFromKohaField );
29
use C4::Circulation qw( GetBranchItemRule );
30
use C4::Circulation qw( GetBranchItemRule );
30
use C4::Reserves;
31
use C4::Reserves;
31
use C4::ClassSource qw( GetClassSort );
32
use C4::ClassSource qw( GetClassSort );
Lines 38-43 use Koha::SearchEngine::Indexer; Link Here
38
use Koha::Exceptions::Item::Transfer;
39
use Koha::Exceptions::Item::Transfer;
39
use Koha::Item::Transfer::Limits;
40
use Koha::Item::Transfer::Limits;
40
use Koha::Item::Transfers;
41
use Koha::Item::Transfers;
42
use Koha::Item::Attributes;
41
use Koha::ItemTypes;
43
use Koha::ItemTypes;
42
use Koha::Patrons;
44
use Koha::Patrons;
43
use Koha::Plugins;
45
use Koha::Plugins;
Lines 847-854 sub has_pending_hold { Link Here
847
849
848
=head3 as_marc_field
850
=head3 as_marc_field
849
851
850
    my $mss   = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
852
    my $field = $item->as_marc_field;
851
    my $field = $item->as_marc_field({ [ mss => $mss ] });
852
853
853
This method returns a MARC::Field object representing the Koha::Item object
854
This method returns a MARC::Field object representing the Koha::Item object
854
with the current mappings configuration.
855
with the current mappings configuration.
Lines 856-892 with the current mappings configuration. Link Here
856
=cut
857
=cut
857
858
858
sub as_marc_field {
859
sub as_marc_field {
859
    my ( $self, $params ) = @_;
860
    my ( $self ) = @_;
861
862
    my ( $itemtag, $itemtagsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
860
863
861
    my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
864
    my $tagslib = C4::Biblio::GetMarcStructure( 1, $self->biblio->frameworkcode, { unsafe => 1 });
862
    my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
863
865
864
    my @subfields;
866
    my @subfields;
865
867
866
    my @columns = $self->_result->result_source->columns;
868
    my $item_field = $tagslib->{$itemtag};
867
869
868
    foreach my $item_field ( @columns ) {
870
    my $more_subfields = $self->additional_attributes->to_hashref;
869
        my $mapping = $mss->{ "items.$item_field"}[0];
871
    foreach my $subfield (
870
        my $tagfield    = $mapping->{tagfield};
872
        sort {
871
        my $tagsubfield = $mapping->{tagsubfield};
873
               $a->{display_order} <=> $b->{display_order}
872
        next if !$tagfield; # TODO: Should we raise an exception instead?
874
            || $a->{subfield} cmp $b->{subfield}
873
                            # Feels like safe fallback is better
875
        } grep { ref($_) && %$_ } values %$item_field
876
    ){
874
877
875
        push @subfields, $tagsubfield => $self->$item_field
878
        my $kohafield = $subfield->{kohafield};
876
            if defined $self->$item_field and $item_field ne '';
879
        my $tagsubfield = $subfield->{tagsubfield};
877
    }
880
        my $value;
881
        if ( defined $kohafield ) {
882
            next if $kohafield !~ m{^items\.}; # That would be weird!
883
            ( my $attribute = $kohafield ) =~ s|^items\.||;
884
            $value = $self->$attribute # This call may fail if a kohafield is not a DB column but we don't want to add extra work for that there
885
                if defined $self->$attribute and $self->$attribute ne '';
886
        } else {
887
            $value = $more_subfields->{$tagsubfield}
888
        }
889
890
        next unless defined $value
891
            and $value ne q{};
878
892
879
    my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
893
        if ( $subfield->{repeatable} ) {
880
    push( @subfields, @{$unlinked_item_subfields} )
894
            my @values = split '\|', $value;
881
        if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
895
            push @subfields, ( $tagsubfield => $_ ) for @values;
896
        }
897
        else {
898
            push @subfields, ( $tagsubfield => $value );
899
        }
882
900
883
    my $field;
901
    }
884
902
885
    $field = MARC::Field->new(
903
    return unless @subfields;
886
        "$item_tag", ' ', ' ', @subfields
887
    ) if @subfields;
888
904
889
    return $field;
905
    return MARC::Field->new(
906
        "$itemtag", ' ', ' ', @subfields
907
    );
890
}
908
}
891
909
892
=head3 renewal_branchcode
910
=head3 renewal_branchcode
Lines 1010-1015 sub columns_to_str { Link Here
1010
    return $values;
1028
    return $values;
1011
}
1029
}
1012
1030
1031
=head3 additional_attributes
1032
1033
    my $attributes = $item->additional_attributes;
1034
    $attributes->{k} = 'new k';
1035
    $item->update({ more_subfields => $attributes->to_marcxml });
1036
1037
Returns a Koha::Item::Attributes object that represents the non-mapped
1038
attributes for this item.
1039
1040
=cut
1041
1042
sub additional_attributes {
1043
    my ($self) = @_;
1044
1045
    return Koha::Item::Attributes->new_from_marcxml(
1046
        $self->more_subfields_xml,
1047
    );
1048
}
1049
1013
=head3 _set_found_trigger
1050
=head3 _set_found_trigger
1014
1051
1015
    $self->_set_found_trigger
1052
    $self->_set_found_trigger
(-)a/Koha/Item/Attributes.pm (+149 lines)
Line 0 Link Here
1
package Koha::Item::Attributes;
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 MARC::Record;
20
use MARC::Field;
21
use List::MoreUtils qw( uniq );
22
23
use C4::Biblio;
24
use C4::Charset qw( StripNonXmlChars );
25
26
=head1 NAME
27
28
Koha::Item::Attributes - Class to represent the additional attributes of items.
29
30
Additional attributes are 'more subfields xml'
31
32
=head1 API
33
34
=head2 Class methods
35
36
=cut
37
38
=head3 new_from_marcxml
39
40
    my $attributes = Koha::Item::Attributes->new_from_marcxml( $item->more_subfield_xml );
41
42
Constructor that takes a MARCXML.
43
44
=cut
45
46
# FIXME maybe this needs to care about repeatable but don't from batchMod - To implement later?
47
sub new_from_marcxml {
48
    my ( $class, $more_subfields_xml ) = @_;
49
50
    my $self = {};
51
    if ($more_subfields_xml) {
52
        # FIXME MARC::Record->new_from_xml (vs MARC::Record::new_from_xml) does not return the correctly encoded subfield code (??)
53
        my $marc_more =
54
          MARC::Record::new_from_xml(
55
            C4::Charset::StripNonXmlChars($more_subfields_xml), 'UTF-8' );
56
57
        # use of tag 999 is arbitrary, and doesn't need to match the item tag
58
        # used in the framework
59
        my $field = $marc_more->field('999');
60
        my $more_subfields = [ uniq map { $_->[0] } $field->subfields ];
61
        for my $more_subfield (@$more_subfields) {
62
            my @s = $field->subfield($more_subfield);
63
            $self->{$more_subfield} = join '|', @s;
64
        }
65
    }
66
    return bless $self, $class;
67
}
68
69
=head3 new
70
71
Constructor
72
73
=cut
74
75
# FIXME maybe this needs to care about repeatable but don't from batchMod - To implement later?
76
sub new {
77
    my ( $class, $attributes ) = @_;
78
79
    my $self = $attributes;
80
    return bless $self, $class;
81
}
82
83
=head3 to_marcxml
84
85
    $attributes->to_marcxml;
86
87
    $item->more_subfields_xml( $attributes->to_marcxml );
88
89
Return the MARCXML representation of the attributes.
90
91
=cut
92
93
sub to_marcxml {
94
    my ( $self, $frameworkcode ) = @_;
95
96
    return unless keys %$self;
97
98
    my $tagslib =
99
      C4::Biblio::GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
100
101
    my ( $itemtag, $itemtagsubfield ) =
102
      C4::Biblio::GetMarcFromKohaField("items.itemnumber");
103
    my @subfields;
104
    for my $tagsubfield (
105
        sort {
106
            $tagslib->{$itemtag}->{$a}->{display_order} <=> $tagslib->{$itemtag}->{$b}->{display_order}
107
              || $tagslib->{$itemtag}->{$a}->{subfield} cmp $tagslib->{$itemtag}->{$b}->{subfield}
108
        } keys %$self
109
      )
110
    {
111
        next
112
          if not defined $self->{$tagsubfield}
113
          or $self->{$tagsubfield} eq "";
114
115
        if ( $tagslib->{$itemtag}->{$tagsubfield}->{repeatable} ) {
116
            my @values = split '\|', $self->{$tagsubfield};
117
            push @subfields, ( $tagsubfield => $_ ) for @values;
118
        }
119
        else {
120
            push @subfields, ( $tagsubfield => $self->{$tagsubfield} );
121
        }
122
    }
123
124
    return unless @subfields;
125
126
    my $marc_more = MARC::Record->new();
127
128
    # use of tag 999 is arbitrary, and doesn't need to match the item tag
129
    # used in the framework
130
    $marc_more->append_fields(
131
        MARC::Field->new( '999', ' ', ' ', @subfields ) );
132
    $marc_more->encoding("UTF-8");
133
    return $marc_more->as_xml("USMARC");
134
}
135
136
=head3 to_hashref
137
138
    $attributes->to_hashref;
139
140
Returns the hashref representation of the attributes.
141
142
=cut
143
144
sub to_hashref {
145
    my ($self) = @_;
146
    return { map { $_ => $self->{$_} } keys %$self };
147
}
148
149
1;
(-)a/Koha/Items.pm (+213 lines)
Lines 18-27 package Koha::Items; Link Here
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
use Array::Utils qw( array_minus );
22
use List::MoreUtils qw( uniq );
21
23
24
use C4::Context;
25
use C4::Biblio qw( GetMarcStructure GetMarcFromKohaField );
22
26
23
use Koha::Database;
27
use Koha::Database;
28
use Koha::SearchEngine::Indexer;
24
29
30
use Koha::Item::Attributes;
25
use Koha::Item;
31
use Koha::Item;
26
32
27
use base qw(Koha::Objects);
33
use base qw(Koha::Objects);
Lines 108-113 sub filter_out_lost { Link Here
108
    return $self->search( $params );
114
    return $self->search( $params );
109
}
115
}
110
116
117
=head3 batch_update
118
119
    Koha::Items->search->batch_update
120
        {
121
            new_values => {
122
                itemnotes => $new_item_notes,
123
                k         => $k,
124
            },
125
            regex_mod => {
126
                itemnotes_nonpublic => {
127
                    search => 'foo',
128
                    replace => 'bar',
129
                    modifiers => 'gi',
130
                },
131
            },
132
            exclude_from_local_holds_priority => 1|0,
133
            callback => sub {
134
                # increment something here
135
            },
136
        }
137
    );
138
139
Batch update the items.
140
141
Returns ( $report, $self )
142
Report has 2 keys:
143
  * modified_itemnumbers - list of the modified itemnumbers
144
  * modified_fields - number of fields modified
145
146
Parameters:
147
148
=over
149
150
=item new_values
151
152
Allows to set a new value for given fields.
153
The key can be one of the item's column name, or one subfieldcode of a MARC subfields not linked with a Koha field
154
155
=item regex_mod
156
157
Allows to modify existing subfield's values using a regular expression
158
159
=item exclude_from_local_holds_priority
160
161
Set the passed boolean value to items.exclude_from_local_holds_priority
162
163
=item callback
164
165
Callback function to call after an item has been modified
166
167
=back
168
169
=cut
170
171
sub batch_update {
172
    my ( $self, $params ) = @_;
173
174
    my $regex_mod = $params->{regex_mod} || {};
175
    my $new_values = $params->{new_values} || {};
176
    my $exclude_from_local_holds_priority = $params->{exclude_from_local_holds_priority};
177
    my $callback = $params->{callback};
178
179
    my (@modified_itemnumbers, $modified_fields);
180
    my $i;
181
    while ( my $item = $self->next ) {
182
183
        my $modified_holds_priority = 0;
184
        if ( defined $exclude_from_local_holds_priority ) {
185
            if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
186
                $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
187
                $modified_holds_priority = 1;
188
            }
189
        }
190
191
        my $modified = 0;
192
        my $new_values = {%$new_values};    # Don't modify the original
193
194
        my $old_values = $item->unblessed;
195
        if ( $item->more_subfields_xml ) {
196
            $old_values = {
197
                %$old_values,
198
                %{$item->additional_attributes->to_hashref},
199
            };
200
        }
201
202
        for my $attr ( keys %$regex_mod ) {
203
            my $old_value = $old_values->{$attr};
204
205
            next unless $old_value;
206
207
            my $value = apply_regex(
208
                {
209
                    %{ $regex_mod->{$attr} },
210
                    value => $old_value,
211
                }
212
            );
213
214
            $new_values->{$attr} = $value;
215
        }
216
217
        for my $attribute ( keys %$new_values ) {
218
            next if $attribute eq 'more_subfields_xml'; # Already counted before
219
220
            my $old = $old_values->{$attribute};
221
            my $new = $new_values->{$attribute};
222
            $modified++
223
              if ( defined $old xor defined $new )
224
              || ( defined $old && defined $new && $new ne $old );
225
        }
226
227
        { # Dealing with more_subfields_xml
228
229
            my $frameworkcode = $item->biblio->frameworkcode;
230
            my $tagslib = C4::Biblio::GetMarcStructure( 1, $frameworkcode, { unsafe => 1 });
231
            my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
232
233
            my @more_subfield_tags = map {
234
                (
235
                         ref($_)
236
                      && %$_
237
                      && !$_->{kohafield}    # Get subfields that are not mapped
238
                  )
239
                  ? $_->{tagsubfield}
240
                  : ()
241
            } values %{ $tagslib->{$itemtag} };
242
243
            my $more_subfields_xml = Koha::Item::Attributes->new(
244
                {
245
                    map {
246
                        exists $new_values->{$_} ? ( $_ => $new_values->{$_} )
247
                          : exists $old_values->{$_}
248
                          ? ( $_ => $old_values->{$_} )
249
                          : ()
250
                    } @more_subfield_tags
251
                }
252
            )->to_marcxml($frameworkcode);
253
254
            $new_values->{more_subfields_xml} = $more_subfields_xml;
255
256
            delete $new_values->{$_} for @more_subfield_tags; # Clean the hash
257
258
        }
259
260
        if ( $modified ) {
261
            my $itemlost_pre = $item->itemlost;
262
            $item->set($new_values)->store({skip_record_index => 1});
263
264
            LostItem(
265
                $item->itemnumber, 'batchmod', undef,
266
                { skip_record_index => 1 }
267
            ) if $item->itemlost
268
                  and not $itemlost_pre;
269
270
            push @modified_itemnumbers, $item->itemnumber if $modified || $modified_holds_priority;
271
            $modified_fields += $modified + $modified_holds_priority;
272
        }
273
274
        if ( $callback ) {
275
            $callback->(++$i);
276
        }
277
    }
278
279
    if (@modified_itemnumbers) {
280
        my @biblionumbers = uniq(
281
            Koha::Items->search( { itemnumber => \@modified_itemnumbers } )
282
                       ->get_column('biblionumber'));
283
284
        my $indexer = Koha::SearchEngine::Indexer->new(
285
            { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
286
        $indexer->index_records( \@biblionumbers, 'specialUpdate',
287
            "biblioserver", undef )
288
          if @biblionumbers;
289
    }
290
291
    return ( { modified_itemnumbers => \@modified_itemnumbers, modified_fields => $modified_fields }, $self );
292
}
293
294
sub apply_regex { # FIXME Should be moved outside of Koha::Items
295
    my ($params) = @_;
296
    my $search   = $params->{search};
297
    my $replace  = $params->{replace};
298
    my $modifiers = $params->{modifiers} || q{};
299
    my $value = $params->{value};
300
301
    my @available_modifiers = qw( i g );
302
    my $retained_modifiers  = q||;
303
    for my $modifier ( split //, $modifiers ) {
304
        $retained_modifiers .= $modifier
305
          if grep { /$modifier/ } @available_modifiers;
306
    }
307
    if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
308
        $value =~ s/$search/$replace/ig;
309
    }
310
    elsif ( $retained_modifiers eq 'i' ) {
311
        $value =~ s/$search/$replace/i;
312
    }
313
    elsif ( $retained_modifiers eq 'g' ) {
314
        $value =~ s/$search/$replace/g;
315
    }
316
    else {
317
        $value =~ s/$search/$replace/;
318
    }
319
320
    return $value;
321
}
322
323
111
=head2 Internal methods
324
=head2 Internal methods
112
325
113
=head3 _type
326
=head3 _type
(-)a/Koha/UI/Form/Builder/Item.pm (-23 / +29 lines)
Lines 82-87 sub generate_subfield_form { Link Here
82
    my $prefill_with_default_values = $params->{prefill_with_default_values};
82
    my $prefill_with_default_values = $params->{prefill_with_default_values};
83
    my $branch_limit = $params->{branch_limit};
83
    my $branch_limit = $params->{branch_limit};
84
    my $default_branches_empty = $params->{default_branches_empty};
84
    my $default_branches_empty = $params->{default_branches_empty};
85
    my $readonly = $params->{readonly};
85
86
86
    my $item         = $self->{item};
87
    my $item         = $self->{item};
87
    my $subfield     = $tagslib->{$tag}{$subfieldtag};
88
    my $subfield     = $tagslib->{$tag}{$subfieldtag};
Lines 379-406 sub generate_subfield_form { Link Here
379
        };
380
        };
380
    }
381
    }
381
382
382
    # Getting list of subfields to keep when restricted editing is enabled
383
    # If we're on restricted editing, and our field is not in the list of subfields to allow,
383
    # FIXME Improve the following block, no need to do it for every subfields
384
    # then it is read-only
384
    my $subfieldsToAllowForRestrictedEditing =
385
    $subfield_data{marc_value}->{readonly} = $readonly;
385
      C4::Context->preference('SubfieldsToAllowForRestrictedEditing');
386
    my $allowAllSubfields = (
387
        not defined $subfieldsToAllowForRestrictedEditing
388
          or $subfieldsToAllowForRestrictedEditing eq q||
389
    ) ? 1 : 0;
390
    my @subfieldsToAllow = split( / /, $subfieldsToAllowForRestrictedEditing );
391
392
# If we're on restricted editing, and our field is not in the list of subfields to allow,
393
# then it is read-only
394
    $subfield_data{marc_value}->{readonly} =
395
      (       not $allowAllSubfields
396
          and $restricted_edition
397
          and !grep { $tag . '$' . $subfieldtag eq $_ } @subfieldsToAllow )
398
      ? 1
399
      : 0;
400
386
401
    return \%subfield_data;
387
    return \%subfield_data;
402
}
388
}
403
389
390
=head3 edit_form
391
404
    my $subfields =
392
    my $subfields =
405
      Koha::UI::Form::Builder::Item->new(
393
      Koha::UI::Form::Builder::Item->new(
406
        { biblionumber => $biblionumber, item => $current_item } )->edit_form(
394
        { biblionumber => $biblionumber, item => $current_item } )->edit_form(
Lines 440-448 List of subfields to prefill (value of syspref SubfieldsToUseWhenPrefill) Link Here
440
428
441
=item subfields_to_allow
429
=item subfields_to_allow
442
430
443
List of subfields to allow (value of syspref SubfieldsToAllowForRestrictedBatchmod)
431
List of subfields to allow (value of syspref SubfieldsToAllowForRestrictedBatchmod or SubfieldsToAllowForRestrictedEditing)
432
433
=item ignore_not_allowed_subfields
434
435
If set, the subfields in subfields_to_allow will be ignored (ie. they will not be part of the subfield list.
436
If not set, the subfields in subfields_to_allow will be marked as readonly.
444
437
445
=item subfields_to_ignore
438
=item kohafields_to_ignore
446
439
447
List of subfields to ignore/skip
440
List of subfields to ignore/skip
448
441
Lines 469-475 sub edit_form { Link Here
469
    my $restricted_edition = $params->{restricted_editition};
462
    my $restricted_edition = $params->{restricted_editition};
470
    my $subfields_to_prefill = $params->{subfields_to_prefill} || [];
463
    my $subfields_to_prefill = $params->{subfields_to_prefill} || [];
471
    my $subfields_to_allow = $params->{subfields_to_allow} || [];
464
    my $subfields_to_allow = $params->{subfields_to_allow} || [];
472
    my $subfields_to_ignore= $params->{subfields_to_ignore} || [];
465
    my $ignore_not_allowed_subfields = $params->{ignore_not_allowed_subfields};
466
    my $kohafields_to_ignore = $params->{kohafields_to_ignore} || [];
473
    my $prefill_with_default_values = $params->{prefill_with_default_values};
467
    my $prefill_with_default_values = $params->{prefill_with_default_values};
474
    my $branch_limit = $params->{branch_limit};
468
    my $branch_limit = $params->{branch_limit};
475
    my $default_branches_empty = $params->{default_branches_empty};
469
    my $default_branches_empty = $params->{default_branches_empty};
Lines 494-503 sub edit_form { Link Here
494
488
495
            next if IsMarcStructureInternal($subfield);
489
            next if IsMarcStructureInternal($subfield);
496
            next if $subfield->{tab} ne "10";
490
            next if $subfield->{tab} ne "10";
497
            next if @$subfields_to_allow && !grep { $subfield->{kohafield} eq $_ } @$subfields_to_allow;
498
            next
491
            next
499
              if grep { $subfield->{kohafield} && $subfield->{kohafield} eq $_ }
492
              if grep { $subfield->{kohafield} && $subfield->{kohafield} eq $_ }
500
              @$subfields_to_ignore;
493
              @$kohafields_to_ignore;
494
495
            my $readonly;
496
            if (
497
                @$subfields_to_allow && !grep {
498
                    sprintf( "%s\$%s", $subfield->{tagfield}, $subfield->{tagsubfield} ) eq $_
499
                } @$subfields_to_allow
500
              )
501
            {
502
503
                next if $ignore_not_allowed_subfields;
504
                $readonly = 1 if $restricted_edition;
505
            }
501
506
502
            my @values = ();
507
            my @values = ();
503
508
Lines 545-550 sub edit_form { Link Here
545
                        prefill_with_default_values => $prefill_with_default_values,
550
                        prefill_with_default_values => $prefill_with_default_values,
546
                        branch_limit       => $branch_limit,
551
                        branch_limit       => $branch_limit,
547
                        default_branches_empty => $default_branches_empty,
552
                        default_branches_empty => $default_branches_empty,
553
                        readonly           => $readonly
548
                    }
554
                    }
549
                );
555
                );
550
                push @subfields, $subfield_data;
556
                push @subfields, $subfield_data;
(-)a/Koha/UI/Table/Builder/Items.pm (+147 lines)
Line 0 Link Here
1
package Koha::UI::Table::Builder::Items;
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 List::MoreUtils qw( uniq );
20
use C4::Biblio qw( GetMarcStructure GetMarcFromKohaField IsMarcStructureInternal );
21
use Koha::Items;
22
23
=head1 NAME
24
25
Koha::UI::Table::Builder::Items
26
27
Helper to build a table with a list of items with all their information.
28
29
Items' attributes that are mapped and not mapped will be listed in the table.
30
31
Only attributes that have been defined only once will be displayed (empty string is considered as not defined).
32
33
=head1 API
34
35
=head2 Class methods
36
37
=cut
38
39
=head3 new
40
41
    my $table = Koha::UI::Table::Builder::Items->new( { itemnumbers => \@itemnumbers } );
42
43
Constructor.
44
45
=cut
46
47
sub new {
48
    my ( $class, $params ) = @_;
49
50
    my $self;
51
    $self->{itemnumbers} = $params->{itemnumbers} || [];
52
53
    bless $self, $class;
54
    return $self;
55
}
56
57
=head3 build_table
58
59
    my $items_table = Koha::UI::Table::Builder::Items->new( { itemnumbers => \@itemnumbers } )
60
                                                     ->build_table;
61
62
    my $items   = $items_table->{items};
63
    my $headers = $items_table->{headers};
64
65
Build the headers and rows for the table.
66
67
Use it with:
68
    [% PROCESS items_table_batchmod headers => headers, items => items %]
69
70
=cut
71
72
sub build_table {
73
    my ( $self, $params ) = @_;
74
75
    my $items = Koha::Items->search( { itemnumber => $self->{itemnumbers} } );
76
77
    my @items;
78
    while ( my $item = $items->next ) {
79
        my $item_info = $item->columns_to_str;
80
        $item_info = {
81
            %$item_info,
82
            biblio         => $item->biblio,
83
            safe_to_delete => $item->safe_to_delete,
84
            holds          => $item->biblio->holds->count,
85
            item_holds     => $item->holds->count,
86
            is_checked_out => $item->checkout || 0,
87
        };
88
        push @items, $item_info;
89
    }
90
91
    $self->{headers} = $self->_build_headers( \@items );
92
    $self->{items}   = \@items;
93
    return $self;
94
}
95
96
=head2 Internal methods
97
98
=cut
99
100
=head3 _build_headers
101
102
Build the headers given the items' info.
103
104
=cut
105
106
sub _build_headers {
107
    my ( $self, $items ) = @_;
108
109
    my @witness_attributes = uniq map {
110
        my $item = $_;
111
        map { defined $item->{$_} && $item->{$_} ne "" ? $_ : () } keys %$item
112
    } @$items;
113
114
    my ( $itemtag, $itemsubfield ) =
115
      C4::Biblio::GetMarcFromKohaField("items.itemnumber");
116
    my $tagslib = C4::Biblio::GetMarcStructure(1);
117
    my $subfieldcode_attribute_mappings;
118
    for my $subfield_code ( keys %{ $tagslib->{$itemtag} } ) {
119
120
        my $subfield = $tagslib->{$itemtag}->{$subfield_code};
121
122
        next if IsMarcStructureInternal($subfield);
123
        next unless $subfield->{tab} eq 10;    # Is this really needed?
124
125
        my $attribute;
126
        if ( $subfield->{kohafield} ) {
127
            ( $attribute = $subfield->{kohafield} ) =~ s|^items\.||;
128
        }
129
        else {
130
            $attribute = $subfield_code;       # It's in more_subfields_xml
131
        }
132
        next unless grep { $attribute eq $_ } @witness_attributes;
133
        $subfieldcode_attribute_mappings->{$subfield_code} = $attribute;
134
    }
135
136
    return [
137
        map {
138
            {
139
                header_value  => $tagslib->{$itemtag}->{$_}->{lib},
140
                attribute     => $subfieldcode_attribute_mappings->{$_},
141
                subfield_code => $_,
142
            }
143
        } sort keys %$subfieldcode_attribute_mappings
144
    ];
145
}
146
147
1
(-)a/admin/background_jobs.pl (-8 / +2 lines)
Lines 51-64 if ( $op eq 'view' ) { Link Here
51
        }
51
        }
52
        else {
52
        else {
53
            $template->param( job => $job, );
53
            $template->param( job => $job, );
54
            $template->param(
54
            my $report = $job->additional_report() || {};
55
                lists => scalar Koha::Virtualshelves->search(
55
            $template->param( %$report );
56
                    [
57
                        { category => 1, owner => $loggedinuser },
58
                        { category => 2 }
59
                    ]
60
                )
61
            ) if $job->type eq 'batch_biblio_record_modification';
62
        }
56
        }
63
    } else {
57
    } else {
64
        $op = 'list';
58
        $op = 'list';
(-)a/cataloguing/additem.pl (-3 / +10 lines)
Lines 508-514 my @witness_attributes = uniq map { Link Here
508
    map { defined $item->{$_} && $item->{$_} ne "" ? $_ : () } keys %$item
508
    map { defined $item->{$_} && $item->{$_} ne "" ? $_ : () } keys %$item
509
} @items;
509
} @items;
510
510
511
our ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField("items.itemnumber");
511
our ( $itemtagfield, $itemtagsubfield ) = GetMarcFromKohaField("items.itemnumber");
512
512
513
my $subfieldcode_attribute_mappings;
513
my $subfieldcode_attribute_mappings;
514
for my $subfield_code ( keys %{ $tagslib->{$itemtagfield} } ) {
514
for my $subfield_code ( keys %{ $tagslib->{$itemtagfield} } ) {
Lines 560-571 if ( $nextop eq 'additem' && $prefillitem ) { Link Here
560
    # Setting to 1 element if SubfieldsToUseWhenPrefill is empty to prevent all the subfields to be prefilled
560
    # Setting to 1 element if SubfieldsToUseWhenPrefill is empty to prevent all the subfields to be prefilled
561
    @subfields_to_prefill = split(' ', C4::Context->preference('SubfieldsToUseWhenPrefill')) || ("");
561
    @subfields_to_prefill = split(' ', C4::Context->preference('SubfieldsToUseWhenPrefill')) || ("");
562
}
562
}
563
564
# Getting list of subfields to keep when restricted editing is enabled
565
my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedEditing') : ();
566
563
my $subfields =
567
my $subfields =
564
  Koha::UI::Form::Builder::Item->new(
568
  Koha::UI::Form::Builder::Item->new(
565
    { biblionumber => $biblionumber, item => $current_item } )->edit_form(
569
    { biblionumber => $biblionumber, item => $current_item } )->edit_form(
566
    {
570
    {
567
        branchcode           => $branchcode,
571
        branchcode           => $branchcode,
568
        restricted_editition => $restrictededition,
572
        restricted_editition => $restrictededition,
573
        (
574
            @subfields_to_allow
575
            ? ( subfields_to_allow => \@subfields_to_allow )
576
            : ()
577
        ),
569
        (
578
        (
570
            @subfields_to_prefill
579
            @subfields_to_prefill
571
            ? ( subfields_to_prefill => \@subfields_to_prefill )
580
            ? ( subfields_to_prefill => \@subfields_to_prefill )
Lines 594-601 $template->param( Link Here
594
    subfields        => $subfields,
603
    subfields        => $subfields,
595
    itemnumber       => $itemnumber,
604
    itemnumber       => $itemnumber,
596
    barcode          => $current_item->{barcode},
605
    barcode          => $current_item->{barcode},
597
    itemtagfield     => $itemtagfield,
598
    itemtagsubfield  => $itemtagsubfield,
599
    op      => $nextop,
606
    op      => $nextop,
600
    popup => scalar $input->param('popup') ? 1: 0,
607
    popup => scalar $input->param('popup') ? 1: 0,
601
    C4::Search::enabled_staff_search_views,
608
    C4::Search::enabled_staff_search_views,
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_authority_record_modification.inc (-1 / +1 lines)
Lines 27-33 Link Here
27
            [% END %]
27
            [% END %]
28
            [% SWITCH m.code %]
28
            [% SWITCH m.code %]
29
            [% CASE 'authority_not_modified' %]
29
            [% CASE 'authority_not_modified' %]
30
                Authority record <a href="/cgi-bin/koha/authorities/detail.pl?authid=[% m.authid | uri %]">[% m.authid | html %]</a> has not been modified. An error occurred on modifying it[% IF m.error %] ([% m.error %])[% END %].
30
                Authority record <a href="/cgi-bin/koha/authorities/detail.pl?authid=[% m.authid | uri %]">[% m.authid | html %]</a> has not been modified. An error occurred on modifying it[% IF m.error %] ([% m.error | html %])[% END %].
31
            [% CASE 'authority_modified' %]
31
            [% CASE 'authority_modified' %]
32
                Authority record <a href="/cgi-bin/koha/authorities/detail.pl?authid=[% m.authid | uri %]">[% m.authid | html %]</a> has successfully been modified..
32
                Authority record <a href="/cgi-bin/koha/authorities/detail.pl?authid=[% m.authid | uri %]">[% m.authid | html %]</a> has successfully been modified..
33
            [% END %]
33
            [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_biblio_record_modification.inc (-14 / +16 lines)
Lines 46-64 Link Here
46
[% END %]
46
[% END %]
47
47
48
[% BLOCK js %]
48
[% BLOCK js %]
49
    $("#add_bibs_to_list").change(function(){
49
    <script>
50
        var selected = $("#add_bibs_to_list").find("option:selected");
50
        $("#add_bibs_to_list").change(function(){
51
        if ( selected.attr("class") == "shelf" ){
51
            var selected = $("#add_bibs_to_list").find("option:selected");
52
            var shelfnumber = selected.attr("value");
52
            if ( selected.attr("class") == "shelf" ){
53
            var bibs = new Array();
53
                var shelfnumber = selected.attr("value");
54
            [% FOREACH message IN job.messages %]
54
                var bibs = new Array();
55
                [% IF message.code == 'biblio_modified' %]
55
                [% FOREACH message IN job.messages %]
56
                    bibs.push("biblionumber="+[% message.biblionumber | html %]);
56
                    [% IF message.code == 'biblio_modified' %]
57
                        bibs.push("biblionumber="+[% message.biblionumber | html %]);
58
                    [% END %]
57
                [% END %]
59
                [% END %]
58
            [% END %]
60
                var bibstring = bibs.join("&");
59
            var bibstring = bibs.join("&");
61
                window.open('/cgi-bin/koha/virtualshelves/addbybiblionumber.pl?shelfnumber='+shelfnumber+'&confirm=1&'+bibstring, 'popup', 'width=500,height=500,toolbar=false,scrollbars=yes,resizable=yes');
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');
62
                return false;
61
            return false;
63
            }
62
        }
64
        });
63
    });
65
    </script>
64
[% END %]
66
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_item_record_deletion.inc (+58 lines)
Line 0 Link Here
1
[% BLOCK report %]
2
    [% SET report = job.report %]
3
    [% IF report %]
4
        <div class="dialog message">
5
            [% IF report.deleted_itemnumbers.size %]
6
                <p>[% report.deleted_itemnumbers.size | html %] item(s) deleted.</p>
7
                [% IF report.deleted_biblionumbers.size %]
8
                    <p>[% report.deleted_biblionumbers.size | html %] record(s) deleted.</p>
9
                [% END %]
10
            [% ELSE %]
11
                No items deleted.
12
            [% END %]
13
        </div>
14
15
        [% IF report.not_deleted_itemnumbers.size %]
16
            <div class="dialog error">
17
                [% report.not_deleted_itemnumbers.size | html %] item(s) could not be deleted: [% FOREACH not_deleted_itemnumber IN not_deleted_itemnumbers %][% not_deleted_itemnumber.itemnumber | html %][% END %]
18
            </div>
19
        [% END %]
20
21
        [% IF job.status == 'cancelled' %]
22
            <div class="dialog error">
23
                The job has been cancelled before it finished.
24
                <a href="/cgi-bin/koha/tools/batchMod.pl" title="New batch item modification">New batch item modification</a>
25
            </div>
26
        [% END %]
27
    [% END %]
28
[% END %]
29
30
[% BLOCK detail %]
31
    [% FOR m IN job.messages %]
32
        <div class="dialog message">
33
            [% IF m.type == 'success' %]
34
                <i class="fa fa-check success"></i>
35
            [% ELSIF m.type == 'warning' %]
36
                <i class="fa fa-warning warn"></i>
37
            [% ELSIF m.type == 'error' %]
38
                <i class="fa fa-exclamation error"></i>
39
            [% END %]
40
            [% SWITCH m.code %]
41
                [% CASE 'item_not_deleted' %]
42
                    Item with barcode <a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% m.biblionumber | uri %]&itemnumber=[% m.itemnumber | uri %]">[% m.barcode | html %]</a> cannot be deleted:
43
                    [% SWITCH m.reason %]
44
                        [% CASE "book_on_loan" %]Item is checked out
45
                        [% CASE "not_same_branch" %]Item does not belong to your library
46
                        [% CASE "book_reserved" %]Item has a waiting hold
47
                        [% CASE "linked_analytics" %]Item has linked analytics
48
                        [% CASE "last_item_for_hold" %]Last item for bibliographic record with biblio-level hold on it
49
                        [% CASE %]Unknown reason '[% m.reason | html %]'
50
                    [% END %]
51
                [% CASE %]Unknown message '[% m.code | html %]'
52
            [% END %]
53
        </div>
54
    [% END %]
55
[% END %]
56
57
[% BLOCK js %]
58
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/background_jobs/batch_item_record_modification.inc (+38 lines)
Line 0 Link Here
1
[% BLOCK report %]
2
    [% SET report = job.report %]
3
    [% IF report %]
4
        <div class="dialog message">
5
            [% IF report.modified_itemnumbers.size %]
6
                [% report.modified_itemnumbers.size | html %] item(s) modified (with [% report.modified_fields | html %] field(s) modified).
7
            [% ELSE %]
8
                No items modified.
9
            [% END %]
10
11
            [% IF job.status == 'cancelled' %]The job has been cancelled before it finished.[% END %]
12
            <a href="/cgi-bin/koha/tools/batchMod.pl" title="New batch item modification">New batch item modification</a>
13
        </div>
14
    [% END %]
15
[% END %]
16
17
[% BLOCK detail %]
18
    [% FOR m IN job.messages %]
19
        <div class="dialog message">
20
            [% IF m.type == 'success' %]
21
                <i class="fa fa-check success"></i>
22
            [% ELSIF m.type == 'warning' %]
23
                <i class="fa fa-warning warn"></i>
24
            [% ELSIF m.type == 'error' %]
25
                <i class="fa fa-exclamation error"></i>
26
            [% END %]
27
            [% SWITCH m.code %]
28
            [% CASE %]Unknown message '[% m.code | html %]'
29
            [% END %]
30
        </div>
31
    [% END %]
32
33
    [% PROCESS items_table_batchmod headers => item_header_loop, items => items, display_columns_selection => 1 %]
34
[% END %]
35
36
[% BLOCK js %]
37
    [% Asset.js("js/pages/batchMod.js") | $raw %]
38
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/html_helpers.inc (+130 lines)
Lines 181-183 Link Here
181
        [% END %]
181
        [% END %]
182
    </ol>
182
    </ol>
183
[% END %]
183
[% END %]
184
185
[% BLOCK items_table_batchmod %]
186
187
    [% IF display_columns_selection %][%# Needs js/pages/batchMod.js %]
188
        [% IF checkboxes_edit OR checkboxes_delete %]
189
            <div id="toolbar">
190
                <a id="selectallbutton" href="#"><i class="fa fa-check"></i> Select all</a> | <a id="clearallbutton" href="#"><i class="fa fa-remove"></i> Clear all</a> | <a id="clearonloanbutton" href="#">Clear on loan</a>
191
            </div>
192
        [% END %]
193
194
        <div id="cataloguing_additem_itemlist">
195
196
            <p id="selections">
197
                <strong>Show/hide columns:</strong>
198
                <span class="selected">
199
                    <input type="checkbox" checked="checked" id="showall" />
200
                    <label for="showall">Show all columns</label>
201
                </span>
202
                <span>
203
                    <input type="checkbox" id="hideall" />
204
                    <label for="hideall">Hide all columns</label>
205
                </span>
206
207
                [% FOREACH header IN item_header_loop %]
208
                    <span class="selected">
209
                        <input id="checkheader[% loop.count | html %]" type="checkbox" checked="checked" />
210
                        <label for="checkheader[% loop.count | html %]">[% header.header_value | html %]</label>
211
                    </span>
212
                [% END %]
213
            </p> <!-- /#selections -->
214
        </div>
215
    [% END %]
216
    [% SET date_fields = [ 'dateaccessioned', 'onloan', 'datelastseen', 'datelastborrowed', 'replacementpricedate' ] %]
217
    <table id="itemst">
218
        <thead>
219
            <tr>
220
                [% IF checkboxes_edit OR checkboxes_delete %]
221
                    <th></th>
222
                [% END %]
223
                <th class="anti-the">Title</th>
224
                <th class="holds_count" title="Item holds / Total holds">Holds</th>
225
                [% FOREACH item_header IN headers %]
226
                    [% IF item_header.column_name %]
227
                        <th data-colname="[% item_header.column_name | html %]">
228
                    [% ELSE %]
229
                        <th>
230
                    [% END %]
231
                        [% item_header.header_value | html %]
232
                    </th>
233
                [% END %]
234
            </tr>
235
        </thead>
236
        <tbody>
237
            [% FOREACH item IN items %]
238
                [% SET can_be_edited = ! ( Koha.Preference('IndependentBranches') && ! logged_in_user && item.homebranch != Branches.GetLoggedInBranchcode() ) %]
239
240
                <tr>
241
                    [% IF checkboxes_edit %]
242
                        [% UNLESS can_be_edited%]
243
                            <td class="error">Cannot edit</td>
244
                        [% ELSE %]
245
                            <td>
246
                                <input type="checkbox" name="itemnumber" value="[% item.itemnumber | html %]" id="row[% item.itemnumber | html %]" checked="checked" data-is-onloan="[% item.is_checked_out | html %]" />
247
                            </td>
248
                        [% END %]
249
                    [% ELSIF checkboxes_delete %]
250
                        [% UNLESS can_be_edited %]
251
                            <td class="error">Cannot delete</td>
252
                        [% ELSE %]
253
                            [% IF item.safe_to_delete == 1 %]
254
                                <td><input type="checkbox" name="itemnumber" value="[% item.itemnumber | html %]" id="row[% item.itemnumber | html %]" checked="checked" /></td>
255
                            [% ELSE %]
256
                                [% SWITCH item.safe_to_delete%]
257
                                [% CASE "book_on_loan" %][% SET cannot_delete_reason = t("Item is checked out") %]
258
                                [% CASE "not_same_branch" %][% SET cannot_delete_reason = t("Item does not belong to your library") %]
259
                                [% CASE "book_reserved" %][% SET cannot_delete_reason = t("Item has a waiting hold") %]
260
                                [% CASE "linked_analytics" %][% SET cannot_delete_reason = t("Item has linked analytics") %]
261
                                [% CASE "last_item_for_hold" %][% SET cannot_delete_reason = t("Last item for bibliographic record with biblio-level hold on it") %]
262
                                [% CASE %][% SET cannot_delete_reason = t("Unknown reason") _ '(' _ item.safe_to_delete _ ')' %]
263
                                [% END %]
264
265
                                <td><input type="checkbox" name="itemnumber" value="[% item.itemnumber | html %]" id="row[% item.itemnumber | html %]" disabled="disabled" title="[% cannot_delete_reason | html %]"/></td>
266
                            [% END %]
267
268
                        [% END %]
269
                    [% END %]
270
                    <td>
271
                        <label for="row[% item.itemnumber | html %]">
272
                            <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% item.biblionumber | uri %]">
273
                                [% item.biblio.title | html %]
274
                            </a>
275
                            [% IF ( item.biblio.author ) %], by [% item.biblio.author | html %][% END %]
276
                        </label>
277
                    </td>
278
                    <td class="holds_count">
279
                        [% IF item.holds %]
280
                            [% IF item.item_holds %]
281
                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item.biblionumber | uri %]" title="Holds on this item: [% item.item_holds | html %] / Total holds on this record: [% item.holds | html -%]" >
282
                            [% ELSE %]
283
                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item.biblionumber | uri %]" title="No holds on this item / Total holds on this record: [% item.holds | html -%]" >
284
                            [% END %]
285
                        [% ELSE %]
286
                            [% IF item.holds %]
287
                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item.biblionumber | uri %]" title="Holds on this record: [% item.holds | html -%]" >
288
                            [% ELSE %]
289
                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item.biblionumber | uri %]" title="No holds on this record" >
290
                            [% END %]
291
                        [% END # /IF item.holds %]
292
                        [% IF item.holds %]
293
                            [% item.item_holds | html %]/[% item.holds | html %]
294
                        [% ELSE %]
295
                            [% item.holds | html %]
296
                        [% END %]
297
                        </a>
298
                    </td>
299
                    [% FOREACH header IN headers %]
300
                        [% SET attribute = header.attribute %]
301
                        [% IF header.attribute AND date_fields.grep('^' _ attribute _ '$').size %]
302
                            <td data-order="[% item.$attribute | html %]">[% item.$attribute | $KohaDates %]</td>
303
                        [% ELSE %]
304
                            <td>[% item.$attribute | html %]</td>
305
                        [% END %]
306
                    [% END %]
307
308
                </tr>
309
            [% END # /FOREACH items %]
310
        </tbody>
311
    </table> <!-- /#itemst -->
312
313
[% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/background_jobs.tt (-13 / +18 lines)
Lines 1-4 Link Here
1
[% USE raw %]
1
[% USE raw %]
2
[% USE KohaDates %]
2
[% USE Asset %]
3
[% USE Asset %]
3
[% SET footerjs = 1 %]
4
[% SET footerjs = 1 %]
4
[% INCLUDE 'doc-head-open.inc' %]
5
[% INCLUDE 'doc-head-open.inc' %]
Lines 64-70 Administration &rsaquo; Koha Link Here
64
            <li><span class="label">Job ID: </span>[% job.id | html %]</li>
65
            <li><span class="label">Job ID: </span>[% job.id | html %]</li>
65
            <li><label for="job_status">Status: </label>[% job.status | html %]</li>
66
            <li><label for="job_status">Status: </label>[% job.status | html %]</li>
66
            <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li>
67
            <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li>
67
            <li><label for="job_type">Type: </label>[% job.type | html %]</li>
68
            <li><label for="job_type">Type: </label>[% PROCESS display_job_type job_type => job.type %]</li>
68
            <li><label for="job_enqueued_on">Queued: </label>[% job.enqueued_on | html %]</li>
69
            <li><label for="job_enqueued_on">Queued: </label>[% job.enqueued_on | html %]</li>
69
            <li><label for="job_started_on">Started: </label>[% job.started_on | html %]</li>
70
            <li><label for="job_started_on">Started: </label>[% job.started_on | html %]</li>
70
            <li><label for="job_ended_on">Ended: </label>[% job.ended_on | html %]</li>
71
            <li><label for="job_ended_on">Ended: </label>[% job.ended_on | html %]</li>
Lines 106-120 Administration &rsaquo; Koha Link Here
106
                    <td>[% job.id | html %]</td>
107
                    <td>[% job.id | html %]</td>
107
                    <td>[% job.status | html %]</td>
108
                    <td>[% job.status | html %]</td>
108
                    <td>[% job.progress || 0 | html %] / [% job.size | html %]</td>
109
                    <td>[% job.progress || 0 | html %] / [% job.size | html %]</td>
109
                    <td>
110
                    <td>[% PROCESS display_job_type job_type => job.type %]</td>
110
                        [% SWITCH job.type %]
111
                        [% CASE 'batch_biblio_record_modification' %]Batch bibliographic record modification
112
                        [% CASE 'batch_biblio_record_deletion' %]Batch bibliographic record record deletion
113
                        [% CASE 'batch_authority_record_modification' %]Batch authority record modification
114
                        [% CASE 'batch_authority_record_deletion' %]Batch authority record deletion
115
                        [% CASE %][% job.type | html %]
116
                        [% END %]
117
                    </td>
118
                    <td>[% job.enqueued_on | html %]</td>
111
                    <td>[% job.enqueued_on | html %]</td>
119
                    <td>[% job.started_on| html %]</td>
112
                    <td>[% job.started_on| html %]</td>
120
                    <td>[% job.ended_on| html %]</td>
113
                    <td>[% job.ended_on| html %]</td>
Lines 159-168 Administration &rsaquo; Koha Link Here
159
                "sPaginationType": "full_numbers"
152
                "sPaginationType": "full_numbers"
160
            }));
153
            }));
161
154
162
            [% IF op == 'view' %]
163
                [% PROCESS 'js' %]
164
            [% END %]
165
        });
155
        });
166
    </script>
156
    </script>
157
    [% IF op == 'view' %]
158
        [% PROCESS 'js' %]
159
    [% END %]
167
[% END %]
160
[% END %]
161
[% BLOCK display_job_type %]
162
    [% SWITCH job_type %]
163
    [% CASE 'batch_biblio_record_modification' %]Batch bibliographic record modification
164
    [% CASE 'batch_biblio_record_deletion' %]Batch bibliographic record record deletion
165
    [% CASE 'batch_authority_record_modification' %]Batch authority record modification
166
    [% CASE 'batch_authority_record_deletion' %]Batch authority record deletion
167
    [% CASE 'batch_item_record_modification' %]Batch item record modification
168
    [% CASE 'batch_item_record_deletion' %]Batch item record deletion
169
    [% CASE %]Unknown job type '[% job_type | html %]'
170
    [% END %]
171
[% END %]
172
168
[% INCLUDE 'intranet-bottom.inc' %]
173
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt (-2 lines)
Lines 182-189 Link Here
182
    </fieldset>
182
    </fieldset>
183
183
184
    [% ELSE %]
184
    [% ELSE %]
185
    <input type="hidden" name="tag" value="[% itemtagfield | html %]" />
186
    <input type="hidden" name="subfield" value="[% itemtagsubfield | html %]" />
187
    [% IF op != 'add_item' %]
185
    [% IF op != 'add_item' %]
188
        <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />
186
        <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />
189
    [% END %]
187
    [% END %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batchMod-del.tt (-171 / +35 lines)
Lines 1-4 Link Here
1
[% USE raw %]
1
[% USE raw %]
2
[% USE KohaDates %]
2
[% USE Asset %]
3
[% USE Asset %]
3
[% SET footerjs = 1 %]
4
[% SET footerjs = 1 %]
4
[% PROCESS 'i18n.inc' %]
5
[% PROCESS 'i18n.inc' %]
Lines 29-39 Link Here
29
30
30
<div class="main container-fluid">
31
<div class="main container-fluid">
31
32
32
                [% IF ( show ) %]<h1>Batch item deletion</h1>[% ELSE %]<h1>Batch item deletion results</h1>[% END %]
33
    <h1>Batch item deletion</h1>
33
        [% IF ( barcode_not_unique ) %]<div class="dialog alert"><strong>Error saving item</strong>: Barcode must be unique.</div>[% END %]
34
34
        [% IF ( no_next_barcode ) %]<div class="dialog alert"><strong>Error saving items</strong>: Unable to automatically determine values for barcodes. No item has been inserted.</div>[% END %]
35
    [% FOREACH message IN messages %]
35
        [% IF ( book_on_loan ) %]<div class="dialog alert"><strong>Cannot delete</strong>: item is checked out.</div>[% END %]
36
      [% IF message.type == 'success' %]
36
        [% IF ( book_reserved ) %]<div class="dialogalert"><strong>Cannot delete</strong>: item has a waiting hold.</div>[% END %]
37
        <div class="dialog message">
38
      [% ELSIF message.type == 'warning' %]
39
        <div class="dialog alert">
40
      [% ELSIF message.type == 'error' %]
41
        <div class="dialog alert" style="margin:auto;">
42
      [% END %]
43
      [% IF message.code == 'cannot_enqueue_job' %]
44
          Cannot enqueue this job.
45
      [% END %]
46
      [% IF message.error %]
47
        (The error was: [% message.error | html %], see the Koha log file for more information).
48
      [% END %]
49
      </div>
50
    [% END %]
51
37
52
38
    [% UNLESS ( action ) %]
53
    [% UNLESS ( action ) %]
39
54
Lines 93-186 Link Here
93
     <input type="hidden" name="biblionumber" id="biblionumber" value="[% biblionumber | html %]" />
108
     <input type="hidden" name="biblionumber" id="biblionumber" value="[% biblionumber | html %]" />
94
     <input type="hidden" name="op" value="[% op | html %]" />
109
     <input type="hidden" name="op" value="[% op | html %]" />
95
     <input type="hidden" name="searchid" value="[% searchid | html %]" />
110
     <input type="hidden" name="searchid" value="[% searchid | html %]" />
96
     <input type="hidden" name="uploadedfileid" id="uploadedfileid" value="" />
97
     <input type="hidden" name="completedJobID" id="completedJobID" value="" />
98
     <input type="hidden" name="src" id="src" value="[% src | html %]" />
111
     <input type="hidden" name="src" id="src" value="[% src | html %]" />
99
     [% IF biblionumber %]
112
     [% IF biblionumber %]
100
        <input type="hidden" name="biblionumber" id="biblionumber" value="[% biblionumber | html %]" />
113
        <input type="hidden" name="biblionumber" id="biblionumber" value="[% biblionumber | html %]" />
101
     [% END %]
114
     [% END %]
102
115
103
[% IF ( item_loop ) %]
116
[% IF items.size %]
104
    [% IF ( show ) %]<div id="toolbar"><a id="selectallbutton" href="#"><i class="fa fa-check"></i> Select all</a> | <a id="clearallbutton" href="#"><i class="fa fa-remove"></i> Clear all</a></div>[% END %]
117
    [% PROCESS items_table_batchmod headers => item_header_loop, items => items, checkboxes_delete => 1, display_columns_selection => 1 %]
105
    <div id="cataloguing_additem_itemlist">
106
107
    <p id="selections"><strong>Show/hide columns:</strong> <span class="selected"><input type="checkbox" checked="checked" id="showall"/><label for="showall">Show all columns</label></span> <span><input type="checkbox" id="hideall"/><label for="hideall">Hide all columns</label></span>
108
        [% FOREACH item_header_loo IN item_header_loop %]
109
        <span class="selected"><input id="checkheader[% loop.count | html %]" type="checkbox" checked="checked" /> <label for="checkheader[% loop.count | html %]">[% item_header_loo.header_value | html %]</label> </span>
110
        [% END %]
111
    </p>
112
113
        <table id="itemst">
114
            <thead>
115
            <tr>
116
                [% IF ( show ) %]<th>&nbsp;</th>[% END %]
117
                <th class="anti-the">Title</th>
118
                <th class="holds_count" title="Item holds / Total holds">Holds</th>
119
                [% FOREACH item_header_loo IN item_header_loop %]
120
                <th> [% item_header_loo.header_value | html %] </th>
121
                [% END %]
122
            </tr>
123
            </thead>
124
            <tbody>
125
            [% FOREACH item_loo IN item_loop %]
126
              <tr>
127
                [% IF show %]
128
                  [% IF item_loo.nomod %]
129
                    <td class="error">Cannot delete</td>
130
                [% ELSE %]
131
                    [% IF item_loo.safe_to_delete == 1 %]
132
                        <td><input type="checkbox" name="itemnumber" value="[% item_loo.itemnumber | html %]" id="row[% item_loo.itemnumber | html %]" checked="checked" /></td>
133
                    [% ELSE %]
134
                        [% SWITCH item_loo.safe_to_delete%]
135
                        [% CASE "book_on_loan" %][% SET cannot_delete_reason = t("Item is checked out") %]
136
                        [% CASE "not_same_branch" %][% SET cannot_delete_reason = t("Item does not belong to your library") %]
137
                        [% CASE "book_reserved" %][% SET cannot_delete_reason = t("Item has a waiting hold") %]
138
                        [% CASE "linked_analytics" %][% SET cannot_delete_reason = t("Item has linked analytics") %]
139
                        [% CASE "last_item_for_hold" %][% SET cannot_delete_reason = t("Last item for bibliographic record with biblio-level hold on it") %]
140
                        [% CASE %][% SET cannot_delete_reason = t("Unknown reason") _ '(' _ item_loo.safe_to_delete _ ')' %]
141
                        [% END %]
142
143
                        <td><input type="checkbox" name="itemnumber" value="[% item_loo.itemnumber | html %]" id="row[% item_loo.itemnumber | html %]" disabled="disabled" title="[% cannot_delete_reason | html %]"/></td>
144
                    [% END %]
145
                  [% END %]
146
                [% ELSE %]
147
                  <td>&nbsp;</td>
148
                [% END %]
149
                <td>
150
                    <label for="row[% item_loo.itemnumber | html %]">
151
                        <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% item_loo.biblionumber | uri %]">
152
                            [% item_loo.title | html %]
153
                        </a>
154
                        [% IF ( item_loo.author ) %], by [% item_loo.author | html %][% END %]
155
                    </label>
156
                </td>
157
                <td class="holds_count">
158
                    [% IF item_loo.holds %]
159
                        [% IF item_loo.item_holds %]
160
                            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="Holds on this item: [% item_loo.item_holds | html %] / Total holds on this record: [% item_loo.holds | html -%]" >
161
                        [% ELSE %]
162
                            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="No holds on this item / Total holds on this record: [% item_loo.holds | html -%]" >
163
                        [% END %]
164
                    [% ELSE %]
165
                        [% IF item_loo.holds %]
166
                            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="Holds on this record: [% item_loo.holds | html -%]" >
167
                        [% ELSE %]
168
                            <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="No holds on this record" >
169
                        [% END %]
170
                    [% END %]
171
                                [% IF item_loo.holds %]
172
                                    [% item_loo.item_holds | html %]/[% item_loo.holds | html %]
173
                                [% ELSE %]
174
                                    [% item_loo.holds | html %]
175
                                [% END %]
176
                            </a>
177
                </td>
178
            [% FOREACH item_valu IN item_loo.item_value %] <td>[% item_valu.field | html %]</td>
179
                    [% END %] </tr>
180
            [% END %]
181
            </tbody>
182
        </table>
183
    </div>
184
[% END %]
118
[% END %]
185
119
186
[% IF ( simple_items_display ) %]
120
[% IF ( simple_items_display ) %]
Lines 203-209 Link Here
203
    [% END %]
137
    [% END %]
204
[% END %]
138
[% END %]
205
139
206
[% IF ( itemresults ) %]
140
    [% IF ( itemresults ) %]
207
        <div id="cataloguing_additem_newitem">
141
        <div id="cataloguing_additem_newitem">
208
         <input type="hidden" name="op" value="[% op | html %]" />
142
         <input type="hidden" name="op" value="[% op | html %]" />
209
         <p>This will delete [% IF ( too_many_items_display ) %]all the[% ELSE %]the selected[% END %] items.</p>
143
         <p>This will delete [% IF ( too_many_items_display ) %]all the[% ELSE %]the selected[% END %] items.</p>
Lines 226-312 Link Here
226
    </form>
160
    </form>
227
    [% END %]
161
    [% END %]
228
162
229
[% IF ( action ) %]
163
230
    [% IF deletion_failed %]
164
    [% IF op == 'enqueued' %]
231
        <div class="dialog alert">
232
            At least one item blocked the deletion. The operation rolled back and nothing happened!
233
        </div>
234
    [% ELSE %]
235
        <div class="dialog message">
165
        <div class="dialog message">
236
            <p>[% deleted_items | html %] item(s) deleted.</p>
166
          <p>The job has been enqueued! It will be processed as soon as possible.</p>
237
            [% IF delete_records %] <p>[% deleted_records | html %] record(s) deleted.</p> [% END %]
167
          <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>
168
          | <a href="/cgi-bin/koha/tools/batchMod.pl?del=1" title="New batch item deletion">New batch item deletion</a></p>
169
        </div>
170
171
        <fieldset class="action">
238
            [% IF src == 'CATALOGUING' # from catalogue/detail.pl > Delete items in a batch%]
172
            [% IF src == 'CATALOGUING' # from catalogue/detail.pl > Delete items in a batch%]
239
                [% IF biblio_deleted %]
173
                [% IF searchid %]
240
                    [% IF searchid %]
174
                    <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]&searchid=[% searchid | uri %]">Return to the record</a>
241
                        <div id="previous_search_link"></div>
242
                    [% END %]
243
                    <a href="/cgi-bin/koha/cataloguing/addbooks.pl">Return to the cataloging module</a>
244
                [% ELSE %]
175
                [% ELSE %]
245
                    [% IF searchid %]
176
                    <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]">Return to the record</a>
246
                        <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]&searchid=[% searchid | uri %]">Return to the record</a>
247
                    [% ELSE %]
248
                        <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]">Return to the record</a>
249
                    [% END %]
250
                [% END %]
177
                [% END %]
251
            [% ELSIF src %]
178
            [% ELSIF src %]
252
                <a href="[% src | url %]">Return to where you were</a>
179
                <a href="[% src | url %]">Return to where you were</a>
253
            [% ELSE %]
180
            [% ELSE %]
254
                <a href="/cgi-bin/koha/tools/batchMod.pl?del=1">Return to batch item deletion</a>
181
                <a href="/cgi-bin/koha/tools/batchMod.pl?del=1">Return to batch item deletion</a>
255
            [% END %]
182
            [% END %]
256
        </div>
183
        </fieldset>
257
    [% END %]
258
    [% IF ( not_deleted_items ) %]
259
    <div style="width:55%;margin:auto;">
260
        <p>[% not_deleted_items | html %] item(s) could not be deleted: [% FOREACH not_deleted_itemnumber IN not_deleted_itemnumbers %][% not_deleted_itemnumber.itemnumber | html %][% END %]</p>
261
    [% IF ( not_deleted_loop ) %]
262
    <table id="itemst">
263
        <thead>
264
            <tr>
265
            <th>Itemnumber</th>
266
            <th>Barcode</th>
267
            <th>Reason</th>
268
            </tr>
269
        </thead>
270
        <tbody>
271
            [% FOREACH not_deleted_loo IN not_deleted_loop %]
272
            <tr>
273
                <td>[% not_deleted_loo.itemnumber | html %]</td>
274
                <td>[% IF ( CAN_user_editcatalogue_edit_items ) %]<a href="/cgi-bin/koha/cataloguing/additem.pl?op=edititem&amp;biblionumber=[% not_deleted_loo.biblionumber | uri %]&amp;itemnumber=[% not_deleted_loo.itemnumber | uri %]">[% not_deleted_loo.barcode | html %]</a>[% ELSE %][% not_deleted_loo.barcode | html %][% END %]</td>
275
                <td>
276
                    [% SWITCH not_deleted_loo.reason %]
277
                    [% CASE "book_on_loan" %][% SET cannot_delete_reason = t("Item is checked out") %]
278
                    [% CASE "not_same_branch" %][% SET cannot_delete_reason = t("Item does not belong to your library") %]
279
                    [% CASE "book_reserved" %][% SET cannot_delete_reason = t("Item has a waiting hold") %]
280
                    [% CASE "linked_analytics" %][% SET cannot_delete_reason = t("Item has linked analytics") %]
281
                    [% CASE "last_item_for_hold" %][% SET cannot_delete_reason = t("Last item for bibliographic record with biblio-level hold on it") %]
282
                    [% CASE %][% SET cannot_delete_reason = t("Unknown reason") _ '(' _ can_be_deleted _ ')' %]
283
                    [% END %]
284
                    [% cannot_delete_reason | html %]
285
                </td>
286
            </tr>
287
            [% END %]
288
        </tbody>
289
        </table>
290
    [% END %]
291
    </div>
292
    [% END %]
184
    [% END %]
293
185
294
    <p>
295
        [% IF src == 'CATALOGUING' # from catalogue/detail.pl > Delete items in a batch%]
296
            [% IF biblio_deleted %]
297
                <a class="btn btn-default" href="/cgi-bin/koha/cataloguing/addbooks.pl"><i class="fa fa-check-square-o"></i> Return to the cataloging module</a>
298
            [% ELSIF searchid %]
299
               <a class="btn btn-default" href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]&searchid=[% searchid | uri %]"><i class="fa fa-check-square-o"></i> Return to the record</a>
300
            [% ELSE %]
301
               <a class="btn btn-default" href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]"><i class="fa fa-check-square-o"></i> Return to the record</a>
302
            [% END %]
303
        [% ELSIF src %]
304
           <a class="btn btn-default" href="[% src | url %]"><i class="fa fa-check-square-o"></i> Return to where you were</a>
305
        [% ELSE %]
306
           <a class="btn btn-default" href="/cgi-bin/koha/tools/batchMod.pl?del=1"><i class="fa fa-check-square-o"></i> Return to batch item deletion</a>
307
        [% END %]
308
    </p>
309
[% END %]
310
    </div>
186
    </div>
311
187
312
[% MACRO jsinclude BLOCK %]
188
[% MACRO jsinclude BLOCK %]
Lines 315-332 Link Here
315
    [% Asset.js("js/pages/batchMod.js") | $raw %]
191
    [% Asset.js("js/pages/batchMod.js") | $raw %]
316
    [% Asset.js("js/browser.js") | $raw %]
192
    [% Asset.js("js/browser.js") | $raw %]
317
    <script>
193
    <script>
318
        // Prepare array of all column headers, incrementing each index by
319
        // two to accommodate control and title columns
320
        var allColumns = new Array([% FOREACH item_header_loo IN item_header_loop %]'[% loop.count | html %]'[% UNLESS ( loop.last ) %],[% END %][% END %]);
321
        for( x=0; x<allColumns.length; x++ ){
322
          allColumns[x] = Number(allColumns[x]) + 2;
323
        }
324
        $(document).ready(function(){
325
            $("#mainformsubmit").on("click",function(){
326
                return submitBackgroundJob(this.form);
327
            });
328
        });
329
330
        [% IF searchid %]
194
        [% IF searchid %]
331
            browser = KOHA.browser('[% searchid | html %]');
195
            browser = KOHA.browser('[% searchid | html %]');
332
            browser.show_back_link();
196
            browser.show_back_link();
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/batchMod-edit.tt (-116 / +42 lines)
Lines 1-6 Link Here
1
[% USE raw %]
1
[% USE raw %]
2
[% USE Asset %]
2
[% USE Asset %]
3
[% USE Koha %]
3
[% USE Koha %]
4
[% USE KohaDates %]
4
[% SET footerjs = 1 %]
5
[% SET footerjs = 1 %]
5
[% INCLUDE 'doc-head-open.inc' %]
6
[% INCLUDE 'doc-head-open.inc' %]
6
<title>Batch item modification &rsaquo; Tools &rsaquo; Koha</title>
7
<title>Batch item modification &rsaquo; Tools &rsaquo; Koha</title>
Lines 31-61 Link Here
31
32
32
    <div class="main container-fluid">
33
    <div class="main container-fluid">
33
34
34
        [% IF ( show ) %]
35
        <h1>Batch item modification</h1>
35
            <h1>Batch item modification</h1>
36
        [% IF op == 'enqueued' %]
36
        [% ELSE %]
37
            <h1>Batch item modification results</h1>
38
            <div class="dialog message">
37
            <div class="dialog message">
39
                [% IF (modified_items) %]
38
              <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).
39
              <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 %]
40
              | <a href="/cgi-bin/koha/tools/batchMod.pl" title="New batch item modification">New batch item modification</a></p>
42
                    No items modified.
41
            </div>
43
                [% 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 %]
47
                            <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]&searchid=[% searchid | uri %]">Return to the record</a>
46
                        <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]&searchid=[% searchid | uri %]">Return to the record</a>
48
                        [% ELSE %]
49
                            <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]">Return to the record</a>
50
                        [% END %]
51
                    [% ELSIF src %]
52
                        <a href="[% src | url %]">Return to where you were</a>
53
                    [% ELSE %]
47
                    [% ELSE %]
54
                        <a href="/cgi-bin/koha/tools/batchMod.pl">Return to batch item modification</a>
48
                        <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% biblionumber | uri %]">Return to the record</a>
55
                    [% END %]
49
                    [% END %]
56
                </fieldset>
50
                [% ELSIF src %]
57
            </div> <!-- /.dialog.message -->
51
                    <a href="[% src | url %]">Return to where you were</a>
58
        [% END # /IF show %]
52
                [% ELSE %]
53
                    <a href="/cgi-bin/koha/tools/batchMod.pl">Return to batch item modification</a>
54
                [% END %]
55
            </fieldset>
56
        [% END %]
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 123-221 Link Here
123
                <input type="hidden" name="biblionumber" id="biblionumber" value="[% biblionumber | html %]" />
139
                <input type="hidden" name="biblionumber" id="biblionumber" value="[% biblionumber | html %]" />
124
            [% END %]
140
            [% END %]
125
141
126
            [% IF ( item_loop ) %]
142
            [% IF items.size %]
127
                [% IF show %]
143
                    [% PROCESS items_table_batchmod headers => item_header_loop, items => items, checkboxes_edit => 1, display_columns_selection => 1 %]
128
                    <div id="toolbar">
144
            [% END %]
129
                        <a id="selectallbutton" href="#"><i class="fa fa-check"></i> Select all</a> | <a id="clearallbutton" href="#"><i class="fa fa-remove"></i> Clear all</a> | <a id="clearonloanbutton" href="#">Clear on loan</a>
130
                    </div>
131
                [% END %]
132
133
                <div id="cataloguing_additem_itemlist">
134
135
                    <p id="selections">
136
                        <strong>Show/hide columns:</strong>
137
                        <span class="selected">
138
                            <input type="checkbox" checked="checked" id="showall" />
139
                            <label for="showall">Show all columns</label>
140
                        </span>
141
                        <span>
142
                            <input type="checkbox" id="hideall" />
143
                            <label for="hideall">Hide all columns</label>
144
                        </span>
145
146
                        [% FOREACH item_header_loo IN item_header_loop %]
147
                            <span class="selected">
148
                                <input id="checkheader[% loop.count | html %]" type="checkbox" checked="checked" />
149
                                <label for="checkheader[% loop.count | html %]">[% item_header_loo.header_value | html %]</label>
150
                            </span>
151
                        [% END %]
152
                    </p> <!-- /#selections -->
153
154
                    <table id="itemst">
155
                        <thead>
156
                            <tr>
157
                                <th>&nbsp;</th>
158
                                <th class="anti-the">Title</th>
159
                                <th class="holds_count" title="Item holds / Total holds">Holds</th>
160
                                [% FOREACH item_header_loo IN item_header_loop %]
161
                                    <th> [% item_header_loo.header_value | html %] </th>
162
                                [% END %]
163
                            </tr>
164
                        </thead>
165
                        <tbody>
166
                            [% FOREACH item_loo IN item_loop %]
167
                                <tr>
168
                                    [% IF show %]
169
                                        [% IF item_loo.nomod %]
170
                                            <td class="error">Cannot edit</td>
171
                                        [% ELSE %]
172
                                            <td>
173
                                                <input type="checkbox" name="itemnumber" value="[% item_loo.itemnumber | html %]" id="row[% item_loo.itemnumber | html %]" checked="checked" data-is-onloan="[% item_loo.onloan | html %]" />
174
                                            </td>
175
                                        [% END %]
176
                                    [% ELSE %]
177
                                        <td>&nbsp;</td>
178
                                    [% END %]
179
                                    <td>
180
                                        <label for="row[% item_loo.itemnumber | html %]">
181
                                            <a href="/cgi-bin/koha/catalogue/detail.pl?biblionumber=[% item_loo.biblionumber | uri %]">
182
                                                [% item_loo.title | html %]
183
                                            </a>
184
                                            [% IF ( item_loo.author ) %], by [% item_loo.author | html %][% END %]
185
                                        </label>
186
                                    </td>
187
                                    <td class="holds_count">
188
                                        [% IF item_loo.holds %]
189
                                            [% IF item_loo.item_holds %]
190
                                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="Holds on this item: [% item_loo.item_holds | html %] / Total holds on this record: [% item_loo.holds | html -%]" >
191
                                            [% ELSE %]
192
                                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="No holds on this item / Total holds on this record: [% item_loo.holds | html -%]" >
193
                                            [% END %]
194
                                        [% ELSE %]
195
                                            [% IF item_loo.holds %]
196
                                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="Holds on this record: [% item_loo.holds | html -%]" >
197
                                            [% ELSE %]
198
                                                <a href="/cgi-bin/koha/reserve/request.pl?biblionumber=[% item_loo.biblionumber | uri %]" title="No holds on this record" >
199
                                            [% END %]
200
                                        [% END # /IF item_loo.holds %]
201
                                        [% IF item_loo.holds %]
202
                                            [% item_loo.item_holds | html %]/[% item_loo.holds | html %]
203
                                        [% ELSE %]
204
                                            [% item_loo.holds | html %]
205
                                        [% END %]
206
                                        </a>
207
                                    </td>
208
                                    [% FOREACH item_valu IN item_loo.item_value %]
209
                                        <td>
210
                                            [% item_valu.field | html %]
211
                                        </td>
212
                                    [% END %]
213
                                </tr>
214
                            [% END # /FOREACH item_loo %]
215
                        </tbody>
216
                    </table> <!-- /#itemst -->
217
                </div> <!-- /#cataloguing_additem_itemlist -->
218
            [% END # /IF item_loop %]
219
145
220
            [% IF ( simple_items_display || job_completed ) %]
146
            [% IF ( simple_items_display || job_completed ) %]
221
                [% IF ( too_many_items_display ) %]
147
                [% IF ( too_many_items_display ) %]
Lines 290-296 Link Here
290
                        <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>
216
                        <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>
291
                    [% END %]
217
                    [% END %]
292
                </fieldset> <!-- /.action -->
218
                </fieldset> <!-- /.action -->
293
            [% END #/IF show %]
219
            [% END %]
294
        </form>
220
        </form>
295
221
296
    [% MACRO jsinclude BLOCK %]
222
    [% MACRO jsinclude BLOCK %]
(-)a/koha-tmpl/intranet-tmpl/prog/js/pages/batchMod.js (-5 / +20 lines)
Lines 3-16 Link Here
3
var date = new Date();
3
var date = new Date();
4
date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
4
date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000));
5
5
6
function guess_nb_cols() {
7
    // This is a bit ugly, we are trying to know if there are checkboxes in the first column of the table
8
    if ( $("#itemst tr:first th:first").html() == "" ) {
9
        // First header is empty, it's a checkbox
10
        return 3;
11
    } else {
12
        // First header is not empty, there are no checkboxes
13
        return 2;
14
    }
15
}
16
6
function hideColumns() {
17
function hideColumns() {
7
    var valCookie = Cookies.get("showColumns");
18
    var valCookie = Cookies.get("showColumns");
19
    var nb_cols = guess_nb_cols();
8
    if (valCookie) {
20
    if (valCookie) {
9
        valCookie = valCookie.split("/");
21
        valCookie = valCookie.split("/");
10
        $("#showall").prop("checked", false).parent().removeClass("selected");
22
        $("#showall").prop("checked", false).parent().removeClass("selected");
11
        for ( var i = 0; i < valCookie.length; i++ ) {
23
        for ( var i = 0; i < valCookie.length; i++ ) {
12
            if (valCookie[i] !== '') {
24
            if (valCookie[i] !== '') {
13
                var index = valCookie[i] - 3;
25
                var index = valCookie[i] - nb_cols;
14
                $("#itemst td:nth-child(" + valCookie[i] + "),#itemst th:nth-child(" + valCookie[i] + ")").toggle();
26
                $("#itemst td:nth-child(" + valCookie[i] + "),#itemst th:nth-child(" + valCookie[i] + ")").toggle();
15
                $("#checkheader" + index).prop("checked", false).parent().removeClass("selected");
27
                $("#checkheader" + index).prop("checked", false).parent().removeClass("selected");
16
            }
28
            }
Lines 20-29 function hideColumns() { Link Here
20
32
21
function hideColumn(num) {
33
function hideColumn(num) {
22
    $("#hideall,#showall").prop("checked", false).parent().removeClass("selected");
34
    $("#hideall,#showall").prop("checked", false).parent().removeClass("selected");
35
    var nb_cols = guess_nb_cols();
23
    var valCookie = Cookies.get("showColumns");
36
    var valCookie = Cookies.get("showColumns");
24
    // set the index of the table column to hide
37
    // set the index of the table column to hide
25
    $("#" + num).parent().removeClass("selected");
38
    $("#" + num).parent().removeClass("selected");
26
    var hide = Number(num.replace("checkheader", "")) + 3;
39
    var hide = Number(num.replace("checkheader", "")) + nb_cols;
27
    // hide header and cells matching the index
40
    // hide header and cells matching the index
28
    $("#itemst td:nth-child(" + hide + "),#itemst th:nth-child(" + hide + ")").toggle();
41
    $("#itemst td:nth-child(" + hide + "),#itemst th:nth-child(" + hide + ")").toggle();
29
    // set or modify cookie with the hidden column's index
42
    // set or modify cookie with the hidden column's index
Lines 59-65 function showColumn(num) { Link Here
59
    $("#" + num).parent().addClass("selected");
72
    $("#" + num).parent().addClass("selected");
60
    var valCookie = Cookies.get("showColumns");
73
    var valCookie = Cookies.get("showColumns");
61
    // set the index of the table column to hide
74
    // set the index of the table column to hide
62
    var show = Number(num.replace("checkheader", "")) + 3;
75
    var nb_cols = guess_nb_cols();
76
    var show = Number(num.replace("checkheader", "")) + nb_cols;
63
    // hide header and cells matching the index
77
    // hide header and cells matching the index
64
    $("#itemst td:nth-child(" + show + "),#itemst th:nth-child(" + show + ")").toggle();
78
    $("#itemst td:nth-child(" + show + "),#itemst th:nth-child(" + show + ")").toggle();
65
    // set or modify cookie with the hidden column's index
79
    // set or modify cookie with the hidden column's index
Lines 80-90 function showColumn(num) { Link Here
80
}
94
}
81
95
82
function showAllColumns() {
96
function showAllColumns() {
97
    var nb_cols = guess_nb_cols();
83
    $("#selections input:checkbox").each(function () {
98
    $("#selections input:checkbox").each(function () {
84
        $(this).prop("checked", true);
99
        $(this).prop("checked", true);
85
    });
100
    });
86
    $("#selections span").addClass("selected");
101
    $("#selections span").addClass("selected");
87
    $("#itemst td:nth-child(3),#itemst tr th:nth-child(3)").nextAll().show();
102
    $("#itemst td:nth-child("+nb_cols+"),#itemst tr th:nth-child("+nb_cols+")").nextAll().show();
88
    $.removeCookie("showColumns", { path: '/' });
103
    $.removeCookie("showColumns", { path: '/' });
89
    $("#hideall").prop("checked", false).parent().removeClass("selected");
104
    $("#hideall").prop("checked", false).parent().removeClass("selected");
90
}
105
}
Lines 94-100 function hideAllColumns() { Link Here
94
        $(this).prop("checked", false);
109
        $(this).prop("checked", false);
95
    });
110
    });
96
    $("#selections span").removeClass("selected");
111
    $("#selections span").removeClass("selected");
97
    $("#itemst td:nth-child(3),#itemst th:nth-child(3)").nextAll().hide();
112
    $("#itemst td:nth-child("+nb_cols+"),#itemst tr th:nth-child("+nb_cols+")").nextAll().hide();
98
    $("#hideall").prop("checked", true).parent().addClass("selected");
113
    $("#hideall").prop("checked", true).parent().addClass("selected");
99
    var cookieString = allColumns.join("/");
114
    var cookieString = allColumns.join("/");
100
    Cookies.set("showColumns", cookieString, { expires: date, path: '/' });
115
    Cookies.set("showColumns", cookieString, { expires: date, path: '/' });
(-)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/t/db_dependent/Koha/Item.t (-5 / +17 lines)
Lines 91-101 subtest 'has_pending_hold() tests' => sub { Link Here
91
subtest "as_marc_field() tests" => sub {
91
subtest "as_marc_field() tests" => sub {
92
92
93
    my $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
93
    my $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
94
    my ( $itemtag, $itemtagsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
94
95
95
    my @schema_columns = $schema->resultset('Item')->result_source->columns;
96
    my @schema_columns = $schema->resultset('Item')->result_source->columns;
96
    my @mapped_columns = grep { exists $mss->{'items.'.$_} } @schema_columns;
97
    my @mapped_columns = grep { exists $mss->{'items.'.$_} } @schema_columns;
97
98
98
    plan tests => 2 * (scalar @mapped_columns + 1) + 2;
99
    plan tests => 2 * (scalar @mapped_columns + 1) + 3;
99
100
100
    $schema->storage->txn_begin;
101
    $schema->storage->txn_begin;
101
102
Lines 108-114 subtest "as_marc_field() tests" => sub { Link Here
108
109
109
    is(
110
    is(
110
        $marc_field->tag,
111
        $marc_field->tag,
111
        $mss->{'items.itemnumber'}[0]->{tagfield},
112
        $itemtag,
112
        'Generated field set the right tag number'
113
        'Generated field set the right tag number'
113
    );
114
    );
114
115
Lines 123-129 subtest "as_marc_field() tests" => sub { Link Here
123
124
124
    is(
125
    is(
125
        $marc_field->tag,
126
        $marc_field->tag,
126
        $mss->{'items.itemnumber'}[0]->{tagfield},
127
        $itemtag,
127
        'Generated field set the right tag number'
128
        'Generated field set the right tag number'
128
    );
129
    );
129
130
Lines 136-160 subtest "as_marc_field() tests" => sub { Link Here
136
    my $unmapped_subfield = Koha::MarcSubfieldStructure->new(
137
    my $unmapped_subfield = Koha::MarcSubfieldStructure->new(
137
        {
138
        {
138
            frameworkcode => '',
139
            frameworkcode => '',
139
            tagfield      => $mss->{'items.itemnumber'}[0]->{tagfield},
140
            tagfield      => $itemtag,
140
            tagsubfield   => 'X',
141
            tagsubfield   => 'X',
141
        }
142
        }
142
    )->store;
143
    )->store;
143
144
144
    $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
145
    my @unlinked_subfields;
145
    my @unlinked_subfields;
146
    push @unlinked_subfields, X => 'Something weird';
146
    push @unlinked_subfields, X => 'Something weird';
147
    $item->more_subfields_xml( C4::Items::_get_unlinked_subfields_xml( \@unlinked_subfields ) )->store;
147
    $item->more_subfields_xml( C4::Items::_get_unlinked_subfields_xml( \@unlinked_subfields ) )->store;
148
148
149
    Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
150
    Koha::MarcSubfieldStructures->search(
151
        { frameworkcode => '', tagfield => $itemtag } )
152
      ->update( { display_order => \['FLOOR( 1 + RAND( ) * 10 )'] } );
153
149
    $marc_field = $item->as_marc_field;
154
    $marc_field = $item->as_marc_field;
150
155
156
    my $tagslib = C4::Biblio::GetMarcStructure(1, '');
151
    my @subfields = $marc_field->subfields;
157
    my @subfields = $marc_field->subfields;
152
    my $result = all { defined $_->[1] } @subfields;
158
    my $result = all { defined $_->[1] } @subfields;
153
    ok( $result, 'There are no undef subfields' );
159
    ok( $result, 'There are no undef subfields' );
160
    my @ordered_subfields = sort {
161
            $tagslib->{$itemtag}->{ $a->[0] }->{display_order}
162
        <=> $tagslib->{$itemtag}->{ $b->[0] }->{display_order}
163
    } @subfields;
164
    is_deeply(\@subfields, \@ordered_subfields);
154
165
155
    is( scalar $marc_field->subfield('X'), 'Something weird', 'more_subfield_xml is considered' );
166
    is( scalar $marc_field->subfield('X'), 'Something weird', 'more_subfield_xml is considered' );
156
167
157
    $schema->storage->txn_rollback;
168
    $schema->storage->txn_rollback;
169
    Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
158
};
170
};
159
171
160
subtest 'pickup_locations' => sub {
172
subtest 'pickup_locations' => sub {
(-)a/t/db_dependent/Koha/Item/Attributes.t (+151 lines)
Line 0 Link Here
1
#!/usr/bin/perl
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 Test::More tests=> 10;
20
use utf8;
21
22
use Koha::Database;
23
use Koha::Caches;
24
25
use C4::Biblio;
26
use Koha::Item::Attributes;
27
use Koha::MarcSubfieldStructures;
28
29
use t::lib::TestBuilder;
30
31
my $schema = Koha::Database->new->schema;
32
$schema->storage->txn_begin;
33
34
my $builder = t::lib::TestBuilder->new;
35
my $biblio = $builder->build_sample_biblio({ frameworkcode => '' });
36
my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
37
38
my $cache = Koha::Caches->get_instance;
39
$cache->clear_from_cache("MarcStructure-0-");
40
$cache->clear_from_cache("MarcStructure-1-");
41
$cache->clear_from_cache("default_value_for_mod_marc-");
42
$cache->clear_from_cache("MarcSubfieldStructure-");
43
44
# 952 $x $é $y are not linked with a kohafield
45
# $952$x $é repeatable
46
# $952$y is not repeatable
47
setup_mss();
48
49
$item->more_subfields_xml(undef)->store; # Shouldn't be needed, but we want to make sure
50
my $attributes = $item->additional_attributes;
51
is( ref($attributes), 'Koha::Item::Attributes' );
52
is( $attributes->to_marcxml, undef );
53
is_deeply($attributes->to_hashref, {});
54
55
my $some_marc_xml = q{<?xml version="1.0" encoding="UTF-8"?>
56
<collection
57
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
58
  xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
59
  xmlns="http://www.loc.gov/MARC21/slim">
60
61
<record>
62
  <leader>         a              </leader>
63
  <datafield tag="999" ind1=" " ind2=" ">
64
    <subfield code="x">value for x 1</subfield>
65
    <subfield code="x">value for x 2</subfield>
66
    <subfield code="y">value for y</subfield>
67
    <subfield code="é">value for é 1</subfield>
68
    <subfield code="é">value for é 2</subfield>
69
    <subfield code="z">value for z 1|value for z 2</subfield>
70
  </datafield>
71
</record>
72
73
</collection>};
74
75
$item->more_subfields_xml($some_marc_xml)->store;
76
77
$attributes = $item->additional_attributes;
78
is( ref($attributes), 'Koha::Item::Attributes' );
79
is( $attributes->{'x'}, "value for x 1|value for x 2");
80
is( $attributes->{'y'}, "value for y");
81
is( $attributes->{'é'}, "value for é 1|value for é 2");
82
is( $attributes->{'z'}, "value for z 1|value for z 2");
83
84
is( $attributes->to_marcxml, $some_marc_xml );
85
is_deeply(
86
    $attributes->to_hashref,
87
    {
88
        'x' => "value for x 1|value for x 2",
89
        'y' => "value for y",
90
        'é' => "value for é 1|value for é 2",
91
        'z' => "value for z 1|value for z 2",
92
    }
93
);
94
95
Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
96
97
sub setup_mss {
98
99
    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
100
101
    Koha::MarcSubfieldStructures->search(
102
        {
103
            frameworkcode => '',
104
            tagfield => $itemtag,
105
            tagsubfield => 'é',
106
        }
107
    )->delete;    # In case it exist already
108
109
    Koha::MarcSubfieldStructure->new(
110
        {
111
            frameworkcode => '',
112
            tagfield      => $itemtag,
113
            tagsubfield   => 'é',
114
            kohafield     => undef,
115
            repeatable    => 1,
116
            tab           => 10,
117
        }
118
    )->store;
119
120
    Koha::MarcSubfieldStructures->search(
121
        {
122
            frameworkcode => '',
123
            tagfield      => $itemtag,
124
            tagsubfield   => [ 'x', 'y' ]
125
        }
126
    )->update( { kohafield => undef } );
127
128
    Koha::MarcSubfieldStructures->search(
129
        {
130
            frameworkcode => '',
131
            tagfield => $itemtag,
132
            tagsubfield => [ 'x', 'é' ],
133
        }
134
    )->update( { repeatable => 1 } );
135
136
    Koha::MarcSubfieldStructures->search(
137
        {
138
            frameworkcode => '',
139
            tagfield => $itemtag,
140
            tagsubfield => ['y'],
141
        }
142
    )->update( { repeatable => 0 } );
143
144
    my $i = 0;
145
    for my $sf ( qw( x y é z ) ) {
146
        Koha::MarcSubfieldStructures->search(
147
            { frameworkcode => '', tagfield => $itemtag, tagsubfield => $sf } )
148
          ->update( { display_order => $i++ } );
149
    }
150
151
}
(-)a/t/db_dependent/Koha/Items/BatchUpdate.t (+383 lines)
Line 0 Link Here
1
#!/usr/bin/perl
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 Test::More tests=> 7;
20
use utf8;
21
22
use Koha::Database;
23
use Koha::Caches;
24
25
use C4::Biblio;
26
use Koha::Item::Attributes;
27
use Koha::MarcSubfieldStructures;
28
29
use t::lib::TestBuilder;
30
31
my $schema = Koha::Database->new->schema;
32
$schema->storage->txn_begin;
33
34
my $builder = t::lib::TestBuilder->new;
35
36
Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
37
38
# 952 $x $é $y are not linked with a kohafield
39
# $952$x $é repeatable
40
# $952$t is not repeatable
41
# 952$z is linked with items.itemnotes and is repeatable
42
# 952$t is linked with items.copynumber and is not repeatable
43
setup_mss();
44
45
my $biblio = $builder->build_sample_biblio({ frameworkcode => '' });
46
my $item = $builder->build_sample_item({ biblionumber => $biblio->biblionumber });
47
48
my $items = Koha::Items->search({itemnumber => $item->itemnumber});
49
50
subtest 'MARC subfield linked with kohafield' => sub {
51
    plan tests => 9;
52
53
    $items->batch_update({
54
            new_values => {itemnotes => 'new note'}
55
        });
56
    $items->reset;
57
58
    $item = $item->get_from_storage;
59
    is( $item->itemnotes, 'new note' );
60
    is( $item->as_marc_field->subfield('t'), undef );
61
62
    is( $items->batch_update({
63
            new_values => {itemnotes=> 'another note'}
64
        })->count, 1, 'Can be chained');
65
    $items->reset;
66
67
    $items->batch_update({new_values => {itemnotes=> undef }})->reset;
68
    $item = $item->get_from_storage;
69
    is( $item->itemnotes, undef, "blank" );
70
    is( $item->as_marc_field->subfield('t'), undef, '' );
71
72
    $items->batch_update({new_values => {itemnotes=> 'new note', copynumber => 'new copynumber'}})->reset;
73
    $item = $item->get_from_storage;
74
    is( $item->itemnotes, 'new note', "multi" );
75
    is( $item->as_marc_field->subfield('z'), 'new note', '' );
76
    is( $item->copynumber, 'new copynumber', "multi" );
77
    is( $item->as_marc_field->subfield('t'), 'new copynumber', '' );
78
};
79
80
subtest 'More marc subfields (no linked)' => sub {
81
    plan tests => 1;
82
83
    $items->batch_update({new_values => {x => 'new xxx' }})->reset;
84
    is( $item->get_from_storage->as_marc_field->subfield('x'), 'new xxx' );
85
};
86
87
subtest 'repeatable' => sub {
88
    plan tests => 2;
89
90
    subtest 'linked' => sub {
91
        plan tests => 4;
92
93
        $items->batch_update({new_values => {itemnotes => 'new zzz 1|new zzz 2' }})->reset;
94
        is( $item->get_from_storage->itemnotes, 'new zzz 1|new zzz 2');
95
        is_deeply( [$item->get_from_storage->as_marc_field->subfield('z')], ['new zzz 1', 'new zzz 2'], 'z is repeatable' );
96
97
        $items->batch_update({new_values => {copynumber => 'new ttt 1|new ttt 2' }})->reset;
98
        is( $item->get_from_storage->copynumber, 'new ttt 1|new ttt 2');
99
        is_deeply( [$item->get_from_storage->as_marc_field->subfield('t')], ['new ttt 1|new ttt 2'], 't is not repeatable' );
100
    };
101
102
    subtest 'not linked' => sub {
103
        plan tests => 2;
104
105
        $items->batch_update({new_values => {x => 'new xxx 1|new xxx 2' }})->reset;
106
        is_deeply( [$item->get_from_storage->as_marc_field->subfield('x')], ['new xxx 1', 'new xxx 2'], 'i is repeatable' );
107
108
        $items->batch_update({new_values => {y => 'new yyy 1|new yyy 2' }})->reset;
109
        is_deeply( [$item->get_from_storage->as_marc_field->subfield('y')], ['new yyy 1|new yyy 2'], 'y is not repeatable' );
110
    };
111
};
112
113
subtest 'blank' => sub {
114
    plan tests => 5;
115
116
    $items->batch_update(
117
        {
118
            new_values => {
119
                itemnotes  => 'new notes 1|new notes 2',
120
                copynumber => 'new cn 1|new cn 2',
121
                x          => 'new xxx 1|new xxx 2',
122
                y          => 'new yyy 1|new yyy 2',
123
124
            }
125
        }
126
    )->reset;
127
128
    $items->batch_update(
129
        {
130
            new_values => {
131
                itemnotes  => undef,
132
                copynumber => undef,
133
                x          => undef,
134
            }
135
        }
136
    )->reset;
137
138
    $item = $item->get_from_storage;
139
    is( $item->itemnotes,                    undef );
140
    is( $item->copynumber,                   undef );
141
    is( $item->as_marc_field->subfield('x'), undef );
142
    is_deeply( [ $item->as_marc_field->subfield('y') ],
143
        ['new yyy 1|new yyy 2'] );
144
145
    $items->batch_update(
146
        {
147
            new_values => {
148
                y => undef,
149
            }
150
        }
151
    )->reset;
152
153
    is( $item->get_from_storage->more_subfields_xml, undef );
154
155
};
156
157
subtest 'regex' => sub {
158
    plan tests => 6;
159
160
    $items->batch_update(
161
        {
162
            new_values => {
163
                itemnotes  => 'new notes 1|new notes 2',
164
                copynumber => 'new cn 1|new cn 2',
165
                x          => 'new xxx 1|new xxx 2',
166
                y          => 'new yyy 1|new yyy 2',
167
168
            }
169
        }
170
    )->reset;
171
172
    my $re = {
173
        search    => 'new',
174
        replace   => 'awesome',
175
        modifiers => '',
176
    };
177
    $items->batch_update(
178
        {
179
            regex_mod =>
180
              { itemnotes => $re, copynumber => $re, x => $re, y => $re }
181
        }
182
    )->reset;
183
    $item = $item->get_from_storage;
184
    is( $item->itemnotes, 'awesome notes 1|new notes 2' );
185
    is_deeply(
186
        [ $item->as_marc_field->subfield('z') ],
187
        [ 'awesome notes 1', 'new notes 2' ],
188
        'z is repeatable'
189
    );
190
191
    is( $item->copynumber, 'awesome cn 1|new cn 2' );
192
    is_deeply( [ $item->as_marc_field->subfield('t') ],
193
        ['awesome cn 1|new cn 2'], 't is not repeatable' );
194
195
    is_deeply(
196
        [ $item->as_marc_field->subfield('x') ],
197
        [ 'awesome xxx 1', 'new xxx 2' ],
198
        'i is repeatable'
199
    );
200
201
    is_deeply(
202
        [ $item->as_marc_field->subfield('y') ],
203
        ['awesome yyy 1|new yyy 2'],
204
        'y is not repeatable'
205
    );
206
};
207
208
subtest 'encoding' => sub {
209
    plan tests => 1;
210
211
    $items->batch_update({
212
            new_values => { 'é' => 'new note é'}
213
        });
214
    $items->reset;
215
216
    $item = $item->get_from_storage;
217
    is( $item->as_marc_field->subfield('é'), 'new note é', );
218
};
219
220
subtest 'report' => sub {
221
    plan tests => 5;
222
223
    my $item_1 =
224
      $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
225
    my $item_2 =
226
      $builder->build_sample_item( { biblionumber => $biblio->biblionumber } );
227
228
    my $items = Koha::Items->search(
229
        { itemnumber => [ $item_1->itemnumber, $item_2->itemnumber ] } );
230
231
    my ($report) = $items->batch_update(
232
        {
233
            new_values => { itemnotes => 'new note' }
234
        }
235
    );
236
    $items->reset;
237
    is_deeply(
238
        $report,
239
        {
240
            modified_itemnumbers =>
241
              [ $item_1->itemnumber, $item_2->itemnumber ],
242
            modified_fields => 2
243
        }
244
    );
245
246
    ($report) = $items->batch_update(
247
        {
248
            new_values => { itemnotes => 'new note', copynumber => 'new cn' }
249
        }
250
    );
251
    $items->reset;
252
253
    is_deeply(
254
        $report,
255
        {
256
            modified_itemnumbers =>
257
              [ $item_1->itemnumber, $item_2->itemnumber ],
258
            modified_fields => 2
259
        }
260
    );
261
262
    $item_1->get_from_storage->update( { itemnotes => 'not new note' } );
263
    ($report) = $items->batch_update(
264
        {
265
            new_values => { itemnotes => 'new note', copynumber => 'new cn' }
266
        }
267
    );
268
    $items->reset;
269
270
    is_deeply(
271
        $report,
272
        {
273
            modified_itemnumbers => [ $item_1->itemnumber ],
274
            modified_fields      => 1
275
        }
276
    );
277
278
    ($report) = $items->batch_update(
279
        {
280
            new_values => { x => 'new xxx', y => 'new yyy' }
281
        }
282
    );
283
    $items->reset;
284
285
    is_deeply(
286
        $report,
287
        {
288
            modified_itemnumbers =>
289
              [ $item_1->itemnumber, $item_2->itemnumber ],
290
            modified_fields => 4
291
        }
292
    );
293
294
    my $re = {
295
        search    => 'new',
296
        replace   => 'awesome',
297
        modifiers => '',
298
    };
299
300
    $item_2->get_from_storage->update( { itemnotes => 'awesome note' } );
301
    ($report) = $items->batch_update(
302
        {
303
            regex_mod =>
304
              { itemnotes => $re, copynumber => $re, x => $re, y => $re }
305
        }
306
    );
307
    $items->reset;
308
309
    is_deeply(
310
        $report,
311
        {
312
            modified_itemnumbers =>
313
              [ $item_1->itemnumber, $item_2->itemnumber ],
314
            modified_fields => 7
315
        }
316
    );
317
318
};
319
320
Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
321
322
sub setup_mss {
323
324
    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
325
326
    Koha::MarcSubfieldStructures->search(
327
        {
328
            frameworkcode => '',
329
            tagfield => $itemtag,
330
            tagsubfield => 'é',
331
        }
332
    )->delete;    # In case it exist already
333
334
    Koha::MarcSubfieldStructure->new(
335
        {
336
            frameworkcode => '',
337
            tagfield      => $itemtag,
338
            tagsubfield   => 'é',
339
            kohafield     => undef,
340
            repeatable    => 1,
341
            tab           => 10,
342
        }
343
    )->store;
344
345
    Koha::MarcSubfieldStructures->search(
346
        {
347
            frameworkcode => '',
348
            tagfield      => $itemtag,
349
            tagsubfield   => [ 'x', 'y' ]
350
        }
351
    )->update( { kohafield => undef } );
352
353
    Koha::MarcSubfieldStructures->search(
354
        {
355
            frameworkcode => '',
356
            tagfield => $itemtag,
357
            tagsubfield => [ 'x', 'é' ],
358
        }
359
    )->update( { repeatable => 1 } );
360
361
    Koha::MarcSubfieldStructures->search(
362
        {
363
            frameworkcode => '',
364
            tagfield => $itemtag,
365
            tagsubfield => ['t'],
366
        }
367
    )->update( { repeatable => 0 } );
368
369
    Koha::MarcSubfieldStructures->search(
370
        {
371
            frameworkcode => '',
372
            tagfield => $itemtag,
373
            tagsubfield => ['z'],
374
        }
375
    )->update( { kohafield => 'items.itemnotes', repeatable => 1 } );
376
377
    Koha::MarcSubfieldStructures->search(
378
        {
379
            frameworkcode => '',
380
            tagfield => $itemtag,
381
        }
382
    )->update( { display_order => \['FLOOR( 1 + RAND( ) * 10 )'] } );
383
}
(-)a/t/db_dependent/Koha/UI/Form/Builder/Item.t (+329 lines)
Line 0 Link Here
1
#!/usr/bin/perl
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 Test::More tests => 6;
20
use utf8;
21
22
use List::MoreUtils qw( uniq );
23
24
use Koha::Libraries;
25
use Koha::MarcSubfieldStructures;
26
use Koha::UI::Form::Builder::Item;
27
use t::lib::TestBuilder;
28
29
my $schema = Koha::Database->new->schema;
30
$schema->storage->txn_begin;
31
32
my $builder = t::lib::TestBuilder->new;
33
34
my $cache = Koha::Caches->get_instance();
35
$cache->clear_from_cache("MarcStructure-0-");
36
$cache->clear_from_cache("MarcStructure-1-");
37
$cache->clear_from_cache("default_value_for_mod_marc-");
38
$cache->clear_from_cache("MarcSubfieldStructure-");
39
40
# 952 $x $é are not linked with a kohafield
41
# $952$x $é repeatable
42
# $952$t is not repeatable
43
# 952$z is linked with items.itemnotes and is repeatable
44
# 952$t is linked with items.copynumber and is not repeatable
45
setup_mss();
46
47
subtest 'authorised values' => sub {
48
    #plan tests => 1;
49
50
    my $biblio = $builder->build_sample_biblio({ value => {frameworkcode => ''}});
51
    my $subfields =
52
      Koha::UI::Form::Builder::Item->new(
53
        { biblionumber => $biblio->biblionumber } )->edit_form;
54
55
    my @display_orders = uniq map { $_->{display_order} } @$subfields;
56
    is_deeply( \@display_orders, [sort {$a <=> $b} @display_orders], 'subfields are sorted by display order' );
57
58
    subtest 'normal AV' => sub {
59
        plan tests => 2;
60
        my ($subfield) =
61
          grep { $_->{kohafield} eq 'items.notforloan' } @$subfields;
62
        my $avs = Koha::AuthorisedValues->search( { category => 'NOT_LOAN' } );
63
64
        is_deeply(
65
            $subfield->{marc_value}->{values},
66
            [
67
                "",
68
                map    { $_->authorised_value }
69
                  sort { $a->lib cmp $b->lib }
70
                  $avs->as_list
71
            ],
72
            'AVs are sorted by lib and en empty option is created first'
73
        );
74
        is_deeply(
75
            $subfield->{marc_value}->{labels},
76
            {
77
                map    { $_->authorised_value => $_->lib }
78
                  sort { $a->lib cmp $b->lib }
79
                  $avs->as_list
80
            }
81
        );
82
    };
83
84
    subtest 'cn_source' => sub {
85
        plan tests => 2;
86
        my ( $subfield ) = grep { $_->{kohafield} eq 'items.cn_source' } @$subfields;
87
        is_deeply( $subfield->{marc_value}->{values}, [ '', 'ddc', 'lcc' ] );
88
        is_deeply(
89
            $subfield->{marc_value}->{labels},
90
            {
91
                ddc => "Dewey Decimal Classification",
92
                lcc => "Library of Congress Classification",
93
            }
94
        );
95
    };
96
    subtest 'branches' => sub {
97
        plan tests => 2;
98
        my ( $subfield ) = grep { $_->{kohafield} eq 'items.homebranch' } @$subfields;
99
        my $libraries = Koha::Libraries->search;
100
        is_deeply(
101
            $subfield->{marc_value}->{values},
102
            [ $libraries->get_column('branchcode') ]
103
        );
104
        is_deeply(
105
            $subfield->{marc_value}->{labels},
106
            { map { $_->branchcode => $_->branchname } $libraries->as_list }
107
        );
108
    };
109
110
    subtest 'itemtypes' => sub {
111
        plan tests => 2;
112
        my ($subfield) = grep { $_->{kohafield} eq 'items.itype' } @$subfields;
113
        my $itemtypes = Koha::ItemTypes->search;
114
115
        is_deeply(
116
            $subfield->{marc_value}->{values},
117
            [
118
                "",
119
                map    { $_->itemtype }
120
                  # We need to sort using uc or perl won't be case insensitive
121
                  sort { uc($a->translated_description) cmp uc($b->translated_description) }
122
                  $itemtypes->as_list
123
            ],
124
            "Item types should be sorted by description and an empty entries should be shown"
125
        );
126
        is_deeply( $subfield->{marc_value}->{labels},
127
            { map { $_->itemtype => $_->description } $itemtypes->as_list },
128
            'Labels should be correctly displayed'
129
        );
130
    };
131
};
132
133
subtest 'prefill_with_default_values' => sub {
134
    plan tests => 2;
135
136
    my $biblio = $builder->build_sample_biblio({ value => {frameworkcode => ''}});
137
    my $subfields =
138
      Koha::UI::Form::Builder::Item->new(
139
        { biblionumber => $biblio->biblionumber } )->edit_form;
140
141
142
    my ($subfield) = grep { $_->{subfield} eq 'é' } @$subfields;
143
    is( $subfield->{marc_value}->{value}, '', 'no default value if prefill_with_default_values not passed' );
144
145
    $subfields =
146
      Koha::UI::Form::Builder::Item->new(
147
        { biblionumber => $biblio->biblionumber } )->edit_form({ prefill_with_default_values => 1 });
148
149
150
    ($subfield) = grep { $_->{subfield} eq 'é' } @$subfields;
151
    is( $subfield->{marc_value}->{value}, 'ééé', 'default value should be set if prefill_with_default_values passed');
152
153
154
};
155
156
subtest 'branchcode' => sub {
157
    plan tests => 2;
158
159
    my $biblio = $builder->build_sample_biblio({ value => {frameworkcode => ''}});
160
    my $library = $builder->build_object({ class => 'Koha::Libraries' });
161
    my $subfields =
162
      Koha::UI::Form::Builder::Item->new(
163
        { biblionumber => $biblio->biblionumber } )->edit_form;
164
165
    my ( $subfield ) = grep { $_->{kohafield} eq 'items.homebranch' } @$subfields;
166
    is( $subfield->{marc_value}->{default}, '', 'no library preselected if no branchcode passed');
167
168
    $subfields =
169
      Koha::UI::Form::Builder::Item->new(
170
        { biblionumber => $biblio->biblionumber } )->edit_form({ branchcode => $library->branchcode });
171
172
    ( $subfield ) = grep { $_->{kohafield} eq 'items.homebranch' } @$subfields;
173
    is( $subfield->{marc_value}->{default}, $library->branchcode, 'the correct library should be preselected if branchcode is passed');
174
};
175
176
subtest 'default_branches_empty' => sub {
177
    plan tests => 2;
178
179
    my $biblio = $builder->build_sample_biblio({ value => {frameworkcode => ''}});
180
    my $subfields =
181
      Koha::UI::Form::Builder::Item->new(
182
        { biblionumber => $biblio->biblionumber } )->edit_form;
183
184
    my ( $subfield ) = grep { $_->{kohafield} eq 'items.homebranch' } @$subfields;
185
    isnt( $subfield->{marc_value}->{values}->[0], "", 'No empty option for branches' );
186
187
    $subfields =
188
      Koha::UI::Form::Builder::Item->new(
189
        { biblionumber => $biblio->biblionumber } )->edit_form({ default_branches_empty => 1 });
190
191
    ( $subfield ) = grep { $_->{kohafield} eq 'items.homebranch' } @$subfields;
192
    is( $subfield->{marc_value}->{values}->[0], "", 'empty option for branches if default_branches_empty passed' );
193
};
194
195
subtest 'kohafields_to_ignore' => sub {
196
    plan tests => 2;
197
198
    my $biblio =
199
      $builder->build_sample_biblio( { value => { frameworkcode => '' } } );
200
    my $subfields =
201
      Koha::UI::Form::Builder::Item->new(
202
        { biblionumber => $biblio->biblionumber } )->edit_form;
203
204
    my ($subfield) = grep { $_->{kohafield} eq 'items.barcode' } @$subfields;
205
    isnt( $subfield, undef, 'barcode subfield should be in the subfield list' );
206
207
    $subfields =
208
      Koha::UI::Form::Builder::Item->new(
209
        { biblionumber => $biblio->biblionumber } )
210
      ->edit_form( { kohafields_to_ignore => ['items.barcode'] } );
211
212
    ($subfield) = grep { $_->{kohafield} eq 'items.barcode' } @$subfields;
213
    is( $subfield, undef,
214
        'barcode subfield should have not been built if passed to kohafields_to_ignore'
215
    );
216
};
217
218
subtest 'subfields_to_allow & ignore_not_allowed_subfields' => sub {
219
    plan tests => 6;
220
221
    my ( $tag_cn, $subtag_cn ) = C4::Biblio::GetMarcFromKohaField("items.itemcallnumber");
222
    my ( $tag_notes, $subtag_notes ) = C4::Biblio::GetMarcFromKohaField("items.itemnotes");
223
    my $biblio = $builder->build_sample_biblio( { value => { frameworkcode => '' } } );
224
    my $subfields =
225
      Koha::UI::Form::Builder::Item->new(
226
        { biblionumber => $biblio->biblionumber } )->edit_form(
227
            {
228
                subfields_to_allow => [
229
                    sprintf( '%s$%s', $tag_cn,    $subtag_cn ),
230
                    sprintf( '%s$%s', $tag_notes, $subtag_notes )
231
                ]
232
            }
233
        );
234
235
    isnt( scalar(@$subfields), 2, "There are more than the 2 subfields we allowed" );
236
    my ($subfield) = grep { $_->{kohafield} eq 'items.itemcallnumber' } @$subfields;
237
    is( $subfield->{marc_value}->{readonly}, undef, "subfields to allowed are not marked as readonly" );
238
    ($subfield) = grep { $_->{kohafield} eq 'items.copynumber' } @$subfields;
239
    isnt( $subfield->{marc_value}->{readonly}, 1, "subfields that are not in the allow list are marked as readonly" );
240
241
    $subfields =
242
      Koha::UI::Form::Builder::Item->new(
243
        { biblionumber => $biblio->biblionumber } )->edit_form(
244
            {
245
                subfields_to_allow => [
246
                    sprintf( '%s$%s', $tag_cn,    $subtag_cn ),
247
                    sprintf( '%s$%s', $tag_notes, $subtag_notes )
248
                ],
249
                ignore_not_allowed_subfields => 1,
250
            }
251
        );
252
253
    is( scalar(@$subfields), 2, "With ignore_not_allowed_subfields, only the subfields to ignore are returned" );
254
    ($subfield) =
255
      grep { $_->{kohafield} eq 'items.itemcallnumber' } @$subfields;
256
    is( $subfield->{marc_value}->{readonly}, undef, "subfields to allowed are not marked as readonly" );
257
    ($subfield) = grep { $_->{kohafield} eq 'items.copynumber' } @$subfields;
258
    is( $subfield, undef, "subfield that is not in the allow list is not returned" );
259
};
260
261
262
$cache->clear_from_cache("MarcStructure-0-");
263
$cache->clear_from_cache("MarcStructure-1-");
264
$cache->clear_from_cache("default_value_for_mod_marc-");
265
$cache->clear_from_cache("MarcSubfieldStructure-");
266
267
sub setup_mss {
268
269
    my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
270
271
    Koha::MarcSubfieldStructures->search(
272
        {
273
            frameworkcode => '',
274
            tagfield => $itemtag,
275
            tagsubfield => 'é',
276
        }
277
    )->delete;    # In case it exist already
278
279
    Koha::MarcSubfieldStructure->new(
280
        {
281
            frameworkcode => '',
282
            tagfield      => $itemtag,
283
            tagsubfield   => 'é',
284
            kohafield     => undef,
285
            repeatable    => 1,
286
            defaultvalue  => 'ééé',
287
            tab           => 10,
288
        }
289
    )->store;
290
291
    Koha::MarcSubfieldStructures->search(
292
        {
293
            frameworkcode => '',
294
            tagfield      => $itemtag,
295
            tagsubfield   => [ 'x' ]
296
        }
297
    )->update( { kohafield => undef } );
298
299
    Koha::MarcSubfieldStructures->search(
300
        {
301
            frameworkcode => '',
302
            tagfield => $itemtag,
303
            tagsubfield => [ 'x', 'é' ],
304
        }
305
    )->update( { repeatable => 1 } );
306
307
    Koha::MarcSubfieldStructures->search(
308
        {
309
            frameworkcode => '',
310
            tagfield => $itemtag,
311
            tagsubfield => ['t'],
312
        }
313
    )->update( { repeatable => 0 } );
314
315
    Koha::MarcSubfieldStructures->search(
316
        {
317
            frameworkcode => '',
318
            tagfield => $itemtag,
319
            tagsubfield => ['z'],
320
        }
321
    )->update( { kohafield => 'items.itemnotes', repeatable => 1 } );
322
323
    Koha::MarcSubfieldStructures->search(
324
        {
325
            frameworkcode => '',
326
            tagfield => $itemtag,
327
        }
328
    )->update( { display_order => \['FLOOR( 1 + RAND( ) * 10 )'] } );
329
}
(-)a/tools/batchMod.pl (-427 / +113 lines)
Lines 24-56 use Try::Tiny qw( catch try ); Link Here
24
24
25
use C4::Auth qw( get_template_and_user haspermission );
25
use C4::Auth qw( get_template_and_user haspermission );
26
use C4::Output qw( output_html_with_http_headers );
26
use C4::Output qw( output_html_with_http_headers );
27
use C4::Biblio qw(
28
    DelBiblio
29
    GetAuthorisedValueDesc
30
    GetMarcFromKohaField
31
    GetMarcStructure
32
    IsMarcStructureInternal
33
    TransformHtmlToXml
34
);
35
use C4::Items qw( GetItemsInfo Item2Marc ModItemFromMarc );
36
use C4::Circulation qw( LostItem IsItemIssued );
37
use C4::Context;
27
use C4::Context;
38
use C4::Koha;
39
use C4::BackgroundJob;
40
use C4::ClassSource qw( GetClassSources GetClassSource );
41
use MARC::File::XML;
28
use MARC::File::XML;
42
use List::MoreUtils qw( uniq );
29
use List::MoreUtils qw( uniq );
30
use Encode qw( encode_utf8 );
43
31
44
use Koha::Database;
32
use Koha::Database;
33
use Koha::DateUtils qw( dt_from_string );
45
use Koha::Exceptions::Exception;
34
use Koha::Exceptions::Exception;
46
use Koha::AuthorisedValues;
47
use Koha::Biblios;
35
use Koha::Biblios;
48
use Koha::DateUtils qw( dt_from_string );
49
use Koha::Items;
36
use Koha::Items;
50
use Koha::ItemTypes;
51
use Koha::Patrons;
37
use Koha::Patrons;
52
use Koha::SearchEngine::Indexer;
38
use Koha::Item::Attributes;
39
use Koha::BackgroundJob::BatchDeleteItem;
40
use Koha::BackgroundJob::BatchUpdateItem;
53
use Koha::UI::Form::Builder::Item;
41
use Koha::UI::Form::Builder::Item;
42
use Koha::UI::Table::Builder::Items;
54
43
55
my $input = CGI->new;
44
my $input = CGI->new;
56
my $dbh = C4::Context->dbh;
45
my $dbh = C4::Context->dbh;
Lines 90-346 my $restrictededition = $uid ? haspermission($uid, {'tools' => 'items_batchmod_ Link Here
90
# In case user is a superlibrarian, edition is not restricted
79
# In case user is a superlibrarian, edition is not restricted
91
$restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian());
80
$restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian());
92
81
93
$template->param(del       => $del);
94
95
my $nextop="";
82
my $nextop="";
96
my @errors; # store errors found while checking data BEFORE saving item.
83
my $display_items;
97
my $items_display_hashref;
98
our $tagslib = &GetMarcStructure(1);
99
100
my $deleted_items = 0;     # Number of deleted items
101
my $deleted_records = 0;   # Number of deleted records ( with no items attached )
102
my $not_deleted_items = 0; # Number of items that could not be deleted
103
my @not_deleted;           # List of the itemnumbers that could not be deleted
104
my $modified_items = 0;    # Numbers of modified items
105
my $modified_fields = 0;   # Numbers of modified fields
106
84
107
my %cookies = parse CGI::Cookie($cookie);
85
my %cookies = parse CGI::Cookie($cookie);
108
my $sessionID = $cookies{'CGISESSID'}->value;
86
my $sessionID = $cookies{'CGISESSID'}->value;
109
87
88
my @messages;
110
89
111
#--- ----------------------------------------------------------------------------
90
if ( $op eq "action" ) {
112
if ($op eq "action") {
91
113
#-------------------------------------------------------------------------------
92
    if ($del) {
114
    my @tags      = $input->multi_param('tag');
115
    my @subfields = $input->multi_param('subfield');
116
    my @values    = $input->multi_param('field_value');
117
    my @searches  = $input->multi_param('regex_search');
118
    my @replaces  = $input->multi_param('regex_replace');
119
    my @modifiers = $input->multi_param('regex_modifiers');
120
    my @subfields_to_blank = $input->multi_param('disable_input');
121
    # build indicator hash.
122
    my @ind_tag   = $input->multi_param('ind_tag');
123
    my @indicator = $input->multi_param('indicator');
124
125
    my $upd_biblionumbers;
126
    my $del_biblionumbers;
127
    if ( $del ) {
128
        try {
93
        try {
129
            my $schema = Koha::Database->new->schema;
94
            my $params = {
130
            $schema->txn_do(
95
                record_ids     => \@itemnumbers,
131
                sub {
96
                delete_biblios => $del_records,
132
                    foreach my $itemnumber (@itemnumbers) {
97
            };
133
                        my $item = Koha::Items->find($itemnumber);
98
            my $job_id =
134
                        next
99
              Koha::BackgroundJob::BatchDeleteItem->new->enqueue($params);
135
                          unless $item
100
            $nextop = 'enqueued';
136
                          ; # Should have been tested earlier, but just in case...
101
            $template->param( job_id => $job_id, );
137
                        my $itemdata = $item->unblessed;
138
                        my $return = $item->safe_delete;
139
                        if ( ref( $return ) ) {
140
                            $deleted_items++;
141
                            push @$upd_biblionumbers, $itemdata->{'biblionumber'};
142
                        }
143
                        else {
144
                            $not_deleted_items++;
145
                            push @not_deleted,
146
                              {
147
                                biblionumber => $itemdata->{'biblionumber'},
148
                                itemnumber   => $itemdata->{'itemnumber'},
149
                                barcode      => $itemdata->{'barcode'},
150
                                title        => $itemdata->{'title'},
151
                                reason       => $return,
152
                              };
153
                        }
154
155
                        # If there are no items left, delete the biblio
156
                        if ($del_records) {
157
                            my $itemscount = Koha::Biblios->find( $itemdata->{'biblionumber'} )->items->count;
158
                            if ( $itemscount == 0 ) {
159
                                my $error = DelBiblio( $itemdata->{'biblionumber'}, { skip_record_index => 1 } );
160
                                unless ($error) {
161
                                    $deleted_records++;
162
                                    push @$del_biblionumbers, $itemdata->{'biblionumber'};
163
                                    if ( $src eq 'CATALOGUING' ) {
164
                                        # We are coming catalogue/detail.pl, there were items from a single bib record
165
                                        $template->param( biblio_deleted => 1 );
166
                                    }
167
                                }
168
                            }
169
                        }
170
                    }
171
                    if (@not_deleted) {
172
                        Koha::Exceptions::Exception->throw(
173
                            'Some items have not been deleted, rolling back');
174
                    }
175
                }
176
            );
177
        }
102
        }
178
        catch {
103
        catch {
179
            warn $_;
104
            warn $_;
180
            if ( $_->isa('Koha::Exceptions::Exception') ) {
105
            push @messages,
181
                $template->param( deletion_failed => 1 );
106
              {
182
            }
107
                type  => 'error',
183
            die "Something terrible has happened!"
108
                code  => 'cannot_enqueue_job',
184
                if ($_ =~ /Rollback failed/); # Rollback failed
109
                error => $_,
110
              };
111
            $template->param( view => 'errors' );
185
        };
112
        };
186
    }
113
    }
187
114
188
    else { # modification
115
    else {    # modification
189
116
190
        my @columns = Koha::Items->columns;
117
        my @item_columns = Koha::Items->columns;
191
118
192
        my $new_item_data;
119
        my $new_item_data;
193
        my @columns_with_regex;
120
        my ( $columns_with_regex );
194
        for my $c ( @columns ) {
121
        my @subfields_to_blank = $input->multi_param('disable_input');
195
            if ( $c eq 'more_subfields_xml' ) {
122
        my @more_subfields = $input->multi_param("items.more_subfields_xml");
196
                my @more_subfields_xml = $input->multi_param("items.more_subfields_xml");
123
        for my $item_column (@item_columns) {
197
                my @unlinked_item_subfields;
124
            my @attributes       = ($item_column);
198
                for my $subfield ( @more_subfields_xml ) {
125
            my $cgi_param_prefix = 'items.';
199
                    my $v = $input->param('items.more_subfields_xml_' . $subfield);
126
            if ( $item_column eq 'more_subfields_xml' ) {
200
                    push @unlinked_item_subfields, $subfield, $v;
127
                @attributes       = ();
201
                }
128
                $cgi_param_prefix = 'items.more_subfields_xml_';
202
                if ( @unlinked_item_subfields ) {
129
                for my $subfield (@more_subfields) {
203
                    my $marc = MARC::Record->new();
130
                    push @attributes, $subfield;
204
                    # use of tag 999 is arbitrary, and doesn't need to match the item tag
205
                    # used in the framework
206
                    $marc->append_fields(MARC::Field->new('999', ' ', ' ', @unlinked_item_subfields));
207
                    $marc->encoding("UTF-8");
208
                    # FIXME This is WRONG! We need to use the values that haven't been modified by the batch tool!
209
                    $new_item_data->{more_subfields_xml} = $marc->as_xml("USMARC");
210
                    next;
211
                }
131
                }
212
                $new_item_data->{more_subfields_xml} = undef;
132
            }
213
                # FIXME deal with more_subfields_xml and @subfields_to_blank
214
            } elsif ( grep { $c eq $_ } @subfields_to_blank ) {
215
                # Empty this column
216
                $new_item_data->{$c} = undef
217
            } else {
218
133
219
                my @v = grep { $_ ne "" }
134
            for my $attr (@attributes) {
220
                    uniq $input->multi_param( "items." . $c );
221
135
222
                next unless @v;
136
                my $cgi_var_name = $cgi_param_prefix
137
                  . encode_utf8($attr)
138
                  ;  # We need to deal correctly with encoding on subfield codes
223
139
224
                $new_item_data->{$c} = join ' | ', @v;
140
                if ( grep { $attr eq $_ } @subfields_to_blank ) {
225
            }
141
142
                    # Empty this column
143
                    $new_item_data->{$attr} = undef;
144
                }
145
                elsif ( my $regex_search =
146
                    $input->param( $cgi_var_name . '_regex_search' ) )
147
                {
148
                    $columns_with_regex->{$attr} = {
149
                        search => $regex_search,
150
                        replace =>
151
                          $input->param( $cgi_var_name . '_regex_replace' ),
152
                        modifiers =>
153
                          $input->param( $cgi_var_name . '_regex_modifiers' )
154
                    };
155
                }
156
                else {
157
                    my @v =
158
                      grep { $_ ne "" } uniq $input->multi_param($cgi_var_name);
226
159
227
            if ( my $regex_search = $input->param('items.'.$c.'_regex_search') ) {
160
                    next unless @v;
228
                push @columns_with_regex, $c;
161
162
                    $new_item_data->{$attr} = join '|', @v;
163
                }
229
            }
164
            }
230
        }
165
        }
231
166
167
        my $params = {
168
            record_ids                        => \@itemnumbers,
169
            regex_mod                         => $columns_with_regex,
170
            new_values                        => $new_item_data,
171
            exclude_from_local_holds_priority => (
172
                defined $exclude_from_local_holds_priority
173
                  && $exclude_from_local_holds_priority ne ""
174
              )
175
            ? $exclude_from_local_holds_priority
176
            : undef,
177
178
        };
232
        try {
179
        try {
233
            my $schema = Koha::Database->new->schema;
180
            my $job_id =
234
            $schema->txn_do(
181
              Koha::BackgroundJob::BatchUpdateItem->new->enqueue($params);
235
                sub {
182
            $nextop = 'enqueued';
236
183
            $template->param( job_id => $job_id, );
237
                    foreach my $itemnumber (@itemnumbers) {
238
                        my $item = Koha::Items->find($itemnumber);
239
                        next
240
                          unless $item
241
                          ; # Should have been tested earlier, but just in case...
242
                        my $itemdata = $item->unblessed;
243
244
                        my $modified_holds_priority = 0;
245
                        if ( defined $exclude_from_local_holds_priority && $exclude_from_local_holds_priority ne "" ) {
246
                            if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
247
                                $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
248
                                $modified_holds_priority = 1;
249
                            }
250
                        }
251
252
                        my $modified = 0;
253
                        for my $c ( @columns_with_regex ) {
254
                            my $regex_search = $input->param('items.'.$c.'_regex_search');
255
                            my $old_value = $item->$c;
256
257
                            my $value = apply_regex(
258
                                {
259
                                    search  => $regex_search,
260
                                    replace => $input->param(
261
                                        'items' . $c . '_regex_replace'
262
                                    ),
263
                                    modifiers => $input->param(
264
                                        'items' . $c . '_regex_modifiers'
265
                                    ),
266
                                    value => $old_value,
267
                                }
268
                            );
269
                            unless ( $old_value eq $value ) {
270
                                $modified++;
271
                                $item->$c($value);
272
                            }
273
                        }
274
275
                        $modified += scalar(keys %$new_item_data); # FIXME This is incorrect if old value == new value. Should we loop of the keys and compare the before/after values?
276
                        if ( $modified) {
277
                            my $itemlost_pre = $item->itemlost;
278
                            $item->set($new_item_data)->store({skip_record_index => 1});
279
280
                            push @$upd_biblionumbers, $itemdata->{'biblionumber'};
281
282
                            LostItem(
283
                                $item->itemnumber, 'batchmod', undef,
284
                                { skip_record_index => 1 }
285
                            ) if $item->itemlost
286
                                  and not $itemlost_pre;
287
                        }
288
289
                        $modified_items++ if $modified || $modified_holds_priority;
290
                        $modified_fields += $modified + $modified_holds_priority;
291
                    }
292
                }
293
            );
294
        }
184
        }
295
        catch {
185
        catch {
296
            warn $_;
186
            push @messages,
297
            die "Something terrible has happened!"
187
              {
298
                if ($_ =~ /Rollback failed/); # Rollback failed
188
                type  => 'error',
189
                code  => 'cannot_enqueue_job',
190
                error => $_,
191
              };
192
            $template->param( view => 'errors' );
299
        };
193
        };
300
    }
194
    }
301
195
302
    $upd_biblionumbers = [ uniq @$upd_biblionumbers ]; # Only update each bib once
303
304
    # Don't send specialUpdate for records we are going to delete
305
    my %del_bib_hash = map{ $_ => undef } @$del_biblionumbers;
306
    @$upd_biblionumbers = grep( ! exists( $del_bib_hash{$_} ), @$upd_biblionumbers );
307
308
    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
309
    $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
310
    $indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers;
311
312
    # Once the job is done
313
    # If we have a reasonable amount of items, we display them
314
    my $max_items = $del ? C4::Context->preference("MaxItemsToDisplayForBatchDel") : C4::Context->preference("MaxItemsToDisplayForBatchMod");
315
    if (scalar(@itemnumbers) <= $max_items ){
316
        if (scalar(@itemnumbers) <= 1000 ) {
317
            $items_display_hashref=BuildItemsData(@itemnumbers);
318
        } else {
319
            # Else, we only display the barcode
320
            my @simple_items_display = map {
321
                my $itemnumber = $_;
322
                my $item = Koha::Items->find($itemnumber);
323
                {
324
                    itemnumber   => $itemnumber,
325
                    barcode      => $item ? ( $item->barcode // q{} ) : q{},
326
                    biblionumber => $item ? $item->biblio->biblionumber : q{},
327
                };
328
            } @itemnumbers;
329
            $template->param("simple_items_display" => \@simple_items_display);
330
        }
331
    } else {
332
        $template->param( "too_many_items_display" => scalar(@itemnumbers) );
333
        $template->param( "job_completed" => 1 );
334
    }
335
336
337
    # Calling the template
338
    $template->param(
339
        modified_items => $modified_items,
340
        modified_fields => $modified_fields,
341
    );
342
343
}
196
}
197
198
$template->param(
199
    messages => \@messages,
200
);
344
#
201
#
345
#-------------------------------------------------------------------------------
202
#-------------------------------------------------------------------------------
346
# build screen with existing items. and "new" one
203
# build screen with existing items. and "new" one
Lines 376-385 if ($op eq "show"){ Link Here
376
        }
233
        }
377
    } else {
234
    } else {
378
        if (defined $biblionumber && !@itemnumbers){
235
        if (defined $biblionumber && !@itemnumbers){
379
            my @all_items = GetItemsInfo( $biblionumber );
236
            my $biblio = Koha::Biblios->find($biblionumber);
380
            foreach my $itm (@all_items) {
237
            @itemnumbers = $biblio ? $biblio->items->get_column('itemnumber') : ();
381
                push @itemnumbers, $itm->{itemnumber};
382
            }
383
        }
238
        }
384
        if ( my $list = $input->param('barcodelist') ) {
239
        if ( my $list = $input->param('barcodelist') ) {
385
            my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list );
240
            my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list );
Lines 399-405 if ($op eq "show"){ Link Here
399
        : C4::Context->preference("MaxItemsToDisplayForBatchMod");
254
        : C4::Context->preference("MaxItemsToDisplayForBatchMod");
400
    $template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod");
255
    $template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod");
401
    if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) {
256
    if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) {
402
        $items_display_hashref=BuildItemsData(@itemnumbers);
257
        $display_items = 1;
403
    } else {
258
    } else {
404
        $template->param("too_many_items_display" => scalar(@itemnumbers));
259
        $template->param("too_many_items_display" => scalar(@itemnumbers));
405
        # Even if we do not display the items, we need the itemnumbers
260
        # Even if we do not display the items, we need the itemnumbers
Lines 407-416 if ($op eq "show"){ Link Here
407
    }
262
    }
408
263
409
    # now, build the item form for entering a new item
264
    # now, build the item form for entering a new item
410
    my @loop_data =();
411
    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
412
413
    my $pref_itemcallnumber = C4::Context->preference('itemcallnumber');
414
265
415
    # Getting list of subfields to keep when restricted batchmod edit is enabled
266
    # Getting list of subfields to keep when restricted batchmod edit is enabled
416
    my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod') : ();
267
    my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod') : ();
Lines 423-429 if ($op eq "show"){ Link Here
423
                ? ( subfields_to_allow => \@subfields_to_allow )
274
                ? ( subfields_to_allow => \@subfields_to_allow )
424
                : ()
275
                : ()
425
            ),
276
            ),
426
            subfields_to_ignore         => ['items.barcode'],
277
            ignore_not_allowed_subfields => 1,
278
            kohafields_to_ignore         => ['items.barcode'],
427
            prefill_with_default_values => $use_default_values,
279
            prefill_with_default_values => $use_default_values,
428
            default_branches_empty      => 1,
280
            default_branches_empty      => 1,
429
        }
281
        }
Lines 438-624 if ($op eq "show"){ Link Here
438
    $nextop="action"
290
    $nextop="action"
439
} # -- End action="show"
291
} # -- End action="show"
440
292
441
$template->param(%$items_display_hashref) if $items_display_hashref;
293
if ( $display_items ) {
442
$template->param(
294
    my $items_table =
443
    op      => $nextop,
295
      Koha::UI::Table::Builder::Items->new( { itemnumbers => \@itemnumbers } )
444
);
296
      ->build_table;
445
$template->param( $op => 1 ) if $op;
446
447
if ($op eq "action") {
448
449
    #my @not_deleted_loop = map{{itemnumber=>$_}}@not_deleted;
450
451
    $template->param(
297
    $template->param(
452
	not_deleted_items => $not_deleted_items,
298
        items        => $items_table->{items},
453
	deleted_items => $deleted_items,
299
        item_header_loop => $items_table->{headers},
454
	delete_records => $del_records,
455
	deleted_records => $deleted_records,
456
	not_deleted_loop => \@not_deleted 
457
    );
300
    );
458
}
301
}
459
302
460
foreach my $error (@errors) {
303
$template->param(
461
    $template->param($error => 1) if $error;
304
    op  => $nextop,
462
}
305
    del => $del,
463
$template->param(src => $src);
306
    ( $op ? ( $op => 1 ) : () ),
464
$template->param(biblionumber => $biblionumber);
307
    src          => $src,
465
output_html_with_http_headers $input, $cookie, $template->output;
308
    biblionumber => $biblionumber,
466
exit;
309
);
467
468
469
# ---------------- Functions
470
471
sub BuildItemsData{
472
	my @itemnumbers=@_;
473
		# now, build existiing item list
474
		my %witness; #---- stores the list of subfields used at least once, with the "meaning" of the code
475
		my @big_array;
476
		#---- finds where items.itemnumber is stored
477
    my (  $itemtagfield,   $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
478
    my ($branchtagfield, $branchtagsubfield) = &GetMarcFromKohaField( "items.homebranch" );
479
		foreach my $itemnumber (@itemnumbers){
480
            my $itemdata = Koha::Items->find($itemnumber);
481
            next unless $itemdata; # Should have been tested earlier, but just in case...
482
            $itemdata = $itemdata->unblessed;
483
			my $itemmarc=Item2Marc($itemdata);
484
			my %this_row;
485
			foreach my $field (grep {$_->tag() eq $itemtagfield} $itemmarc->fields()) {
486
				# loop through each subfield
487
				my $itembranchcode=$field->subfield($branchtagsubfield);
488
                if ($itembranchcode && C4::Context->preference("IndependentBranches")) {
489
						#verifying rights
490
						my $userenv = C4::Context->userenv();
491
                        unless (C4::Context->IsSuperLibrarian() or (($userenv->{'branch'} eq $itembranchcode))){
492
								$this_row{'nomod'}=1;
493
						}
494
				}
495
				my $tag=$field->tag();
496
				foreach my $subfield ($field->subfields) {
497
					my ($subfcode,$subfvalue)=@$subfield;
498
					next if ($tagslib->{$tag}->{$subfcode}->{tab} ne 10 
499
							&& $tag        ne $itemtagfield 
500
							&& $subfcode   ne $itemtagsubfield);
501
502
					$witness{$subfcode} = $tagslib->{$tag}->{$subfcode}->{lib} if ($tagslib->{$tag}->{$subfcode}->{tab}  eq 10);
503
					if ($tagslib->{$tag}->{$subfcode}->{tab}  eq 10) {
504
						$this_row{$subfcode}=GetAuthorisedValueDesc( $tag,
505
									$subfcode, $subfvalue, '', $tagslib) 
506
									|| $subfvalue;
507
					}
508
509
					$this_row{itemnumber} = $subfvalue if ($tag eq $itemtagfield && $subfcode eq $itemtagsubfield);
510
				}
511
			}
512
513
            # grab title, author, and ISBN to identify bib that the item
514
            # belongs to in the display
515
            my $biblio = Koha::Biblios->find( $itemdata->{biblionumber} );
516
            $this_row{title}        = $biblio->title;
517
            $this_row{author}       = $biblio->author;
518
            $this_row{isbn}         = $biblio->biblioitem->isbn;
519
            $this_row{biblionumber} = $biblio->biblionumber;
520
            $this_row{holds}        = $biblio->holds->count;
521
            $this_row{item_holds}   = Koha::Holds->search( { itemnumber => $itemnumber } )->count;
522
            $this_row{item}         = Koha::Items->find($itemnumber);
523
524
			if (%this_row) {
525
				push(@big_array, \%this_row);
526
			}
527
		}
528
		@big_array = sort {$a->{0} cmp $b->{0}} @big_array;
529
530
		# now, construct template !
531
		# First, the existing items for display
532
		my @item_value_loop;
533
		my @witnesscodessorted=sort keys %witness;
534
		for my $row ( @big_array ) {
535
			my %row_data;
536
			my @item_fields = map +{ field => $_ || '' }, @$row{ @witnesscodessorted };
537
			$row_data{item_value} = [ @item_fields ];
538
			$row_data{itemnumber} = $row->{itemnumber};
539
			#reporting this_row values
540
			$row_data{'nomod'} = $row->{'nomod'};
541
      $row_data{bibinfo} = $row->{bibinfo};
542
      $row_data{author} = $row->{author};
543
      $row_data{title} = $row->{title};
544
      $row_data{isbn} = $row->{isbn};
545
      $row_data{biblionumber} = $row->{biblionumber};
546
      $row_data{holds}        = $row->{holds};
547
      $row_data{item_holds}   = $row->{item_holds};
548
      $row_data{item}         = $row->{item};
549
      $row_data{safe_to_delete} = $row->{item}->safe_to_delete;
550
      my $is_on_loan = C4::Circulation::IsItemIssued( $row->{itemnumber} );
551
      $row_data{onloan} = $is_on_loan ? 1 : 0;
552
			push(@item_value_loop,\%row_data);
553
		}
554
		my @header_loop=map { { header_value=> $witness{$_}} } @witnesscodessorted;
555
556
    my @cannot_be_deleted = map {
557
        $_->{safe_to_delete} == 1 ? () : $_->{item}->barcode
558
    } @item_value_loop;
559
    return {
560
        item_loop        => \@item_value_loop,
561
        cannot_be_deleted => \@cannot_be_deleted,
562
        item_header_loop => \@header_loop
563
    };
564
}
565
566
#BE WARN : it is not the general case 
567
# This function can be OK in the item marc record special case
568
# Where subfield is not repeated
569
# And where we are sure that field should correspond
570
# And $tag>10
571
sub UpdateMarcWith {
572
  my ($marcfrom,$marcto)=@_;
573
    my (  $itemtag,   $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
574
    my $fieldfrom=$marcfrom->field($itemtag);
575
    my @fields_to=$marcto->field($itemtag);
576
    my $modified = 0;
577
578
    return $modified unless $fieldfrom;
579
580
    foreach my $subfield ( $fieldfrom->subfields() ) {
581
        foreach my $field_to_update ( @fields_to ) {
582
            if ( $subfield->[1] ) {
583
                unless ( $field_to_update->subfield($subfield->[0]) eq $subfield->[1] ) {
584
                    $modified++;
585
                    $field_to_update->update( $subfield->[0] => $subfield->[1] );
586
                }
587
            }
588
            else {
589
                $modified++;
590
                $field_to_update->delete_subfield( code => $subfield->[0] );
591
            }
592
        }
593
    }
594
    return $modified;
595
}
596
597
sub apply_regex {
598
    my ($params) = @_;
599
    my $search   = $params->{search};
600
    my $replace  = $params->{replace};
601
    my $modifiers = $params->{modifiers} || [];
602
    my $value = $params->{value};
603
604
    my @available_modifiers = qw( i g );
605
    my $retained_modifiers  = q||;
606
    for my $modifier ( split //, @$modifiers ) {
607
        $retained_modifiers .= $modifier
608
          if grep { /$modifier/ } @available_modifiers;
609
    }
610
    if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
611
        $value =~ s/$search/$replace/ig;
612
    }
613
    elsif ( $retained_modifiers eq 'i' ) {
614
        $value =~ s/$search/$replace/i;
615
    }
616
    elsif ( $retained_modifiers eq 'g' ) {
617
        $value =~ s/$search/$replace/g;
618
    }
619
    else {
620
        $value =~ s/$search/$replace/;
621
    }
622
310
623
    return $value;
311
output_html_with_http_headers $input, $cookie, $template->output;
624
}
625
- 

Return to bug 28445