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

(-)a/Koha/BackgroundJob.pm (-1 / +5 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
use Koha::BackgroundJob::BatchCancelHold;
33
use Koha::BackgroundJob::BatchCancelHold;
32
34
33
use base qw( Koha::Object );
35
use base qw( Koha::Object );
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 256-261 sub type_to_class_mapping { Link Here
256
        batch_authority_record_modification => 'Koha::BackgroundJob::BatchUpdateAuthority',
258
        batch_authority_record_modification => 'Koha::BackgroundJob::BatchUpdateAuthority',
257
        batch_biblio_record_deletion        => 'Koha::BackgroundJob::BatchDeleteBiblio',
259
        batch_biblio_record_deletion        => 'Koha::BackgroundJob::BatchDeleteBiblio',
258
        batch_biblio_record_modification    => 'Koha::BackgroundJob::BatchUpdateBiblio',
260
        batch_biblio_record_modification    => 'Koha::BackgroundJob::BatchUpdateBiblio',
261
        batch_item_record_deletion          => 'Koha::BackgroundJob::BatchDeleteItem',
262
        batch_item_record_modification      => 'Koha::BackgroundJob::BatchUpdateItem',
259
        batch_hold_cancel                   => 'Koha::BackgroundJob::BatchCancelHold',
263
        batch_hold_cancel                   => 'Koha::BackgroundJob::BatchCancelHold',
260
    };
264
    };
261
}
265
}
(-)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 (-24 / +60 lines)
Lines 38-43 use Koha::SearchEngine::Indexer; Link Here
38
use Koha::Exceptions::Item::Transfer;
38
use Koha::Exceptions::Item::Transfer;
39
use Koha::Item::Transfer::Limits;
39
use Koha::Item::Transfer::Limits;
40
use Koha::Item::Transfers;
40
use Koha::Item::Transfers;
41
use Koha::Item::Attributes;
41
use Koha::ItemTypes;
42
use Koha::ItemTypes;
42
use Koha::Patrons;
43
use Koha::Patrons;
43
use Koha::Plugins;
44
use Koha::Plugins;
Lines 850-857 sub has_pending_hold { Link Here
850
851
851
=head3 as_marc_field
852
=head3 as_marc_field
852
853
853
    my $mss   = C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
854
    my $field = $item->as_marc_field;
854
    my $field = $item->as_marc_field({ [ mss => $mss ] });
855
855
856
This method returns a MARC::Field object representing the Koha::Item object
856
This method returns a MARC::Field object representing the Koha::Item object
857
with the current mappings configuration.
857
with the current mappings configuration.
Lines 859-895 with the current mappings configuration. Link Here
859
=cut
859
=cut
860
860
861
sub as_marc_field {
861
sub as_marc_field {
862
    my ( $self, $params ) = @_;
862
    my ( $self ) = @_;
863
863
864
    my $mss = $params->{mss} // C4::Biblio::GetMarcSubfieldStructure( '', { unsafe => 1 } );
864
    my ( $itemtag, $itemtagsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
865
    my $item_tag = $mss->{'items.itemnumber'}[0]->{tagfield};
866
865
867
    my @subfields;
866
    my $tagslib = C4::Biblio::GetMarcStructure( 1, $self->biblio->frameworkcode, { unsafe => 1 });
868
867
869
    my @columns = $self->_result->result_source->columns;
868
    my @subfields;
870
869
871
    foreach my $item_field ( @columns ) {
870
    my $item_field = $tagslib->{$itemtag};
872
        my $mapping = $mss->{ "items.$item_field"}[0];
871
873
        my $tagfield    = $mapping->{tagfield};
872
    my $more_subfields = $self->additional_attributes->to_hashref;
874
        my $tagsubfield = $mapping->{tagsubfield};
873
    foreach my $subfield (
875
        next if !$tagfield; # TODO: Should we raise an exception instead?
874
        sort {
876
                            # Feels like safe fallback is better
875
               $a->{display_order} <=> $b->{display_order}
876
            || $a->{subfield} cmp $b->{subfield}
877
        } grep { ref($_) && %$_ } values %$item_field
878
    ){
879
880
        my $kohafield = $subfield->{kohafield};
881
        my $tagsubfield = $subfield->{tagsubfield};
882
        my $value;
883
        if ( defined $kohafield ) {
884
            next if $kohafield !~ m{^items\.}; # That would be weird!
885
            ( my $attribute = $kohafield ) =~ s|^items\.||;
886
            $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
887
                if defined $self->$attribute and $self->$attribute ne '';
888
        } else {
889
            $value = $more_subfields->{$tagsubfield}
890
        }
877
891
878
        push @subfields, $tagsubfield => $self->$item_field
892
        next unless defined $value
879
            if defined $self->$item_field and $item_field ne '';
893
            and $value ne q{};
880
    }
881
894
882
    my $unlinked_item_subfields = C4::Items::_parse_unlinked_item_subfields_from_xml($self->more_subfields_xml);
895
        if ( $subfield->{repeatable} ) {
883
    push( @subfields, @{$unlinked_item_subfields} )
896
            my @values = split '\|', $value;
884
        if defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1;
897
            push @subfields, ( $tagsubfield => $_ ) for @values;
898
        }
899
        else {
900
            push @subfields, ( $tagsubfield => $value );
901
        }
885
902
886
    my $field;
903
    }
887
904
888
    $field = MARC::Field->new(
905
    return unless @subfields;
889
        "$item_tag", ' ', ' ', @subfields
890
    ) if @subfields;
891
906
892
    return $field;
907
    return MARC::Field->new(
908
        "$itemtag", ' ', ' ', @subfields
909
    );
893
}
910
}
894
911
895
=head3 renewal_branchcode
912
=head3 renewal_branchcode
Lines 1019-1024 sub columns_to_str { Link Here
1019
    return $values;
1036
    return $values;
1020
}
1037
}
1021
1038
1039
=head3 additional_attributes
1040
1041
    my $attributes = $item->additional_attributes;
1042
    $attributes->{k} = 'new k';
1043
    $item->update({ more_subfields => $attributes->to_marcxml });
1044
1045
Returns a Koha::Item::Attributes object that represents the non-mapped
1046
attributes for this item.
1047
1048
=cut
1049
1050
sub additional_attributes {
1051
    my ($self) = @_;
1052
1053
    return Koha::Item::Attributes->new_from_marcxml(
1054
        $self->more_subfields_xml,
1055
    );
1056
}
1057
1022
=head3 _set_found_trigger
1058
=head3 _set_found_trigger
1023
1059
1024
    $self->_set_found_trigger
1060
    $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
use Koha::CirculationRules;
32
use Koha::CirculationRules;
27
33
Lines 149-154 sub filter_out_lost { Link Here
149
    return $self->search( $params );
155
    return $self->search( $params );
150
}
156
}
151
157
158
152
=head3 move_to_biblio
159
=head3 move_to_biblio
153
160
154
 $items->move_to_biblio($to_biblio);
161
 $items->move_to_biblio($to_biblio);
Lines 171-176 sub move_to_biblio { Link Here
171
    }
178
    }
172
}
179
}
173
180
181
=head3 batch_update
182
183
    Koha::Items->search->batch_update
184
        {
185
            new_values => {
186
                itemnotes => $new_item_notes,
187
                k         => $k,
188
            },
189
            regex_mod => {
190
                itemnotes_nonpublic => {
191
                    search => 'foo',
192
                    replace => 'bar',
193
                    modifiers => 'gi',
194
                },
195
            },
196
            exclude_from_local_holds_priority => 1|0,
197
            callback => sub {
198
                # increment something here
199
            },
200
        }
