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