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