201
    );
202
203
Batch update the items.
204
205
Returns ( $report, $self )
206
Report has 2 keys:
207
  * modified_itemnumbers - list of the modified itemnumbers
208
  * modified_fields - number of fields modified
209
210
Parameters:
211
212
=over
213
214
=item new_values
215
216
Allows to set a new value for given fields.
217
The key can be one of the item's column name, or one subfieldcode of a MARC subfields not linked with a Koha field
218
219
=item regex_mod
220
221
Allows to modify existing subfield's values using a regular expression
222
223
=item exclude_from_local_holds_priority
224
225
Set the passed boolean value to items.exclude_from_local_holds_priority
226
227
=item callback
228
229
Callback function to call after an item has been modified
230
231
=back
232
233
=cut
234
235
sub batch_update {
236
    my ( $self, $params ) = @_;
237
238
    my $regex_mod = $params->{regex_mod} || {};
239
    my $new_values = $params->{new_values} || {};
240
    my $exclude_from_local_holds_priority = $params->{exclude_from_local_holds_priority};
241
    my $callback = $params->{callback};
242
243
    my (@modified_itemnumbers, $modified_fields);
244
    my $i;
245
    while ( my $item = $self->next ) {
246
247
        my $modified_holds_priority = 0;
248
        if ( defined $exclude_from_local_holds_priority ) {
249
            if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
250
                $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
251
                $modified_holds_priority = 1;
252
            }
253
        }
254
255
        my $modified = 0;
256
        my $new_values = {%$new_values};    # Don't modify the original
257
258
        my $old_values = $item->unblessed;
259
        if ( $item->more_subfields_xml ) {
260
            $old_values = {
261
                %$old_values,
262
                %{$item->additional_attributes->to_hashref},
263
            };
264
        }
265
266
        for my $attr ( keys %$regex_mod ) {
267
            my $old_value = $old_values->{$attr};
268
269
            next unless $old_value;
270
271
            my $value = apply_regex(
272
                {
273
                    %{ $regex_mod->{$attr} },
274
                    value => $old_value,
275
                }
276
            );
277
278
            $new_values->{$attr} = $value;
279
        }
280
281
        for my $attribute ( keys %$new_values ) {
282
            next if $attribute eq 'more_subfields_xml'; # Already counted before
283
284
            my $old = $old_values->{$attribute};
285
            my $new = $new_values->{$attribute};
286
            $modified++
287
              if ( defined $old xor defined $new )
288
              || ( defined $old && defined $new && $new ne $old );
289
        }
290
291
        { # Dealing with more_subfields_xml
292
293
            my $frameworkcode = $item->biblio->frameworkcode;
294
            my $tagslib = C4::Biblio::GetMarcStructure( 1, $frameworkcode, { unsafe => 1 });
295
            my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
296
297
            my @more_subfield_tags = map {
298
                (
299
                         ref($_)
300
                      && %$_
301
                      && !$_->{kohafield}    # Get subfields that are not mapped
302
                  )
303
                  ? $_->{tagsubfield}
304
                  : ()
305
            } values %{ $tagslib->{$itemtag} };
306
307
            my $more_subfields_xml = Koha::Item::Attributes->new(
308
                {
309
                    map {
310
                        exists $new_values->{$_} ? ( $_ => $new_values->{$_} )
311
                          : exists $old_values->{$_}
312
                          ? ( $_ => $old_values->{$_} )
313
                          : ()
314
                    } @more_subfield_tags
315
                }
316
            )->to_marcxml($frameworkcode);
317
318
            $new_values->{more_subfields_xml} = $more_subfields_xml;
319
320
            delete $new_values->{$_} for @more_subfield_tags; # Clean the hash
321
322
        }
323
324
        if ( $modified ) {
325
            my $itemlost_pre = $item->itemlost;
326
            $item->set($new_values)->store({skip_record_index => 1});
327
328
            LostItem(
329
                $item->itemnumber, 'batchmod', undef,
330
                { skip_record_index => 1 }
331
            ) if $item->itemlost
332
                  and not $itemlost_pre;
333
334
            push @modified_itemnumbers, $item->itemnumber if $modified || $modified_holds_priority;
335
            $modified_fields += $modified + $modified_holds_priority;
336
        }
337
338
        if ( $callback ) {
339
            $callback->(++$i);
340
        }
341
    }
342
343
    if (@modified_itemnumbers) {
344
        my @biblionumbers = uniq(
345
            Koha::Items->search( { itemnumber => \@modified_itemnumbers } )
346
                       ->get_column('biblionumber'));
347
348
        my $indexer = Koha::SearchEngine::Indexer->new(
349
            { index => $Koha::SearchEngine::BIBLIOS_INDEX } );
350
        $indexer->index_records( \@biblionumbers, 'specialUpdate',
351
            "biblioserver", undef )
352
          if @biblionumbers;
353
    }
354
355
    return ( { modified_itemnumbers => \@modified_itemnumbers, modified_fields => $modified_fields }, $self );
356
}
357
358
sub apply_regex { # FIXME Should be moved outside of Koha::Items
359
    my ($params) = @_;
360
    my $search   = $params->{search};
361
    my $replace  = $params->{replace};
362
    my $modifiers = $params->{modifiers} || q{};
363
    my $value = $params->{value};
364
365
    my @available_modifiers = qw( i g );
366
    my $retained_modifiers  = q||;
367
    for my $modifier ( split //, $modifiers ) {
368
        $retained_modifiers .= $modifier
369
          if grep { /$modifier/ } @available_modifiers;
370
    }
371
    if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
372
        $value =~ s/$search/$replace/ig;
373
    }
374
    elsif ( $retained_modifiers eq 'i' ) {
375
        $value =~ s/$search/$replace/i;
376
    }
377
    elsif ( $retained_modifiers eq 'g' ) {
378
        $value =~ s/$search/$replace/g;
379
    }
380
    else {
381
        $value =~ s/$search/$replace/;
382
    }
383
384
    return $value;
385
}
386
174
387
175
=head2 Internal methods
388
=head2 Internal methods
176
389
(-)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 380-407 sub generate_subfield_form { Link Here
380
        };
381
        };
381
    }
382
    }
382
383
383
    # Getting list of subfields to keep when restricted editing is enabled
384
    # If we're on restricted editing, and our field is not in the list of subfields to allow,
384
    # FIXME Improve the following block, no need to do it for every subfields
385
    # then it is read-only
385
    my $subfieldsToAllowForRestrictedEditing =
386
    $subfield_data{marc_value}->{readonly} = $readonly;
386
      C4::Context->preference('SubfieldsToAllowForRestrictedEditing');
387
    my $allowAllSubfields = (
388
        not defined $subfieldsToAllowForRestrictedEditing
389
          or $subfieldsToAllowForRestrictedEditing eq q||
390
    ) ? 1 : 0;
391
    my @subfieldsToAllow = split( / /, $subfieldsToAllowForRestrictedEditing );
392
393
# If we're on restricted editing, and our field is not in the list of subfields to allow,
394
# then it is read-only
395
    $subfield_data{marc_value}->{readonly} =
396
      (       not $allowAllSubfields
397
          and $restricted_edition
398
          and !grep { $tag . '$' . $subfieldtag eq $_ } @subfieldsToAllow )
