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