Bug 23463: Fix CancelReceipt.t
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 use vars qw(@ISA @EXPORT);
24 BEGIN {
25     require Exporter;
26     @ISA = qw(Exporter);
27
28     @EXPORT = qw(
29         AddItemFromMarc
30         AddItemBatchFromMarc
31         ModItemFromMarc
32         Item2Marc
33         ModDateLastSeen
34         ModItemTransfer
35         CheckItemPreSave
36         GetItemsForInventory
37         GetItemsInfo
38         GetItemsLocationInfo
39         GetHostItemsInfo
40         get_hostitemnumbers_of
41         GetHiddenItemnumbers
42         MoveItemFromBiblio
43         CartToShelf
44         GetAnalyticsCount
45         SearchItems
46         PrepareItemrecordDisplay
47     );
48 }
49
50 use Carp;
51 use Try::Tiny;
52 use C4::Context;
53 use C4::Koha;
54 use C4::Biblio;
55 use Koha::DateUtils;
56 use MARC::Record;
57 use C4::ClassSource;
58 use C4::Log;
59 use List::MoreUtils qw(any);
60 use YAML qw(Load);
61 use DateTime::Format::MySQL;
62 use Data::Dumper; # used as part of logging item record changes, not just for
63                   # debugging; so please don't remove this
64
65 use Koha::AuthorisedValues;
66 use Koha::DateUtils qw(dt_from_string);
67 use Koha::Database;
68
69 use Koha::Biblioitems;
70 use Koha::Items;
71 use Koha::ItemTypes;
72 use Koha::SearchEngine;
73 use Koha::SearchEngine::Search;
74 use Koha::Libraries;
75
76 =head1 NAME
77
78 C4::Items - item management functions
79
80 =head1 DESCRIPTION
81
82 This module contains an API for manipulating item 
83 records in Koha, and is used by cataloguing, circulation,
84 acquisitions, and serials management.
85
86 # FIXME This POD is not up-to-date
87 A Koha item record is stored in two places: the
88 items table and embedded in a MARC tag in the XML
89 version of the associated bib record in C<biblioitems.marcxml>.
90 This is done to allow the item information to be readily
91 indexed (e.g., by Zebra), but means that each item
92 modification transaction must keep the items table
93 and the MARC XML in sync at all times.
94
95 The items table will be considered authoritative.  In other
96 words, if there is ever a discrepancy between the items
97 table and the MARC XML, the items table should be considered
98 accurate.
99
100 =head1 HISTORICAL NOTE
101
102 Most of the functions in C<C4::Items> were originally in
103 the C<C4::Biblio> module.
104
105 =head1 CORE EXPORTED FUNCTIONS
106
107 The following functions are meant for use by users
108 of C<C4::Items>
109
110 =cut
111
112 =head2 CartToShelf
113
114   CartToShelf($itemnumber);
115
116 Set the current shelving location of the item record
117 to its stored permanent shelving location.  This is
118 primarily used to indicate when an item whose current
119 location is a special processing ('PROC') or shelving cart
120 ('CART') location is back in the stacks.
121
122 =cut
123
124 sub CartToShelf {
125     my ( $itemnumber ) = @_;
126
127     unless ( $itemnumber ) {
128         croak "FAILED CartToShelf() - no itemnumber supplied";
129     }
130
131     my $item = Koha::Items->find($itemnumber);
132     if ( $item->location eq 'CART' ) {
133         $item->location($item->permanent_location)->store;
134     }
135 }
136
137 =head2 AddItemFromMarc
138
139   my ($biblionumber, $biblioitemnumber, $itemnumber) 
140       = AddItemFromMarc($source_item_marc, $biblionumber);
141
142 Given a MARC::Record object containing an embedded item
143 record and a biblionumber, create a new item record.
144
145 =cut
146
147 sub AddItemFromMarc {
148     my ( $source_item_marc, $biblionumber ) = @_;
149     my $dbh = C4::Context->dbh;
150
151     # parse item hash from MARC
152     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
153     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
154
155     my $localitemmarc = MARC::Record->new;
156     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
157
158     my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
159     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
160     $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
161     $item_values->{biblionumber} = $biblionumber;
162     my $item = Koha::Item->new( $item_values )->store;
163     return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
164 }
165
166 =head2 AddItemBatchFromMarc
167
168   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
169              $biblionumber, $biblioitemnumber, $frameworkcode);
170
171 Efficiently create item records from a MARC biblio record with
172 embedded item fields.  This routine is suitable for batch jobs.
173
174 This API assumes that the bib record has already been
175 saved to the C<biblio> and C<biblioitems> tables.  It does
176 not expect that C<biblio_metadata.metadata> is populated, but it
177 will do so via a call to ModBibiloMarc.
178
179 The goal of this API is to have a similar effect to using AddBiblio
180 and AddItems in succession, but without inefficient repeated
181 parsing of the MARC XML bib record.
182
183 This function returns an arrayref of new itemsnumbers and an arrayref of item
184 errors encountered during the processing.  Each entry in the errors
185 list is a hashref containing the following keys:
186
187 =over
188
189 =item item_sequence
190
191 Sequence number of original item tag in the MARC record.
192
193 =item item_barcode
194
195 Item barcode, provide to assist in the construction of
196 useful error messages.
197
198 =item error_code
199
200 Code representing the error condition.  Can be 'duplicate_barcode',
201 'invalid_homebranch', or 'invalid_holdingbranch'.
202
203 =item error_information
204
205 Additional information appropriate to the error condition.
206
207 =back
208
209 =cut
210
211 sub AddItemBatchFromMarc {
212     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
213     my $error;
214     my @itemnumbers = ();
215     my @errors = ();
216     my $dbh = C4::Context->dbh;
217
218     # We modify the record, so lets work on a clone so we don't change the
219     # original.
220     $record = $record->clone();
221     # loop through the item tags and start creating items
222     my @bad_item_fields = ();
223     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
224     my $item_sequence_num = 0;
225     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
226         $item_sequence_num++;
227         # we take the item field and stick it into a new
228         # MARC record -- this is required so far because (FIXME)
229         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
230         # and there is no TransformMarcFieldToKoha
231         my $temp_item_marc = MARC::Record->new();
232         $temp_item_marc->append_fields($item_field);
233     
234         # add biblionumber and biblioitemnumber
235         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
236         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
237         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
238         $item->{'biblionumber'} = $biblionumber;
239         $item->{'biblioitemnumber'} = $biblioitemnumber;
240
241         # check for duplicate barcode
242         my %item_errors = CheckItemPreSave($item);
243         if (%item_errors) {
244             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
245             push @bad_item_fields, $item_field;
246             next ITEMFIELD;
247         }
248
249         my $item_object = Koha::Item->new($item)->store;
250         push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
251
252         logaction("CATALOGUING", "ADD", $item->itemnumber, "item") if C4::Context->preference("CataloguingLog");
253
254         my $new_item_marc = _marc_from_item_hash($item->unblessed, $frameworkcode, $unlinked_item_subfields);
255         $item_field->replace_with($new_item_marc->field($itemtag));
256     }
257
258     # remove any MARC item fields for rejected items
259     foreach my $item_field (@bad_item_fields) {
260         $record->delete_field($item_field);
261     }
262
263     # update the MARC biblio
264  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
265
266     return (\@itemnumbers, \@errors);
267 }
268
269 sub ModItemFromMarc {
270     my $item_marc = shift;
271     my $biblionumber = shift;
272     my $itemnumber = shift;
273
274     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
275     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
276
277     my $localitemmarc = MARC::Record->new;
278     $localitemmarc->append_fields( $item_marc->field($itemtag) );
279     my $item_object = Koha::Items->find($itemnumber);
280     my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
281     $item_object->set($item);
282     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
283     $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
284     $item_object->store;
285
286     return $item_object->unblessed;
287 }
288
289 =head2 ModItemTransfer
290
291   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
292
293 Marks an item as being transferred from one branch to another and records the trigger.
294
295 =cut
296
297 sub ModItemTransfer {
298     my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
299
300     my $dbh = C4::Context->dbh;
301     my $item = Koha::Items->find( $itemnumber );
302
303     # Remove the 'shelving cart' location status if it is being used.
304     CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
305
306     $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
307
308     #new entry in branchtransfers....
309     my $sth = $dbh->prepare(
310         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
311         VALUES (?, ?, NOW(), ?, ?)");
312     $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
313
314     # FIXME we are fetching the item twice in the 2 next statements!
315     Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
316     ModDateLastSeen($itemnumber);
317     return;
318 }
319
320 =head2 ModDateLastSeen
321
322 ModDateLastSeen( $itemnumber, $leave_item_lost );
323
324 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
325 C<$itemnumber> is the item number
326 C<$leave_item_lost> determines if a lost item will be found or remain lost
327
328 =cut
329
330 sub ModDateLastSeen {
331     my ( $itemnumber, $leave_item_lost ) = @_;
332
333     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
334
335     my $item = Koha::Items->find($itemnumber);
336     $item->datelastseen($today);
337     $item->itemlost(0) unless $leave_item_lost;
338     $item->store({ log_action => 0 });
339 }
340
341 =head2 CheckItemPreSave
342
343     my $item_ref = TransformMarcToKoha($marc, 'items');
344     # do stuff
345     my %errors = CheckItemPreSave($item_ref);
346     if (exists $errors{'duplicate_barcode'}) {
347         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
348     } elsif (exists $errors{'invalid_homebranch'}) {
349         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
350     } elsif (exists $errors{'invalid_holdingbranch'}) {
351         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
352     } else {
353         print "item is OK";
354     }
355
356 Given a hashref containing item fields, determine if it can be
357 inserted or updated in the database.  Specifically, checks for
358 database integrity issues, and returns a hash containing any
359 of the following keys, if applicable.
360
361 =over 2
362
363 =item duplicate_barcode
364
365 Barcode, if it duplicates one already found in the database.
366
367 =item invalid_homebranch
368
369 Home branch, if not defined in branches table.
370
371 =item invalid_holdingbranch
372
373 Holding branch, if not defined in branches table.
374
375 =back
376
377 This function does NOT implement any policy-related checks,
378 e.g., whether current operator is allowed to save an
379 item that has a given branch code.
380
381 =cut
382
383 sub CheckItemPreSave {
384     my $item_ref = shift;
385
386     my %errors = ();
387
388     # check for duplicate barcode
389     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
390         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
391         if ($existing_item) {
392             if (!exists $item_ref->{'itemnumber'}                       # new item
393                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
394                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
395             }
396         }
397     }
398
399     # check for valid home branch
400     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
401         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
402         unless (defined $home_library) {
403             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
404         }
405     }
406
407     # check for valid holding branch
408     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
409         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
410         unless (defined $holding_library) {
411             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
412         }
413     }
414
415     return %errors;
416
417 }
418
419 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
420
421 The following functions provide various ways of 
422 getting an item record, a set of item records, or
423 lists of authorized values for certain item fields.
424
425 =cut
426
427 =head2 GetItemsForInventory
428
429 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
430   minlocation  => $minlocation,
431   maxlocation  => $maxlocation,
432   location     => $location,
433   itemtype     => $itemtype,
434   ignoreissued => $ignoreissued,
435   datelastseen => $datelastseen,
436   branchcode   => $branchcode,
437   branch       => $branch,
438   offset       => $offset,
439   size         => $size,
440   statushash   => $statushash,
441 } );
442
443 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
444
445 The sub returns a reference to a list of hashes, each containing
446 itemnumber, author, title, barcode, item callnumber, and date last
447 seen. It is ordered by callnumber then title.
448
449 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
450 the datelastseen can be used to specify that you want to see items not seen since a past date only.
451 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
452 $statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
453
454 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
455
456 =cut
457
458 sub GetItemsForInventory {
459     my ( $parameters ) = @_;
460     my $minlocation  = $parameters->{'minlocation'}  // '';
461     my $maxlocation  = $parameters->{'maxlocation'}  // '';
462     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
463     my $location     = $parameters->{'location'}     // '';
464     my $itemtype     = $parameters->{'itemtype'}     // '';
465     my $ignoreissued = $parameters->{'ignoreissued'} // '';
466     my $datelastseen = $parameters->{'datelastseen'} // '';
467     my $branchcode   = $parameters->{'branchcode'}   // '';
468     my $branch       = $parameters->{'branch'}       // '';
469     my $offset       = $parameters->{'offset'}       // '';
470     my $size         = $parameters->{'size'}         // '';
471     my $statushash   = $parameters->{'statushash'}   // '';
472     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
473
474     my $dbh = C4::Context->dbh;
475     my ( @bind_params, @where_strings );
476
477     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
478     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
479
480     my $select_columns = q{
481         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
482     };
483     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
484     my $query = q{
485         FROM items
486         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
487         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
488     };
489     if ($statushash){
490         for my $authvfield (keys %$statushash){
491             if ( scalar @{$statushash->{$authvfield}} > 0 ){
492                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
493                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
494             }
495         }
496     }
497
498     if ($minlocation) {
499         push @where_strings, 'items.cn_sort >= ?';
500         push @bind_params, $min_cnsort;
501     }
502
503     if ($maxlocation) {
504         push @where_strings, 'items.cn_sort <= ?';
505         push @bind_params, $max_cnsort;
506     }
507
508     if ($datelastseen) {
509         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
510         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
511         push @bind_params, $datelastseen;
512     }
513
514     if ( $location ) {
515         push @where_strings, 'items.location = ?';
516         push @bind_params, $location;
517     }
518
519     if ( $branchcode ) {
520         if($branch eq "homebranch"){
521         push @where_strings, 'items.homebranch = ?';
522         }else{
523             push @where_strings, 'items.holdingbranch = ?';
524         }
525         push @bind_params, $branchcode;
526     }
527
528     if ( $itemtype ) {
529         push @where_strings, 'biblioitems.itemtype = ?';
530         push @bind_params, $itemtype;
531     }
532
533     if ( $ignoreissued) {
534         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
535         push @where_strings, 'issues.date_due IS NULL';
536     }
537
538     if ( $ignore_waiting_holds ) {
539         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
540         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
541     }
542
543     if ( @where_strings ) {
544         $query .= 'WHERE ';
545         $query .= join ' AND ', @where_strings;
546     }
547     my $count_query = $select_count . $query;
548     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
549     $query .= " LIMIT $offset, $size" if ($offset and $size);
550     $query = $select_columns . $query;
551     my $sth = $dbh->prepare($query);
552     $sth->execute( @bind_params );
553
554     my @results = ();
555     my $tmpresults = $sth->fetchall_arrayref({});
556     $sth = $dbh->prepare( $count_query );
557     $sth->execute( @bind_params );
558     my ($iTotalRecords) = $sth->fetchrow_array();
559
560     my @avs = Koha::AuthorisedValues->search(
561         {   'marc_subfield_structures.kohafield' => { '>' => '' },
562             'me.authorised_value'                => { '>' => '' },
563         },
564         {   join     => { category => 'marc_subfield_structures' },
565             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
566             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
567             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
568         }
569     );
570
571     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
572
573     foreach my $row (@$tmpresults) {
574
575         # Auth values
576         foreach (keys %$row) {
577             if (
578                 defined(
579                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
580                 )
581             ) {
582                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
583             }
584         }
585         push @results, $row;
586     }
587
588     return (\@results, $iTotalRecords);
589 }
590
591 =head2 GetItemsInfo
592
593   @results = GetItemsInfo($biblionumber);
594
595 Returns information about items with the given biblionumber.
596
597 C<GetItemsInfo> returns a list of references-to-hash. Each element
598 contains a number of keys. Most of them are attributes from the
599 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
600 Koha database. Other keys include:
601
602 =over 2
603
604 =item C<$data-E<gt>{branchname}>
605
606 The name (not the code) of the branch to which the book belongs.
607
608 =item C<$data-E<gt>{datelastseen}>
609
610 This is simply C<items.datelastseen>, except that while the date is
611 stored in YYYY-MM-DD format in the database, here it is converted to
612 DD/MM/YYYY format. A NULL date is returned as C<//>.
613
614 =item C<$data-E<gt>{datedue}>
615
616 =item C<$data-E<gt>{class}>
617
618 This is the concatenation of C<biblioitems.classification>, the book's
619 Dewey code, and C<biblioitems.subclass>.
620
621 =item C<$data-E<gt>{ocount}>
622
623 I think this is the number of copies of the book available.
624
625 =item C<$data-E<gt>{order}>
626
627 If this is set, it is set to C<One Order>.
628
629 =back
630
631 =cut
632
633 sub GetItemsInfo {
634     my ( $biblionumber ) = @_;
635     my $dbh   = C4::Context->dbh;
636     require C4::Languages;
637     my $language = C4::Languages::getlanguage();
638     my $query = "
639     SELECT items.*,
640            biblio.*,
641            biblioitems.volume,
642            biblioitems.number,
643            biblioitems.itemtype,
644            biblioitems.isbn,
645            biblioitems.issn,
646            biblioitems.publicationyear,
647            biblioitems.publishercode,
648            biblioitems.volumedate,
649            biblioitems.volumedesc,
650            biblioitems.lccn,
651            biblioitems.url,
652            items.notforloan as itemnotforloan,
653            issues.borrowernumber,
654            issues.date_due as datedue,
655            issues.onsite_checkout,
656            borrowers.cardnumber,
657            borrowers.surname,
658            borrowers.firstname,
659            borrowers.branchcode as bcode,
660            serial.serialseq,
661            serial.publisheddate,
662            itemtypes.description,
663            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
664            itemtypes.notforloan as notforloan_per_itemtype,
665            holding.branchurl,
666            holding.branchcode,
667            holding.branchname,
668            holding.opac_info as holding_branch_opac_info,
669            home.opac_info as home_branch_opac_info,
670            IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
671      FROM items
672      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
673      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
674      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
675      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
676      LEFT JOIN issues USING (itemnumber)
677      LEFT JOIN borrowers USING (borrowernumber)
678      LEFT JOIN serialitems USING (itemnumber)
679      LEFT JOIN serial USING (serialid)
680      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
681      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
682     $query .= q|
683     LEFT JOIN tmp_holdsqueue USING (itemnumber)
684     LEFT JOIN localization ON itemtypes.itemtype = localization.code
685         AND localization.entity = 'itemtypes'
686         AND localization.lang = ?
687     |;
688
689     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
690     my $sth = $dbh->prepare($query);
691     $sth->execute($language, $biblionumber);
692     my $i = 0;
693     my @results;
694     my $serial;
695
696     my $userenv = C4::Context->userenv;
697     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
698     while ( my $data = $sth->fetchrow_hashref ) {
699         if ( $data->{borrowernumber} && $want_not_same_branch) {
700             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
701         }
702
703         $serial ||= $data->{'serial'};
704
705         my $descriptions;
706         # get notforloan complete status if applicable
707         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
708         $data->{notforloanvalue}     = $descriptions->{lib} // '';
709         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
710
711         # get restricted status and description if applicable
712         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
713         $data->{restrictedvalue}     = $descriptions->{lib} // '';
714         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
715
716         # my stack procedures
717         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
718         $data->{stack}          = $descriptions->{lib} // '';
719
720         # Find the last 3 people who borrowed this item.
721         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
722                                     WHERE itemnumber = ?
723                                     AND old_issues.borrowernumber = borrowers.borrowernumber
724                                     ORDER BY returndate DESC
725                                     LIMIT 3");
726         $sth2->execute($data->{'itemnumber'});
727         my $ii = 0;
728         while (my $data2 = $sth2->fetchrow_hashref()) {
729             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
730             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
731             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
732             $ii++;
733         }
734
735         $results[$i] = $data;
736         $i++;
737     }
738
739     return $serial
740         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
741         : @results;
742 }
743
744 =head2 GetItemsLocationInfo
745
746   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
747
748 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
749
750 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
751
752 =over 2
753
754 =item C<$data-E<gt>{homebranch}>
755
756 Branch Name of the item's homebranch
757
758 =item C<$data-E<gt>{holdingbranch}>
759
760 Branch Name of the item's holdingbranch
761
762 =item C<$data-E<gt>{location}>
763
764 Item's shelving location code
765
766 =item C<$data-E<gt>{location_intranet}>
767
768 The intranet description for the Shelving Location as set in authorised_values 'LOC'
769
770 =item C<$data-E<gt>{location_opac}>
771
772 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
773 description is set.
774
775 =item C<$data-E<gt>{itemcallnumber}>
776
777 Item's itemcallnumber
778
779 =item C<$data-E<gt>{cn_sort}>
780
781 Item's call number normalized for sorting
782
783 =back
784   
785 =cut
786
787 sub GetItemsLocationInfo {
788         my $biblionumber = shift;
789         my @results;
790
791         my $dbh = C4::Context->dbh;
792         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
793                             location, itemcallnumber, cn_sort
794                      FROM items, branches as a, branches as b
795                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
796                      AND biblionumber = ?
797                      ORDER BY cn_sort ASC";
798         my $sth = $dbh->prepare($query);
799         $sth->execute($biblionumber);
800
801         while ( my $data = $sth->fetchrow_hashref ) {
802              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
803              $av = $av->count ? $av->next : undef;
804              $data->{location_intranet} = $av ? $av->lib : '';
805              $data->{location_opac}     = $av ? $av->opac_description : '';
806              push @results, $data;
807         }
808         return @results;
809 }
810
811 =head2 GetHostItemsInfo
812
813     $hostiteminfo = GetHostItemsInfo($hostfield);
814     Returns the iteminfo for items linked to records via a host field
815
816 =cut
817
818 sub GetHostItemsInfo {
819     my ($record) = @_;
820     my @returnitemsInfo;
821
822     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
823         return @returnitemsInfo;
824     }
825
826     my @fields;
827     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
828       C4::Context->preference('marcflavour') eq 'NORMARC') {
829         @fields = $record->field('773');
830     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
831         @fields = $record->field('461');
832     }
833
834     foreach my $hostfield ( @fields ) {
835         my $hostbiblionumber = $hostfield->subfield("0");
836         my $linkeditemnumber = $hostfield->subfield("9");
837         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
838         foreach my $hostitemInfo (@hostitemInfos) {
839             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
840                 push @returnitemsInfo, $hostitemInfo;
841                 last;
842             }
843         }
844     }
845     return @returnitemsInfo;
846 }
847
848 =head2 get_hostitemnumbers_of
849
850   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
851
852 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
853
854 Return a reference on a hash where key is a biblionumber and values are
855 references on array of itemnumbers.
856
857 =cut
858
859
860 sub get_hostitemnumbers_of {
861     my ($biblionumber) = @_;
862
863     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
864         return ();
865     }
866
867     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
868     return unless $marcrecord;
869
870     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
871
872     my $marcflavor = C4::Context->preference('marcflavour');
873     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
874         $tag      = '773';
875         $biblio_s = '0';
876         $item_s   = '9';
877     }
878     elsif ( $marcflavor eq 'UNIMARC' ) {
879         $tag      = '461';
880         $biblio_s = '0';
881         $item_s   = '9';
882     }
883
884     foreach my $hostfield ( $marcrecord->field($tag) ) {
885         my $hostbiblionumber = $hostfield->subfield($biblio_s);
886         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
887         my $linkeditemnumber = $hostfield->subfield($item_s);
888         if ( ! $linkeditemnumber ) {
889             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
890             next;
891         }
892         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
893         push @returnhostitemnumbers, $linkeditemnumber
894           if $is_from_biblio;
895     }
896
897     return @returnhostitemnumbers;
898 }
899
900 =head2 GetHiddenItemnumbers
901
902     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
903
904 Given a list of items it checks which should be hidden from the OPAC given
905 the current configuration. Returns a list of itemnumbers corresponding to
906 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
907 to be excluded
908
909 =cut
910
911 sub GetHiddenItemnumbers {
912     my $params = shift;
913     my $items = $params->{items};
914     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
915         foreach my $except (split(/\|/, $exceptions)){
916             if ($params->{'borcat'} eq $except){
917                 return; # we don't hide anything for this borrower category
918             }
919         }
920     }
921     my @resultitems;
922
923     my $yaml = C4::Context->preference('OpacHiddenItems');
924     return () if (! $yaml =~ /\S/ );
925     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
926     my $hidingrules;
927     eval {
928         $hidingrules = YAML::Load($yaml);
929     };
930     if ($@) {
931         warn "Unable to parse OpacHiddenItems syspref : $@";
932         return ();
933     }
934     my $dbh = C4::Context->dbh;
935
936     # For each item
937     foreach my $item (@$items) {
938
939         # We check each rule
940         foreach my $field (keys %$hidingrules) {
941             my $val;
942             if (exists $item->{$field}) {
943                 $val = $item->{$field};
944             }
945             else {
946                 my $query = "SELECT $field from items where itemnumber = ?";
947                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
948             }
949             $val = '' unless defined $val;
950
951             # If the results matches the values in the yaml file
952             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
953
954                 # We add the itemnumber to the list
955                 push @resultitems, $item->{'itemnumber'};
956
957                 # If at least one rule matched for an item, no need to test the others
958                 last;
959             }
960         }
961     }
962     return @resultitems;
963 }
964
965 =head1 LIMITED USE FUNCTIONS
966
967 The following functions, while part of the public API,
968 are not exported.  This is generally because they are
969 meant to be used by only one script for a specific
970 purpose, and should not be used in any other context
971 without careful thought.
972
973 =cut
974
975 =head2 GetMarcItem
976
977   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
978
979 Returns MARC::Record of the item passed in parameter.
980 This function is meant for use only in C<cataloguing/additem.pl>,
981 where it is needed to support that script's MARC-like
982 editor.
983
984 =cut
985
986 sub GetMarcItem {
987     my ( $biblionumber, $itemnumber ) = @_;
988
989     # GetMarcItem has been revised so that it does the following:
990     #  1. Gets the item information from the items table.
991     #  2. Converts it to a MARC field for storage in the bib record.
992     #
993     # The previous behavior was:
994     #  1. Get the bib record.
995     #  2. Return the MARC tag corresponding to the item record.
996     #
997     # The difference is that one treats the items row as authoritative,
998     # while the other treats the MARC representation as authoritative
999     # under certain circumstances.
1000
1001     my $item = Koha::Items->find($itemnumber) or return;
1002
1003     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1004     # Also, don't emit a subfield if the underlying field is blank.
1005
1006     return Item2Marc($item->unblessed, $biblionumber);
1007
1008 }
1009 sub Item2Marc {
1010         my ($itemrecord,$biblionumber)=@_;
1011     my $mungeditem = { 
1012         map {  
1013             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1014         } keys %{ $itemrecord } 
1015     };
1016     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1017     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1018     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1019         "items.itemnumber", $framework,
1020     );
1021
1022     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1023     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1024                 foreach my $field ($itemmarc->field($itemtag)){
1025             $field->add_subfields(@$unlinked_item_subfields);
1026         }
1027     }
1028         return $itemmarc;
1029 }
1030
1031 =head1 PRIVATE FUNCTIONS AND VARIABLES
1032
1033 The following functions are not meant to be called
1034 directly, but are documented in order to explain
1035 the inner workings of C<C4::Items>.
1036
1037 =cut
1038
1039 =head2 MoveItemFromBiblio
1040
1041   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1042
1043 Moves an item from a biblio to another
1044
1045 Returns undef if the move failed or the biblionumber of the destination record otherwise
1046
1047 =cut
1048
1049 sub MoveItemFromBiblio {
1050     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1051     my $dbh = C4::Context->dbh;
1052     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1053         SELECT biblioitemnumber
1054         FROM biblioitems
1055         WHERE biblionumber = ?
1056     |, undef, $tobiblio );
1057     my $return = $dbh->do(q|
1058         UPDATE items
1059         SET biblioitemnumber = ?,
1060             biblionumber = ?
1061         WHERE itemnumber = ?
1062             AND biblionumber = ?
1063     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1064     if ($return == 1) {
1065         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1066         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1067             # Checking if the item we want to move is in an order 
1068         require C4::Acquisition;
1069         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1070             if ($order) {
1071                     # Replacing the biblionumber within the order if necessary
1072                     $order->{'biblionumber'} = $tobiblio;
1073                 C4::Acquisition::ModOrder($order);
1074             }
1075
1076         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1077         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1078             $dbh->do( qq|
1079                 UPDATE $table_name
1080                 SET biblionumber = ?
1081                 WHERE itemnumber = ?
1082             |, undef, $tobiblio, $itemnumber );
1083         }
1084         return $tobiblio;
1085         }
1086     return;
1087 }
1088
1089 =head2 _marc_from_item_hash
1090
1091   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1092
1093 Given an item hash representing a complete item record,
1094 create a C<MARC::Record> object containing an embedded
1095 tag representing that item.
1096
1097 The third, optional parameter C<$unlinked_item_subfields> is
1098 an arrayref of subfields (not mapped to C<items> fields per the
1099 framework) to be added to the MARC representation
1100 of the item.
1101
1102 =cut
1103
1104 sub _marc_from_item_hash {
1105     my $item = shift;
1106     my $frameworkcode = shift;
1107     my $unlinked_item_subfields;
1108     if (@_) {
1109         $unlinked_item_subfields = shift;
1110     }
1111    
1112     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1113     # Also, don't emit a subfield if the underlying field is blank.
1114     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1115                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1116                                 : ()  } keys %{ $item } }; 
1117
1118     my $item_marc = MARC::Record->new();
1119     foreach my $item_field ( keys %{$mungeditem} ) {
1120         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1121         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
1122         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1123         foreach my $value (@values){
1124             if ( my $field = $item_marc->field($tag) ) {
1125                     $field->add_subfields( $subfield => $value );
1126             } else {
1127                 my $add_subfields = [];
1128                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1129                     $add_subfields = $unlinked_item_subfields;
1130             }
1131             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1132             }
1133         }
1134     }
1135
1136     return $item_marc;
1137 }
1138
1139 =head2 _repack_item_errors
1140
1141 Add an error message hash generated by C<CheckItemPreSave>
1142 to a list of errors.
1143
1144 =cut
1145
1146 sub _repack_item_errors {
1147     my $item_sequence_num = shift;
1148     my $item_ref = shift;
1149     my $error_ref = shift;
1150
1151     my @repacked_errors = ();
1152
1153     foreach my $error_code (sort keys %{ $error_ref }) {
1154         my $repacked_error = {};
1155         $repacked_error->{'item_sequence'} = $item_sequence_num;
1156         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1157         $repacked_error->{'error_code'} = $error_code;
1158         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1159         push @repacked_errors, $repacked_error;
1160     } 
1161
1162     return @repacked_errors;
1163 }
1164
1165 =head2 _get_unlinked_item_subfields
1166
1167   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1168
1169 =cut
1170
1171 sub _get_unlinked_item_subfields {
1172     my $original_item_marc = shift;
1173     my $frameworkcode = shift;
1174
1175     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1176
1177     # assume that this record has only one field, and that that
1178     # field contains only the item information
1179     my $subfields = [];
1180     my @fields = $original_item_marc->fields();
1181     if ($#fields > -1) {
1182         my $field = $fields[0];
1183             my $tag = $field->tag();
1184         foreach my $subfield ($field->subfields()) {
1185             if (defined $subfield->[1] and
1186                 $subfield->[1] ne '' and
1187                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1188                 push @$subfields, $subfield->[0] => $subfield->[1];
1189             }
1190         }
1191     }
1192     return $subfields;
1193 }
1194
1195 =head2 _get_unlinked_subfields_xml
1196
1197   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1198
1199 =cut
1200
1201 sub _get_unlinked_subfields_xml {
1202     my $unlinked_item_subfields = shift;
1203
1204     my $xml;
1205     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1206         my $marc = MARC::Record->new();
1207         # use of tag 999 is arbitrary, and doesn't need to match the item tag
1208         # used in the framework
1209         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1210         $marc->encoding("UTF-8");    
1211         $xml = $marc->as_xml("USMARC");
1212     }
1213
1214     return $xml;
1215 }
1216
1217 =head2 _parse_unlinked_item_subfields_from_xml
1218
1219   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1220
1221 =cut
1222
1223 sub  _parse_unlinked_item_subfields_from_xml {
1224     my $xml = shift;
1225     require C4::Charset;
1226     return unless defined $xml and $xml ne "";
1227     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1228     my $unlinked_subfields = [];
1229     my @fields = $marc->fields();
1230     if ($#fields > -1) {
1231         foreach my $subfield ($fields[0]->subfields()) {
1232             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1233         }
1234     }
1235     return $unlinked_subfields;
1236 }
1237
1238 =head2 GetAnalyticsCount
1239
1240   $count= &GetAnalyticsCount($itemnumber)
1241
1242 counts Usage of itemnumber in Analytical bibliorecords. 
1243
1244 =cut
1245
1246 sub GetAnalyticsCount {
1247     my ($itemnumber) = @_;
1248
1249     ### ZOOM search here
1250     my $query;
1251     $query= "hi=".$itemnumber;
1252     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1253     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1254     return ($result);
1255 }
1256
1257 sub _SearchItems_build_where_fragment {
1258     my ($filter) = @_;
1259
1260     my $dbh = C4::Context->dbh;
1261
1262     my $where_fragment;
1263     if (exists($filter->{conjunction})) {
1264         my (@where_strs, @where_args);
1265         foreach my $f (@{ $filter->{filters} }) {
1266             my $fragment = _SearchItems_build_where_fragment($f);
1267             if ($fragment) {
1268                 push @where_strs, $fragment->{str};
1269                 push @where_args, @{ $fragment->{args} };
1270             }
1271         }
1272         my $where_str = '';
1273         if (@where_strs) {
1274             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1275             $where_fragment = {
1276                 str => $where_str,
1277                 args => \@where_args,
1278             };
1279         }
1280     } else {
1281         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1282         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1283         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1284         my @operators = qw(= != > < >= <= like);
1285         my $field = $filter->{field} // q{};
1286         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1287             my $op = $filter->{operator};
1288             my $query = $filter->{query};
1289
1290             if (!$op or (0 == grep { $_ eq $op } @operators)) {
1291                 $op = '='; # default operator
1292             }
1293
1294             my $column;
1295             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1296                 my $marcfield = $1;
1297                 my $marcsubfield = $2;
1298                 my ($kohafield) = $dbh->selectrow_array(q|
1299                     SELECT kohafield FROM marc_subfield_structure
1300                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1301                 |, undef, $marcfield, $marcsubfield);
1302
1303                 if ($kohafield) {
1304                     $column = $kohafield;
1305                 } else {
1306                     # MARC field is not linked to a DB field so we need to use
1307                     # ExtractValue on marcxml from biblio_metadata or
1308                     # items.more_subfields_xml, depending on the MARC field.
1309                     my $xpath;
1310                     my $sqlfield;
1311                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1312                     if ($marcfield eq $itemfield) {
1313                         $sqlfield = 'more_subfields_xml';
1314                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1315                     } else {
1316                         $sqlfield = 'metadata'; # From biblio_metadata
1317                         if ($marcfield < 10) {
1318                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1319                         } else {
1320                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1321                         }
1322                     }
1323                     $column = "ExtractValue($sqlfield, '$xpath')";
1324                 }
1325             } elsif ($field eq 'issues') {
1326                 # Consider NULL as 0 for issues count
1327                 $column = 'COALESCE(issues,0)';
1328             } else {
1329                 $column = $field;
1330             }
1331
1332             if (ref $query eq 'ARRAY') {
1333                 if ($op eq '=') {
1334                     $op = 'IN';
1335                 } elsif ($op eq '!=') {
1336                     $op = 'NOT IN';
1337                 }
1338                 $where_fragment = {
1339                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
1340                     args => $query,
1341                 };
1342             } else {
1343                 $where_fragment = {
1344                     str => "$column $op ?",
1345                     args => [ $query ],
1346                 };
1347             }
1348         }
1349     }
1350
1351     return $where_fragment;
1352 }
1353
1354 =head2 SearchItems
1355
1356     my ($items, $total) = SearchItems($filter, $params);
1357
1358 Perform a search among items
1359
1360 $filter is a reference to a hash which can be a filter, or a combination of filters.
1361
1362 A filter has the following keys:
1363
1364 =over 2
1365
1366 =item * field: the name of a SQL column in table items
1367
1368 =item * query: the value to search in this column
1369
1370 =item * operator: comparison operator. Can be one of = != > < >= <= like
1371
1372 =back
1373
1374 A combination of filters hash the following keys:
1375
1376 =over 2
1377
1378 =item * conjunction: 'AND' or 'OR'
1379
1380 =item * filters: array ref of filters
1381
1382 =back
1383
1384 $params is a reference to a hash that can contain the following parameters:
1385
1386 =over 2
1387
1388 =item * rows: Number of items to return. 0 returns everything (default: 0)
1389
1390 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1391                (default: 1)
1392
1393 =item * sortby: A SQL column name in items table to sort on
1394
1395 =item * sortorder: 'ASC' or 'DESC'
1396
1397 =back
1398
1399 =cut
1400
1401 sub SearchItems {
1402     my ($filter, $params) = @_;
1403
1404     $filter //= {};
1405     $params //= {};
1406     return unless ref $filter eq 'HASH';
1407     return unless ref $params eq 'HASH';
1408
1409     # Default parameters
1410     $params->{rows} ||= 0;
1411     $params->{page} ||= 1;
1412     $params->{sortby} ||= 'itemnumber';
1413     $params->{sortorder} ||= 'ASC';
1414
1415     my ($where_str, @where_args);
1416     my $where_fragment = _SearchItems_build_where_fragment($filter);
1417     if ($where_fragment) {
1418         $where_str = $where_fragment->{str};
1419         @where_args = @{ $where_fragment->{args} };
1420     }
1421
1422     my $dbh = C4::Context->dbh;
1423     my $query = q{
1424         SELECT SQL_CALC_FOUND_ROWS items.*
1425         FROM items
1426           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1427           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1428           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1429           WHERE 1
1430     };
1431     if (defined $where_str and $where_str ne '') {
1432         $query .= qq{ AND $where_str };
1433     }
1434
1435     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1436     push @where_args, C4::Context->preference('marcflavour');
1437
1438     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1439     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1440     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1441     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1442         ? $params->{sortby} : 'itemnumber';
1443     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1444     $query .= qq{ ORDER BY $sortby $sortorder };
1445
1446     my $rows = $params->{rows};
1447     my @limit_args;
1448     if ($rows > 0) {
1449         my $offset = $rows * ($params->{page}-1);
1450         $query .= qq { LIMIT ?, ? };
1451         push @limit_args, $offset, $rows;
1452     }
1453
1454     my $sth = $dbh->prepare($query);
1455     my $rv = $sth->execute(@where_args, @limit_args);
1456
1457     return unless ($rv);
1458     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1459
1460     return ($sth->fetchall_arrayref({}), $total_rows);
1461 }
1462
1463
1464 =head1  OTHER FUNCTIONS
1465
1466 =head2 _find_value
1467
1468   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1469
1470 Find the given $subfield in the given $tag in the given
1471 MARC::Record $record.  If the subfield is found, returns
1472 the (indicators, value) pair; otherwise, (undef, undef) is
1473 returned.
1474
1475 PROPOSITION :
1476 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1477 I suggest we export it from this module.
1478
1479 =cut
1480
1481 sub _find_value {
1482     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1483     my @result;
1484     my $indicator;
1485     if ( $tagfield < 10 ) {
1486         if ( $record->field($tagfield) ) {
1487             push @result, $record->field($tagfield)->data();
1488         } else {
1489             push @result, "";
1490         }
1491     } else {
1492         foreach my $field ( $record->field($tagfield) ) {
1493             my @subfields = $field->subfields();
1494             foreach my $subfield (@subfields) {
1495                 if ( @$subfield[0] eq $insubfield ) {
1496                     push @result, @$subfield[1];
1497                     $indicator = $field->indicator(1) . $field->indicator(2);
1498                 }
1499             }
1500         }
1501     }
1502     return ( $indicator, @result );
1503 }
1504
1505
1506 =head2 PrepareItemrecordDisplay
1507
1508   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
1509
1510 Returns a hash with all the fields for Display a given item data in a template
1511
1512 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1513
1514 =cut
1515
1516 sub PrepareItemrecordDisplay {
1517
1518     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1519
1520     my $dbh = C4::Context->dbh;
1521     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1522     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1523
1524     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1525     # a shared data structure. No plugin (including custom ones) should change
1526     # its contents. See also GetMarcStructure.
1527     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1528
1529     # return nothing if we don't have found an existing framework.
1530     return q{} unless $tagslib;
1531     my $itemrecord;
1532     if ($itemnum) {
1533         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1534     }
1535     my @loop_data;
1536
1537     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1538     my $query = qq{
1539         SELECT authorised_value,lib FROM authorised_values
1540     };
1541     $query .= qq{
1542         LEFT JOIN authorised_values_branches ON ( id = av_id )
1543     } if $branch_limit;
1544     $query .= qq{
1545         WHERE category = ?
1546     };
1547     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1548     $query .= qq{ ORDER BY lib};
1549     my $authorised_values_sth = $dbh->prepare( $query );
1550     foreach my $tag ( sort keys %{$tagslib} ) {
1551         if ( $tag ne '' ) {
1552
1553             # loop through each subfield
1554             my $cntsubf;
1555             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
1556                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
1557                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
1558                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
1559                 my %subfield_data;
1560                 $subfield_data{tag}           = $tag;
1561                 $subfield_data{subfield}      = $subfield;
1562                 $subfield_data{countsubfield} = $cntsubf++;
1563                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
1564                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
1565
1566                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1567                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
1568                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
1569                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
1570                 $subfield_data{hidden}     = "display:none"
1571                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
1572                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
1573                 my ( $x, $defaultvalue );
1574                 if ($itemrecord) {
1575                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
1576                 }
1577                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
1578                 if ( !defined $defaultvalue ) {
1579                     $defaultvalue = q||;
1580                 } else {
1581                     $defaultvalue =~ s/"/&quot;/g;
1582                 }
1583
1584                 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
1585
1586                 # search for itemcallnumber if applicable
1587                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1588                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
1589                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1590                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
1591                         next unless my $field = $itemrecord->field($CNtag);
1592                         my $CNsubfields = substr( $itemcn_pref, 3 );
1593                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
1594                         last if $defaultvalue;
1595                     }
1596                 }
1597                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
1598                     && $defaultvalues
1599                     && $defaultvalues->{'callnumber'} ) {
1600                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
1601                         # if the item record exists, only use default value if the item has no callnumber
1602                         $defaultvalue = $defaultvalues->{callnumber};
1603                     } elsif ( !$itemrecord and $defaultvalues ) {
1604                         # if the item record *doesn't* exists, always use the default value
1605                         $defaultvalue = $defaultvalues->{callnumber};
1606                     }
1607                 }
1608                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
1609                     && $defaultvalues
1610                     && $defaultvalues->{'branchcode'} ) {
1611                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1612                         $defaultvalue = $defaultvalues->{branchcode};
1613                     }
1614                 }
1615                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
1616                     && $defaultvalues
1617                     && $defaultvalues->{'location'} ) {
1618
1619                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1620                         # if the item record exists, only use default value if the item has no locationr
1621                         $defaultvalue = $defaultvalues->{location};
1622                     } elsif ( !$itemrecord and $defaultvalues ) {
1623                         # if the item record *doesn't* exists, always use the default value
1624                         $defaultvalue = $defaultvalues->{location};
1625                     }
1626                 }
1627                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
1628                     my @authorised_values;
1629                     my %authorised_lib;
1630
1631                     # builds list, depending on authorised value...
1632                     #---- branch
1633                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
1634                         if (   ( C4::Context->preference("IndependentBranches") )
1635                             && !C4::Context->IsSuperLibrarian() ) {
1636                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1637                             $sth->execute( C4::Context->userenv->{branch} );
1638                             push @authorised_values, ""
1639                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1640                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1641                                 push @authorised_values, $branchcode;
1642                                 $authorised_lib{$branchcode} = $branchname;
1643                             }
1644                         } else {
1645                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1646                             $sth->execute;
1647                             push @authorised_values, ""
1648                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1649                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1650                                 push @authorised_values, $branchcode;
1651                                 $authorised_lib{$branchcode} = $branchname;
1652                             }
1653                         }
1654
1655                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1656                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1657                             $defaultvalue = $defaultvalues->{branchcode};
1658                         }
1659
1660                         #----- itemtypes
1661                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
1662                         my $itemtypes = Koha::ItemTypes->search_with_localization;
1663                         push @authorised_values, ""
1664                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1665                         while ( my $itemtype = $itemtypes->next ) {
1666                             push @authorised_values, $itemtype->itemtype;
1667                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1668                         }
1669                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1670                             $defaultvalue = $defaultvalues->{'itemtype'};
1671                         }
1672
1673                         #---- class_sources
1674                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
1675                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1676
1677                         my $class_sources = GetClassSources();
1678                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1679
1680                         foreach my $class_source (sort keys %$class_sources) {
1681                             next unless $class_sources->{$class_source}->{'used'} or
1682                                         ($class_source eq $default_source);
1683                             push @authorised_values, $class_source;
1684                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1685                         }
1686
1687                         $defaultvalue = $default_source;
1688
1689                         #---- "true" authorised value
1690                     } else {
1691                         $authorised_values_sth->execute(
1692                             $tagslib->{$tag}->{$subfield}->{authorised_value},
1693                             $branch_limit ? $branch_limit : ()
1694                         );
1695                         push @authorised_values, ""
1696                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
1697                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1698                             push @authorised_values, $value;
1699                             $authorised_lib{$value} = $lib;
1700                         }
1701                     }
1702                     $subfield_data{marc_value} = {
1703                         type    => 'select',
1704                         values  => \@authorised_values,
1705                         default => $defaultvalue // q{},
1706                         labels  => \%authorised_lib,
1707                     };
1708                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
1709                 # it is a plugin
1710                     require Koha::FrameworkPlugin;
1711                     my $plugin = Koha::FrameworkPlugin->new({
1712                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
1713                         item_style => 1,
1714                     });
1715                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
1716                     $plugin->build( $pars );
1717                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1718                         $defaultvalue = $field->subfield($subfield) || q{};
1719                     }
1720                     if( !$plugin->errstr ) {
1721                         #TODO Move html to template; see report 12176/13397
1722                         my $tab= $plugin->noclick? '-1': '';
1723                         my $class= $plugin->noclick? ' disabled': '';
1724                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
1725                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
1726                     } else {
1727                         warn $plugin->errstr;
1728                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
1729                     }
1730                 }
1731                 elsif ( $tag eq '' ) {       # it's an hidden field
1732                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1733                 }
1734                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
1735                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1736                 }
1737                 elsif ( length($defaultvalue) > 100
1738                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1739                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
1740                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
1741                                   500 <= $tag && $tag < 600                     )
1742                           ) {
1743                     # oversize field (textarea)
1744                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1745                 } else {
1746                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1747                 }
1748                 push( @loop_data, \%subfield_data );
1749             }
1750         }
1751     }
1752     my $itemnumber;
1753     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1754         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1755     }
1756     return {
1757         'itemtagfield'    => $itemtagfield,
1758         'itemtagsubfield' => $itemtagsubfield,
1759         'itemnumber'      => $itemnumber,
1760         'iteminformation' => \@loop_data
1761     };
1762 }
1763
1764 sub ToggleNewStatus {
1765     my ( $params ) = @_;
1766     my @rules = @{ $params->{rules} };
1767     my $report_only = $params->{report_only};
1768
1769     my $dbh = C4::Context->dbh;
1770     my @errors;
1771     my @item_columns = map { "items.$_" } Koha::Items->columns;
1772     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1773     my $report;
1774     for my $rule ( @rules ) {
1775         my $age = $rule->{age};
1776         my $conditions = $rule->{conditions};
1777         my $substitutions = $rule->{substitutions};
1778         foreach ( @$substitutions ) {
1779             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1780         }
1781         my @params;
1782
1783         my $query = q|
1784             SELECT items.*
1785             FROM items
1786             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1787             WHERE 1
1788         |;
1789         for my $condition ( @$conditions ) {
1790             if (
1791                  grep { $_ eq $condition->{field} } @item_columns
1792               or grep { $_ eq $condition->{field} } @biblioitem_columns
1793             ) {
1794                 if ( $condition->{value} =~ /\|/ ) {
1795                     my @values = split /\|/, $condition->{value};
1796                     $query .= qq| AND $condition->{field} IN (|
1797                         . join( ',', ('?') x scalar @values )
1798                         . q|)|;
1799                     push @params, @values;
1800                 } else {
1801                     $query .= qq| AND $condition->{field} = ?|;
1802                     push @params, $condition->{value};
1803                 }
1804             }
1805         }
1806         if ( defined $age ) {
1807             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
1808             push @params, $age;
1809         }
1810         my $sth = $dbh->prepare($query);
1811         $sth->execute( @params );
1812         while ( my $values = $sth->fetchrow_hashref ) {
1813             my $biblionumber = $values->{biblionumber};
1814             my $itemnumber = $values->{itemnumber};
1815             my $item = Koha::Items->find($itemnumber);
1816             for my $substitution ( @$substitutions ) {
1817                 my $field = $substitution->{item_field};
1818                 my $value = $substitution->{value};
1819                 next unless $substitution->{field};
1820                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1821                 $item->$field($value);
1822                 push @{ $report->{$itemnumber} }, $substitution;
1823             }
1824             $item->store unless $report_only;
1825         }
1826     }
1827
1828     return $report;
1829 }
1830
1831 1;