399
      ? 1
400
      : 0;
401
387
402
    return \%subfield_data;
388
    return \%subfield_data;
403
}
389
}
404
390
391
=head3 edit_form
392
405
    my $subfields =
393
    my $subfields =
406
      Koha::UI::Form::Builder::Item->new(
394
      Koha::UI::Form::Builder::Item->new(
407
        { biblionumber => $biblionumber, item => $current_item } )->edit_form(
395
        { biblionumber => $biblionumber, item => $current_item } )->edit_form(
Lines 441-449 List of subfields to prefill (value of syspref SubfieldsToUseWhenPrefill) Link Here
441
429
442
=item subfields_to_allow
430
=item subfields_to_allow
443
431
444
List of subfields to allow (value of syspref SubfieldsToAllowForRestrictedBatchmod)
432
List of subfields to allow (value of syspref SubfieldsToAllowForRestrictedBatchmod or SubfieldsToAllowForRestrictedEditing)
433
434
=item ignore_not_allowed_subfields
435
436
If set, the subfields in subfields_to_allow will be ignored (ie. they will not be part of the subfield list.
437
If not set, the subfields in subfields_to_allow will be marked as readonly.
445
438
446
=item subfields_to_ignore
439
=item kohafields_to_ignore
447
440
448
List of subfields to ignore/skip
441
List of subfields to ignore/skip
449
442
Lines 470-476 sub edit_form { Link Here
470
    my $restricted_edition = $params->{restricted_editition};
463
    my $restricted_edition = $params->{restricted_editition};
471
    my $subfields_to_prefill = $params->{subfields_to_prefill} || [];
464
    my $subfields_to_prefill = $params->{subfields_to_prefill} || [];
472
    my $subfields_to_allow = $params->{subfields_to_allow} || [];
465
    my $subfields_to_allow = $params->{subfields_to_allow} || [];
473
    my $subfields_to_ignore= $params->{subfields_to_ignore} || [];
466
    my $ignore_not_allowed_subfields = $params->{ignore_not_allowed_subfields};
467
    my $kohafields_to_ignore = $params->{kohafields_to_ignore} || [];
474
    my $prefill_with_default_values = $params->{prefill_with_default_values};
468
    my $prefill_with_default_values = $params->{prefill_with_default_values};
475
    my $branch_limit = $params->{branch_limit};
469
    my $branch_limit = $params->{branch_limit};
476
    my $default_branches_empty = $params->{default_branches_empty};
470
    my $default_branches_empty = $params->{default_branches_empty};
Lines 495-504 sub edit_form { Link Here
495
489
496
            next if IsMarcStructureInternal($subfield);
490
            next if IsMarcStructureInternal($subfield);
497
            next if $subfield->{tab} ne "10";
491
            next if $subfield->{tab} ne "10";
498
            next if @$subfields_to_allow && !grep { $subfield->{kohafield} eq $_ } @$subfields_to_allow;
499
            next
492
            next
500
              if grep { $subfield->{kohafield} && $subfield->{kohafield} eq $_ }
493
              if grep { $subfield->{kohafield} && $subfield->{kohafield} eq $_ }
501
              @$subfields_to_ignore;
494
              @$kohafields_to_ignore;
495
496
            my $readonly;
497
            if (
498
                @$subfields_to_allow && !grep {
499
                    sprintf( "%s\$%s", $subfield->{tagfield}, $subfield->{tagsubfield} ) eq $_
500
                } @$subfields_to_allow
501
              )
502
            {
503
504
                next if $ignore_not_allowed_subfields;
505
                $readonly = 1 if $restricted_edition;
506
            }
502
507
503
            my @values = ();
508
            my @values = ();
504
509
Lines 546-551 sub edit_form { Link Here
546
                        prefill_with_default_values => $prefill_with_default_values,
551
                        prefill_with_default_values => $prefill_with_default_values,
547
                        branch_limit       => $branch_limit,
552
                        branch_limit       => $branch_limit,
548
                        default_branches_empty => $default_branches_empty,
553
                        default_branches_empty => $default_branches_empty,
554
                        readonly           => $readonly
549
                    }
555
                    }
550
                );
556
                );
551
                push @subfields, $subfield_data;
557
                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/cataloguing/additem.pl (-3 / +10 lines)
Lines 509-515 my @witness_attributes = uniq map { Link Here
509
    map { defined $item->{$_} && $item->{$_} ne "" ? $_ : () } keys %$item
509
    map { defined $item->{$_} && $item->{$_} ne "" ? $_ : () } keys %$item
510
} @items;
510
} @items;
511
511
512
our ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField("items.itemnumber");
512
our ( $itemtagfield, $itemtagsubfield ) = GetMarcFromKohaField("items.itemnumber");
513
513
514
my $subfieldcode_attribute_mappings;
514
my $subfieldcode_attribute_mappings;
515
for my $subfield_code ( keys %{ $tagslib->{$itemtagfield} } ) {
515
for my $subfield_code ( keys %{ $tagslib->{$itemtagfield} } ) {
Lines 561-572 if ( $nextop eq 'additem' && $prefillitem ) { Link Here
561
    # Setting to 1 element if SubfieldsToUseWhenPrefill is empty to prevent all the subfields to be prefilled
561
    # Setting to 1 element if SubfieldsToUseWhenPrefill is empty to prevent all the subfields to be prefilled
562
    @subfields_to_prefill = split(' ', C4::Context->preference('SubfieldsToUseWhenPrefill')) || ("");
562
    @subfields_to_prefill = split(' ', C4::Context->preference('SubfieldsToUseWhenPrefill')) || ("");
563
}
563
}
564
565
# Getting list of subfields to keep when restricted editing is enabled
566
my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedEditing') : ();
567
564
my $subfields =
568
my $subfields =
565
  Koha::UI::Form::Builder::Item->new(
569
  Koha::UI::Form::Builder::Item->new(
566
    { biblionumber => $biblionumber, item => $current_item } )->edit_form(
570
    { biblionumber => $biblionumber, item => $current_item } )->edit_form(
567
    {
571
    {
568
        branchcode           => $branchcode,
572
        branchcode           => $branchcode,
569
        restricted_editition => $restrictededition,
573
        restricted_editition => $restrictededition,
574
        (
575
            @subfields_to_allow
576
            ? ( subfields_to_allow => \@subfields_to_allow )
577
            : ()
578
        ),
570
        (
579
        (
571
            @subfields_to_prefill
580
            @subfields_to_prefill
572
            ? ( subfields_to_prefill => \@subfields_to_prefill )
581
            ? ( subfields_to_prefill => \@subfields_to_prefill )
Lines 595-602 $template->param( Link Here
595
    subfields        => $subfields,
604
    subfields        => $subfields,
596
    itemnumber       => $itemnumber,
605
    itemnumber       => $itemnumber,
597
    barcode          => $current_item->{barcode},
606
    barcode          => $current_item->{barcode},
598
    itemtagfield     => $itemtagfield,
599
    itemtagsubfield  => $itemtagsubfield,
600
    op      => $nextop,
607
    op      => $nextop,
601
    popup => scalar $input->param('popup') ? 1: 0,
608
    popup => scalar $input->param('popup') ? 1: 0,
602
    C4::Search::enabled_staff_search_views,
609
    C4::Search::enabled_staff_search_views,
(-)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,resizable=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 (-21 / +24 lines)
Lines 1-4 Link Here
1
[% USE raw %]
1
[% USE raw %]
2
[% USE KohaDates %]
2
[% USE Asset %]
3
[% USE Asset %]
3
[% USE KohaDates %]
4
[% USE KohaDates %]
4
[% SET footerjs = 1 %]
5
[% SET footerjs = 1 %]
Lines 19-34 Link Here
19
    [% END -%]
20
    [% END -%]
20
[% END %]
21
[% END %]
21
[% BLOCK show_job_type %]
22
[% BLOCK show_job_type %]
22
    [% SWITCH job.type %]
23
    [% SWITCH job_type %]
23
        [% CASE "batch_biblio_record_modification" %]
24
    [% CASE 'batch_biblio_record_modification' %]
24
            Batch bibliographic record modification
25
        Batch bibliographic record modification
25
        [% CASE "batch_authority_record_modification" %]
26
    [% CASE 'batch_biblio_record_deletion' %]
26
            Batch authority record modification
27
        Batch bibliographic record record deletion
27
        [% CASE "batch_hold_cancel" %]
28
    [% CASE 'batch_authority_record_modification' %]
28
            Batch hold cancellation
29
        Batch authority record modification
29
        [% CASE # Default case %]
30
    [% CASE 'batch_authority_record_deletion' %]
30
            [% job.type | html %]
31
        Batch authority record deletion
32
    [% CASE 'batch_item_record_modification' %]
33
        Batch item record modification
34
    [% CASE 'batch_item_record_deletion' %]
35
        Batch item record deletion
36
    [% CASE "batch_hold_cancel" %]
37
        Batch hold cancellation
38
    [% CASE %]Unknown job type '[% job_type | html %]'
31
    [% END %]
39
    [% END %]
40
32
[% END %]
41
[% END %]
33
[% INCLUDE 'doc-head-open.inc' %]
42
[% INCLUDE 'doc-head-open.inc' %]
34
<title>
43
<title>
Lines 107-113 Link Here
107
            <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li>
116
            <li><label for="job_progress">Progress: </label>[% job.progress || 0 | html %] / [% job.size | html %]</li>
108
            <li>
117
            <li>
109
                <label for="job_type">Type: </label>
118
                <label for="job_type">Type: </label>
110
                [% PROCESS show_job_type %]
119
                [% PROCESS show_job_type job_type => job.type %]
111
            </li>
120
            </li>
112
            <li>
121
            <li>
113
                <label for="job_enqueued_on">Queued: </label>
122
                <label for="job_enqueued_on">Queued: </label>
Lines 164-177 Link Here
164
                    </td>
173
                    </td>
165
                    <td>[% job.progress || 0 | html %] / [% job.size | html %]</td>
174
                    <td>[% job.progress || 0 | html %] / [% job.size | html %]</td>
166
                    <td>
175
                    <td>
167
                        [% SWITCH job.type %]
176
                        [% PROCESS show_job_type job_type => job.type %]
168
                        [% CASE 'batch_biblio_record_modification' %]Batch bibliographic record modification
169
                        [% CASE 'batch_biblio_record_deletion' %]Batch bibliographic record record deletion
170
                        [% CASE 'batch_authority_record_modification' %]Batch authority record modification
171
                        [% CASE 'batch_authority_record_deletion' %]Batch authority record deletion
172
                        [% CASE "batch_hold_cancel" %]Batch hold cancellation
173
                        [% CASE %][% job.type | html %]
174
                        [% END %]
175
                    </td>
177
                    </td>
176
                    <td>[% job.enqueued_on | $KohaDates with_hours = 1 %]</td>
178
                    <td>[% job.enqueued_on | $KohaDates with_hours = 1 %]</td>
177
                    <td>[% job.started_on| $KohaDates with_hours = 1 %]</td>
179
                    <td>[% job.started_on| $KohaDates with_hours = 1 %]</td>
Lines 217-226 Link Here
217
                "sPaginationType": "full_numbers"
219
                "sPaginationType": "full_numbers"
218
            }));
220
            }));
219
221
220
            [% IF op == 'view' %]
221
                [% PROCESS 'js' %]
222
            [% END %]
223
        });
222
        });
224
    </script>
223
    </script>
224
    [% IF op == 'view' %]
225
        [% PROCESS 'js' %]
226
    [% END %]
225
[% END %]
227
[% END %]
228
226
[% INCLUDE 'intranet-bottom.inc' %]
229
[% INCLUDE 'intranet-bottom.inc' %]
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/cataloguing/additem.tt (-2 lines)
Lines 184-191 Link Here
184
    </fieldset>
184
    </fieldset>
185
185
186
    [% ELSE %]
186
    [% ELSE %]
187
    <input type="hidden" name="tag" value="[% itemtagfield | html %]" />
188
    <input type="hidden" name="subfield" value="[% itemtagsubfield | html %]" />
189
    [% IF op != 'add_item' %]
187
    [% IF op != 'add_item' %]
190
        <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />
188
        <input type="hidden" name="itemnumber" value="[% itemnumber | html %]" />
191
    [% END %]
189
    [% 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 / +21 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-100 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
}
91
106
92
function hideAllColumns() {
107
function hideAllColumns() {
108
    var nb_cols = guess_nb_cols();
93
    $("#selections input:checkbox").each(function () {
109
    $("#selections input:checkbox").each(function () {
94
        $(this).prop("checked", false);
110
        $(this).prop("checked", false);
95
    });
111
    });
96
    $("#selections span").removeClass("selected");
112
    $("#selections span").removeClass("selected");
97
    $("#itemst td:nth-child(3),#itemst th:nth-child(3)").nextAll().hide();
113
    $("#itemst td:nth-child("+nb_cols+"),#itemst tr th:nth-child("+nb_cols+")").nextAll().hide();
98
    $("#hideall").prop("checked", true).parent().addClass("selected");
114
    $("#hideall").prop("checked", true).parent().addClass("selected");
99
    var cookieString = allColumns.join("/");
115
    var cookieString = allColumns.join("/");
100
    Cookies.set("showColumns", cookieString, { expires: date, path: '/' });
116
    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
    batch_hold_cancel
38
    batch_hold_cancel
37
);
39
);
38
40
(-)a/t/db_dependent/Koha/Item.t (-5 / +17 lines)
Lines 109-119 subtest 'has_pending_hold() tests' => sub { Link Here
109
subtest "as_marc_field() tests" => sub {
109
subtest "as_marc_field() tests" => sub {
110
110
111
    my $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
111
    my $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
112
    my ( $itemtag, $itemtagsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
112
113
113
    my @schema_columns = $schema->resultset('Item')->result_source->columns;
114
    my @schema_columns = $schema->resultset('Item')->result_source->columns;
114
    my @mapped_columns = grep { exists $mss->{'items.'.$_} } @schema_columns;
115
    my @mapped_columns = grep { exists $mss->{'items.'.$_} } @schema_columns;
115
116
116
    plan tests => 2 * (scalar @mapped_columns + 1) + 2;
117
    plan tests => 2 * (scalar @mapped_columns + 1) + 3;
117
118
118
    $schema->storage->txn_begin;
119
    $schema->storage->txn_begin;
119
120
Lines 126-132 subtest "as_marc_field() tests" => sub { Link Here
126
127
127
    is(
128
    is(
128
        $marc_field->tag,
129
        $marc_field->tag,
129
        $mss->{'items.itemnumber'}[0]->{tagfield},
130
        $itemtag,
130
        'Generated field set the right tag number'
131
        'Generated field set the right tag number'
131
    );
132
    );
132
133
Lines 141-147 subtest "as_marc_field() tests" => sub { Link Here
141
142
142
    is(
143
    is(
143
        $marc_field->tag,
144
        $marc_field->tag,
144
        $mss->{'items.itemnumber'}[0]->{tagfield},
145
        $itemtag,
145
        'Generated field set the right tag number'
146
        'Generated field set the right tag number'
146
    );
147
    );
147
148
Lines 154-178 subtest "as_marc_field() tests" => sub { Link Here
154
    my $unmapped_subfield = Koha::MarcSubfieldStructure->new(
155
    my $unmapped_subfield = Koha::MarcSubfieldStructure->new(
155
        {
156
        {
156
            frameworkcode => '',
157
            frameworkcode => '',
157
            tagfield      => $mss->{'items.itemnumber'}[0]->{tagfield},
158
            tagfield      => $itemtag,
158
            tagsubfield   => 'X',
159
            tagsubfield   => 'X',
159
        }
160
        }
160
    )->store;
161
    )->store;
161
162
162
    $mss = C4::Biblio::GetMarcSubfieldStructure( '' );
163
    my @unlinked_subfields;
163
    my @unlinked_subfields;
164
    push @unlinked_subfields, X => 'Something weird';
164
    push @unlinked_subfields, X => 'Something weird';
165
    $item->more_subfields_xml( C4::Items::_get_unlinked_subfields_xml( \@unlinked_subfields ) )->store;
165
    $item->more_subfields_xml( C4::Items::_get_unlinked_subfields_xml( \@unlinked_subfields ) )->store;
166
166
167
    Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
168
    Koha::MarcSubfieldStructures->search(
169
        { frameworkcode => '', tagfield => $itemtag } )
170
      ->update( { display_order => \['FLOOR( 1 + RAND( ) * 10 )'] } );
171
167
    $marc_field = $item->as_marc_field;
172
    $marc_field = $item->as_marc_field;
168
173
174
    my $tagslib = C4::Biblio::GetMarcStructure(1, '');
169
    my @subfields = $marc_field->subfields;
175
    my @subfields = $marc_field->subfields;
170
    my $result = all { defined $_->[1] } @subfields;
176
    my $result = all { defined $_->[1] } @subfields;
171
    ok( $result, 'There are no undef subfields' );
177
    ok( $result, 'There are no undef subfields' );
178
    my @ordered_subfields = sort {
179
            $tagslib->{$itemtag}->{ $a->[0] }->{display_order}
180
        <=> $tagslib->{$itemtag}->{ $b->[0] }->{display_order}
181
    } @subfields;
182
    is_deeply(\@subfields, \@ordered_subfields);
172
183
173
    is( scalar $marc_field->subfield('X'), 'Something weird', 'more_subfield_xml is considered' );
184
    is( scalar $marc_field->subfield('X'), 'Something weird', 'more_subfield_xml is considered' );
174
185
175
    $schema->storage->txn_rollback;
186
    $schema->storage->txn_rollback;
187
    Koha::Caches->get_instance->clear_from_cache( "MarcStructure-1-" );
176
};
188
};
177
189
178
subtest 'pickup_locations' => sub {
190
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 (-423 / +114 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(
27
use C4::Circulation qw( barcodedecode );
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( barcodedecode LostItem IsItemIssued );
37
use C4::Context;
28
use C4::Context;
38
use C4::Koha;
39
use C4::BackgroundJob;
40
use C4::ClassSource qw( GetClassSources GetClassSource );
41
use MARC::File::XML;
29
use MARC::File::XML;
42
use List::MoreUtils qw( uniq );
30
use List::MoreUtils qw( uniq );
31
use Encode qw( encode_utf8 );
43
32
44
use Koha::Database;
33
use Koha::Database;
34
use Koha::DateUtils qw( dt_from_string );
45
use Koha::Exceptions::Exception;
35
use Koha::Exceptions::Exception;
46
use Koha::AuthorisedValues;
47
use Koha::Biblios;
36
use Koha::Biblios;
48
use Koha::DateUtils qw( dt_from_string );
49
use Koha::Items;
37
use Koha::Items;
50
use Koha::ItemTypes;
51
use Koha::Patrons;
38
use Koha::Patrons;
52
use Koha::SearchEngine::Indexer;
39
use Koha::Item::Attributes;
40
use Koha::BackgroundJob::BatchDeleteItem;
41
use Koha::BackgroundJob::BatchUpdateItem;
53
use Koha::UI::Form::Builder::Item;
42
use Koha::UI::Form::Builder::Item;
43
use Koha::UI::Table::Builder::Items;
54
44
55
my $input = CGI->new;
45
my $input = CGI->new;
56
my $dbh = C4::Context->dbh;
46
my $dbh = C4::Context->dbh;
Lines 90-342 my $restrictededition = $uid ? haspermission($uid, {'tools' => 'items_batchmod_ Link Here
90
# In case user is a superlibrarian, edition is not restricted
80
# In case user is a superlibrarian, edition is not restricted
91
$restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian());
81
$restrictededition = 0 if ($restrictededition != 0 && C4::Context->IsSuperLibrarian());
92
82
93
$template->param(del       => $del);
94
95
my $nextop="";
83
my $nextop="";
96
my @errors; # store errors found while checking data BEFORE saving item.
84
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
85
107
my %cookies = parse CGI::Cookie($cookie);
86
my %cookies = parse CGI::Cookie($cookie);
108
my $sessionID = $cookies{'CGISESSID'}->value;
87
my $sessionID = $cookies{'CGISESSID'}->value;
109
88
89
my @messages;
110
90
111
#--- ----------------------------------------------------------------------------
91
if ( $op eq "action" ) {
112
if ($op eq "action") {
92
113
#-------------------------------------------------------------------------------
93
    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
121
    my $upd_biblionumbers;
122
    my $del_biblionumbers;
123
    if ( $del ) {
124
        try {
94
        try {
125
            my $schema = Koha::Database->new->schema;
95
            my $params = {
126
            $schema->txn_do(
96
                record_ids     => \@itemnumbers,
127
                sub {
97
                delete_biblios => $del_records,
128
                    foreach my $itemnumber (@itemnumbers) {
98
            };
129
                        my $item = Koha::Items->find($itemnumber);
99
            my $job_id =
130
                        next
100
              Koha::BackgroundJob::BatchDeleteItem->new->enqueue($params);
131
                          unless $item
101
            $nextop = 'enqueued';
132
                          ; # Should have been tested earlier, but just in case...
102
            $template->param( job_id => $job_id, );
133
                        my $itemdata = $item->unblessed;
134
                        my $return = $item->safe_delete;
135
                        if ( ref( $return ) ) {
136
                            $deleted_items++;
137
                            push @$upd_biblionumbers, $itemdata->{'biblionumber'};
138
                        }
139
                        else {
140
                            $not_deleted_items++;
141
                            push @not_deleted,
142
                              {
143
                                biblionumber => $itemdata->{'biblionumber'},
144
                                itemnumber   => $itemdata->{'itemnumber'},
145
                                barcode      => $itemdata->{'barcode'},
146
                                title        => $itemdata->{'title'},
147
                                reason       => $return,
148
                              };
149
                        }
150
151
                        # If there are no items left, delete the biblio
152
                        if ($del_records) {
153
                            my $itemscount = Koha::Biblios->find( $itemdata->{'biblionumber'} )->items->count;
154
                            if ( $itemscount == 0 ) {
155
                                my $error = DelBiblio( $itemdata->{'biblionumber'}, { skip_record_index => 1 } );
156
                                unless ($error) {
157
                                    $deleted_records++;
158
                                    push @$del_biblionumbers, $itemdata->{'biblionumber'};
159
                                    if ( $src eq 'CATALOGUING' ) {
160
                                        # We are coming catalogue/detail.pl, there were items from a single bib record
161
                                        $template->param( biblio_deleted => 1 );
162
                                    }
163
                                }
164
                            }
165
                        }
166
                    }
167
                    if (@not_deleted) {
168
                        Koha::Exceptions::Exception->throw(
169
                            'Some items have not been deleted, rolling back');
170
                    }
171
                }
172
            );
173
        }
103
        }
174
        catch {
104
        catch {
175
            warn $_;
105
            warn $_;
176
            if ( $_->isa('Koha::Exceptions::Exception') ) {
106
            push @messages,
177
                $template->param( deletion_failed => 1 );
107
              {
178
            }
108
                type  => 'error',
179
            die "Something terrible has happened!"
109
                code  => 'cannot_enqueue_job',
180
                if ($_ =~ /Rollback failed/); # Rollback failed
110
                error => $_,
111
              };
112
            $template->param( view => 'errors' );
181
        };
113
        };
182
    }
114
    }
183
115
184
    else { # modification
116
    else {    # modification
185
117
186
        my @columns = Koha::Items->columns;
118
        my @item_columns = Koha::Items->columns;
187
119
188
        my $new_item_data;
120
        my $new_item_data;
189
        my @columns_with_regex;
121
        my ( $columns_with_regex );
190
        for my $c ( @columns ) {
122
        my @subfields_to_blank = $input->multi_param('disable_input');
191
            if ( $c eq 'more_subfields_xml' ) {
123
        my @more_subfields = $input->multi_param("items.more_subfields_xml");
192
                my @more_subfields_xml = $input->multi_param("items.more_subfields_xml");
124
        for my $item_column (@item_columns) {
193
                my @unlinked_item_subfields;
125
            my @attributes       = ($item_column);
194
                for my $subfield ( @more_subfields_xml ) {
126
            my $cgi_param_prefix = 'items.';
195
                    my $v = $input->param('items.more_subfields_xml_' . $subfield);
127
            if ( $item_column eq 'more_subfields_xml' ) {
196
                    push @unlinked_item_subfields, $subfield, $v;
128
                @attributes       = ();
197
                }
129
                $cgi_param_prefix = 'items.more_subfields_xml_';
198
                if ( @unlinked_item_subfields ) {
130
                for my $subfield (@more_subfields) {
199
                    my $marc = MARC::Record->new();
131
                    push @attributes, $subfield;
200
                    # use of tag 999 is arbitrary, and doesn't need to match the item tag
201
                    # used in the framework
202
                    $marc->append_fields(MARC::Field->new('999', ' ', ' ', @unlinked_item_subfields));
203
                    $marc->encoding("UTF-8");
204
                    # FIXME This is WRONG! We need to use the values that haven't been modified by the batch tool!
205
                    $new_item_data->{more_subfields_xml} = $marc->as_xml("USMARC");
206
                    next;
207
                }
132
                }
208
                $new_item_data->{more_subfields_xml} = undef;
133
            }
209
                # FIXME deal with more_subfields_xml and @subfields_to_blank
210
            } elsif ( grep { $c eq $_ } @subfields_to_blank ) {
211
                # Empty this column
212
                $new_item_data->{$c} = undef
213
            } else {
214
134
215
                my @v = grep { $_ ne "" }
135
            for my $attr (@attributes) {
216
                    uniq $input->multi_param( "items." . $c );
217
136
218
                next unless @v;
137
                my $cgi_var_name = $cgi_param_prefix
138
                  . encode_utf8($attr)
139
                  ;  # We need to deal correctly with encoding on subfield codes
219
140
220
                $new_item_data->{$c} = join ' | ', @v;
141
                if ( grep { $attr eq $_ } @subfields_to_blank ) {
221
            }
142
143
                    # Empty this column
144
                    $new_item_data->{$attr} = undef;
145
                }
146
                elsif ( my $regex_search =
147
                    $input->param( $cgi_var_name . '_regex_search' ) )
148
                {
149
                    $columns_with_regex->{$attr} = {
150
                        search => $regex_search,
151
                        replace =>
152
                          $input->param( $cgi_var_name . '_regex_replace' ),
153
                        modifiers =>
154
                          $input->param( $cgi_var_name . '_regex_modifiers' )
155
                    };
156
                }
157
                else {
158
                    my @v =
159
                      grep { $_ ne "" } uniq $input->multi_param($cgi_var_name);
222
160
223
            if ( my $regex_search = $input->param('items.'.$c.'_regex_search') ) {
161
                    next unless @v;
224
                push @columns_with_regex, $c;
162
163
                    $new_item_data->{$attr} = join '|', @v;
164
                }
225
            }
165
            }
226
        }
166
        }
227
167
168
        my $params = {
169
            record_ids                        => \@itemnumbers,
170
            regex_mod                         => $columns_with_regex,
171
            new_values                        => $new_item_data,
172
            exclude_from_local_holds_priority => (
173
                defined $exclude_from_local_holds_priority
174
                  && $exclude_from_local_holds_priority ne ""
175
              )
176
            ? $exclude_from_local_holds_priority
177
            : undef,
178
179
        };
228
        try {
180
        try {
229
            my $schema = Koha::Database->new->schema;
181
            my $job_id =
230
            $schema->txn_do(
182
              Koha::BackgroundJob::BatchUpdateItem->new->enqueue($params);
231
                sub {
183
            $nextop = 'enqueued';
232
184
            $template->param( job_id => $job_id, );
233
                    foreach my $itemnumber (@itemnumbers) {
234
                        my $item = Koha::Items->find($itemnumber);
235
                        next
236
                          unless $item
237
                          ; # Should have been tested earlier, but just in case...
238
                        my $itemdata = $item->unblessed;
239
240
                        my $modified_holds_priority = 0;
241
                        if ( defined $exclude_from_local_holds_priority && $exclude_from_local_holds_priority ne "" ) {
242
                            if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) {
243
                                $item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store;
244
                                $modified_holds_priority = 1;
245
                            }
246
                        }
247
248
                        my $modified = 0;
249
                        for my $c ( @columns_with_regex ) {
250
                            my $regex_search = $input->param('items.'.$c.'_regex_search');
251
                            my $old_value = $item->$c;
252
253
                            my $value = apply_regex(
254
                                {
255
                                    search  => $regex_search,
256
                                    replace => $input->param(
257
                                        'items' . $c . '_regex_replace'
258
                                    ),
259
                                    modifiers => $input->param(
260
                                        'items' . $c . '_regex_modifiers'
261
                                    ),
262
                                    value => $old_value,
263
                                }
264
                            );
265
                            unless ( $old_value eq $value ) {
266
                                $modified++;
267
                                $item->$c($value);
268
                            }
269
                        }
270
271
                        $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?
272
                        if ( $modified) {
273
                            my $itemlost_pre = $item->itemlost;
274
                            $item->set($new_item_data)->store({skip_record_index => 1});
275
276
                            push @$upd_biblionumbers, $itemdata->{'biblionumber'};
277
278
                            LostItem(
279
                                $item->itemnumber, 'batchmod', undef,
280
                                { skip_record_index => 1 }
281
                            ) if $item->itemlost
282
                                  and not $itemlost_pre;
283
                        }
284
285
                        $modified_items++ if $modified || $modified_holds_priority;
286
                        $modified_fields += $modified + $modified_holds_priority;
287
                    }
288
                }
289
            );
290
        }
185
        }
291
        catch {
186
        catch {
292
            warn $_;
187
            push @messages,
293
            die "Something terrible has happened!"
188
              {
294
                if ($_ =~ /Rollback failed/); # Rollback failed
189
                type  => 'error',
190
                code  => 'cannot_enqueue_job',
191
                error => $_,
192
              };
193
            $template->param( view => 'errors' );
295
        };
194
        };
296
    }
195
    }
297
196
298
    $upd_biblionumbers = [ uniq @$upd_biblionumbers ]; # Only update each bib once
299
300
    # Don't send specialUpdate for records we are going to delete
301
    my %del_bib_hash = map{ $_ => undef } @$del_biblionumbers;
302
    @$upd_biblionumbers = grep( ! exists( $del_bib_hash{$_} ), @$upd_biblionumbers );
303
304
    my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
305
    $indexer->index_records( $upd_biblionumbers, 'specialUpdate', "biblioserver", undef ) if @$upd_biblionumbers;
306
    $indexer->index_records( $del_biblionumbers, 'recordDelete', "biblioserver", undef ) if @$del_biblionumbers;
307
308
    # Once the job is done
309
    # If we have a reasonable amount of items, we display them
310
    my $max_items = $del ? C4::Context->preference("MaxItemsToDisplayForBatchDel") : C4::Context->preference("MaxItemsToDisplayForBatchMod");
311
    if (scalar(@itemnumbers) <= $max_items ){
312
        if (scalar(@itemnumbers) <= 1000 ) {
313
            $items_display_hashref=BuildItemsData(@itemnumbers);
314
        } else {
315
            # Else, we only display the barcode
316
            my @simple_items_display = map {
317
                my $itemnumber = $_;
318
                my $item = Koha::Items->find($itemnumber);
319
                {
320
                    itemnumber   => $itemnumber,
321
                    barcode      => $item ? ( $item->barcode // q{} ) : q{},
322
                    biblionumber => $item ? $item->biblio->biblionumber : q{},
323
                };
324
            } @itemnumbers;
325
            $template->param("simple_items_display" => \@simple_items_display);
326
        }
327
    } else {
328
        $template->param( "too_many_items_display" => scalar(@itemnumbers) );
329
        $template->param( "job_completed" => 1 );
330
    }
331
332
333
    # Calling the template
334
    $template->param(
335
        modified_items => $modified_items,
336
        modified_fields => $modified_fields,
337
    );
338
339
}
197
}
198
199
$template->param(
200
    messages => \@messages,
201
);
340
#
202
#
341
#-------------------------------------------------------------------------------
203
#-------------------------------------------------------------------------------
342
# build screen with existing items. and "new" one
204
# build screen with existing items. and "new" one
Lines 372-381 if ($op eq "show"){ Link Here
372
        }
234
        }
373
    } else {
235
    } else {
374
        if (defined $biblionumber && !@itemnumbers){
236
        if (defined $biblionumber && !@itemnumbers){
375
            my @all_items = GetItemsInfo( $biblionumber );
237
            my $biblio = Koha::Biblios->find($biblionumber);
376
            foreach my $itm (@all_items) {
238
            @itemnumbers = $biblio ? $biblio->items->get_column('itemnumber') : ();
377
                push @itemnumbers, $itm->{itemnumber};
378
            }
379
        }
239
        }
380
        if ( my $list = $input->param('barcodelist') ) {
240
        if ( my $list = $input->param('barcodelist') ) {
381
            my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list );
241
            my @barcodelist = grep /\S/, ( split /[$split_chars]/, $list );
Lines 398-404 if ($op eq "show"){ Link Here
398
        : C4::Context->preference("MaxItemsToDisplayForBatchMod");
258
        : C4::Context->preference("MaxItemsToDisplayForBatchMod");
399
    $template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod");
259
    $template->param("too_many_items_process" => scalar(@itemnumbers)) if !$del && scalar(@itemnumbers) > C4::Context->preference("MaxItemsToProcessForBatchMod");
400
    if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) {
260
    if (scalar(@itemnumbers) <= ( $max_display_items // 1000 ) ) {
401
        $items_display_hashref=BuildItemsData(@itemnumbers);
261
        $display_items = 1;
402
    } else {
262
    } else {
403
        $template->param("too_many_items_display" => scalar(@itemnumbers));
263
        $template->param("too_many_items_display" => scalar(@itemnumbers));
404
        # Even if we do not display the items, we need the itemnumbers
264
        # Even if we do not display the items, we need the itemnumbers
Lines 406-415 if ($op eq "show"){ Link Here
406
    }
266
    }
407
267
408
    # now, build the item form for entering a new item
268
    # now, build the item form for entering a new item
409
    my @loop_data =();
410
    my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
411
412
    my $pref_itemcallnumber = C4::Context->preference('itemcallnumber');
413
269
414
    # Getting list of subfields to keep when restricted batchmod edit is enabled
270
    # Getting list of subfields to keep when restricted batchmod edit is enabled
415
    my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod') : ();
271
    my @subfields_to_allow = $restrictededition ? split ' ', C4::Context->preference('SubfieldsToAllowForRestrictedBatchmod') : ();
Lines 422-428 if ($op eq "show"){ Link Here
422
                ? ( subfields_to_allow => \@subfields_to_allow )
278
                ? ( subfields_to_allow => \@subfields_to_allow )
423
                : ()
279
                : ()
424
            ),
280
            ),
425
            subfields_to_ignore         => ['items.barcode'],
281
            ignore_not_allowed_subfields => 1,
282
            kohafields_to_ignore         => ['items.barcode'],
426
            prefill_with_default_values => $use_default_values,
283
            prefill_with_default_values => $use_default_values,
427
            default_branches_empty      => 1,
284
            default_branches_empty      => 1,
428
        }
285
        }
Lines 437-623 if ($op eq "show"){ Link Here
437
    $nextop="action"
294
    $nextop="action"
438
} # -- End action="show"
295
} # -- End action="show"
439
296
440
$template->param(%$items_display_hashref) if $items_display_hashref;
297
if ( $display_items ) {
441
$template->param(
298
    my $items_table =
442
    op      => $nextop,
299
      Koha::UI::Table::Builder::Items->new( { itemnumbers => \@itemnumbers } )
443
);
300
      ->build_table;
444
$template->param( $op => 1 ) if $op;
445
446
if ($op eq "action") {
447
448
    #my @not_deleted_loop = map{{itemnumber=>$_}}@not_deleted;
449
450
    $template->param(
301
    $template->param(
451
	not_deleted_items => $not_deleted_items,
302
        items        => $items_table->{items},
452
	deleted_items => $deleted_items,
303
        item_header_loop => $items_table->{headers},
453
	delete_records => $del_records,
454
	deleted_records => $deleted_records,
455
	not_deleted_loop => \@not_deleted 
456
    );
304
    );
457
}
305
}
458
306
459
foreach my $error (@errors) {
307
$template->param(
460
    $template->param($error => 1) if $error;
308
    op  => $nextop,
461
}
309
    del => $del,
462
$template->param(src => $src);
310
    ( $op ? ( $op => 1 ) : () ),
463
$template->param(biblionumber => $biblionumber);
311
    src          => $src,
464
output_html_with_http_headers $input, $cookie, $template->output;
312
    biblionumber => $biblionumber,
465
exit;
313
);
466
467
468
# ---------------- Functions
469
470
sub BuildItemsData{
471
	my @itemnumbers=@_;
472
		# now, build existiing item list
473
		my %witness; #---- stores the list of subfields used at least once, with the "meaning" of the code
474
		my @big_array;
475
		#---- finds where items.itemnumber is stored
476
    my (  $itemtagfield,   $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
477
    my ($branchtagfield, $branchtagsubfield) = &GetMarcFromKohaField( "items.homebranch" );
478
		foreach my $itemnumber (@itemnumbers){
479
            my $itemdata = Koha::Items->find($itemnumber);
480
            next unless $itemdata; # Should have been tested earlier, but just in case...
481
            $itemdata = $itemdata->unblessed;
482
			my $itemmarc=Item2Marc($itemdata);
483
			my %this_row;
484
			foreach my $field (grep {$_->tag() eq $itemtagfield} $itemmarc->fields()) {
485
				# loop through each subfield
486
				my $itembranchcode=$field->subfield($branchtagsubfield);
487
                if ($itembranchcode && C4::Context->preference("IndependentBranches")) {
488
						#verifying rights
489
						my $userenv = C4::Context->userenv();
490
                        unless (C4::Context->IsSuperLibrarian() or (($userenv->{'branch'} eq $itembranchcode))){
491
								$this_row{'nomod'}=1;
492
						}
493
				}
494
				my $tag=$field->tag();
495
				foreach my $subfield ($field->subfields) {
496
					my ($subfcode,$subfvalue)=@$subfield;
497
					next if ($tagslib->{$tag}->{$subfcode}->{tab} ne 10 
498
							&& $tag        ne $itemtagfield 
499
							&& $subfcode   ne $itemtagsubfield);
500
501
					$witness{$subfcode} = $tagslib->{$tag}->{$subfcode}->{lib} if ($tagslib->{$tag}->{$subfcode}->{tab}  eq 10);
502
					if ($tagslib->{$tag}->{$subfcode}->{tab}  eq 10) {
503
						$this_row{$subfcode}=GetAuthorisedValueDesc( $tag,
504
									$subfcode, $subfvalue, '', $tagslib) 
505
									|| $subfvalue;
506
					}
507
508
					$this_row{itemnumber} = $subfvalue if ($tag eq $itemtagfield && $subfcode eq $itemtagsubfield);
509
				}
510
			}
511
512
            # grab title, author, and ISBN to identify bib that the item
513
            # belongs to in the display
514
            my $biblio = Koha::Biblios->find( $itemdata->{biblionumber} );
515
            $this_row{title}        = $biblio->title;
516
            $this_row{author}       = $biblio->author;
517
            $this_row{isbn}         = $biblio->biblioitem->isbn;
518
            $this_row{biblionumber} = $biblio->biblionumber;
519
            $this_row{holds}        = $biblio->holds->count;
520
            $this_row{item_holds}   = Koha::Holds->search( { itemnumber => $itemnumber } )->count;
521
            $this_row{item}         = Koha::Items->find($itemnumber);
522
523
			if (%this_row) {
524
				push(@big_array, \%this_row);
525
			}
526
		}
527
		@big_array = sort {$a->{0} cmp $b->{0}} @big_array;
528
529
		# now, construct template !
530
		# First, the existing items for display
531
		my @item_value_loop;
532
		my @witnesscodessorted=sort keys %witness;
533
		for my $row ( @big_array ) {
534
			my %row_data;
535
			my @item_fields = map +{ field => $_ || '' }, @$row{ @witnesscodessorted };
536
			$row_data{item_value} = [ @item_fields ];
537
			$row_data{itemnumber} = $row->{itemnumber};
538
			#reporting this_row values
539
			$row_data{'nomod'} = $row->{'nomod'};
540
      $row_data{bibinfo} = $row->{bibinfo};
541
      $row_data{author} = $row->{author};
542
      $row_data{title} = $row->{title};
543
      $row_data{isbn} = $row->{isbn};
544
      $row_data{biblionumber} = $row->{biblionumber};
545
      $row_data{holds}        = $row->{holds};
546
      $row_data{item_holds}   = $row->{item_holds};
547
      $row_data{item}         = $row->{item};
548
      $row_data{safe_to_delete} = $row->{item}->safe_to_delete;
549
      my $is_on_loan = C4::Circulation::IsItemIssued( $row->{itemnumber} );
550
      $row_data{onloan} = $is_on_loan ? 1 : 0;
551
			push(@item_value_loop,\%row_data);
552
		}
553
		my @header_loop=map { { header_value=> $witness{$_}} } @witnesscodessorted;
554
555
    my @cannot_be_deleted = map {
556
        $_->{safe_to_delete} == 1 ? () : $_->{item}->barcode
557
    } @item_value_loop;
558
    return {
559
        item_loop        => \@item_value_loop,
560
        cannot_be_deleted => \@cannot_be_deleted,
561
        item_header_loop => \@header_loop
562
    };
563
}
564
565
#BE WARN : it is not the general case 
566
# This function can be OK in the item marc record special case
567
# Where subfield is not repeated
568
# And where we are sure that field should correspond
569
# And $tag>10
570
sub UpdateMarcWith {
571
  my ($marcfrom,$marcto)=@_;
572
    my (  $itemtag,   $itemtagsubfield) = &GetMarcFromKohaField( "items.itemnumber" );
573
    my $fieldfrom=$marcfrom->field($itemtag);
574
    my @fields_to=$marcto->field($itemtag);
575
    my $modified = 0;
576
577
    return $modified unless $fieldfrom;
578
579
    foreach my $subfield ( $fieldfrom->subfields() ) {
580
        foreach my $field_to_update ( @fields_to ) {
581
            if ( $subfield->[1] ) {
582
                unless ( $field_to_update->subfield($subfield->[0]) eq $subfield->[1] ) {
583
                    $modified++;
584
                    $field_to_update->update( $subfield->[0] => $subfield->[1] );
585
                }
586
            }
587
            else {
588
                $modified++;
589
                $field_to_update->delete_subfield( code => $subfield->[0] );
590
            }
591
        }
592
    }
593
    return $modified;
594
}
595
596
sub apply_regex {
597
    my ($params) = @_;
598
    my $search   = $params->{search};
599
    my $replace  = $params->{replace};
600
    my $modifiers = $params->{modifiers} || [];
601
    my $value = $params->{value};
602
603
    my @available_modifiers = qw( i g );
604
    my $retained_modifiers  = q||;
605
    for my $modifier ( split //, @$modifiers ) {
606
        $retained_modifiers .= $modifier
607
          if grep { /$modifier/ } @available_modifiers;
608
    }
609
    if ( $retained_modifiers =~ m/^(ig|gi)$/ ) {
610
        $value =~ s/$search/$replace/ig;
611
    }
612
    elsif ( $retained_modifiers eq 'i' ) {
613
        $value =~ s/$search/$replace/i;
614
    }
615
    elsif ( $retained_modifiers eq 'g' ) {
616
        $value =~ s/$search/$replace/g;
617
    }
618
    else {
619
        $value =~ s/$search/$replace/;
620
    }
621
314
622
    return $value;
315
output_html_with_http_headers $input, $cookie, $template->output;
623
}
624
- 

Return to bug 28445