Bug 14598 [QA Followup] - Correct the behavior of GetItem
[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 strict;
22 #use warnings; FIXME - Bug 2505
23
24 use Carp;
25 use C4::Context;
26 use C4::Koha;
27 use C4::Biblio;
28 use Koha::DateUtils;
29 use MARC::Record;
30 use C4::ClassSource;
31 use C4::Log;
32 use List::MoreUtils qw/any/;
33 use YAML qw/Load/;
34 use DateTime::Format::MySQL;
35 use Data::Dumper; # used as part of logging item record changes, not just for
36                   # debugging; so please don't remove this
37
38 use Koha::AuthorisedValues;
39 use Koha::DateUtils qw/dt_from_string/;
40 use Koha::Database;
41
42 use Koha::Biblioitems;
43 use Koha::Items;
44 use Koha::SearchEngine;
45 use Koha::SearchEngine::Search;
46 use Koha::Libraries;
47
48 use vars qw(@ISA @EXPORT);
49
50 BEGIN {
51
52         require Exporter;
53     @ISA = qw( Exporter );
54
55     # function exports
56     @EXPORT = qw(
57         GetItem
58         AddItemFromMarc
59         AddItem
60         AddItemBatchFromMarc
61         ModItemFromMarc
62     Item2Marc
63         ModItem
64         ModDateLastSeen
65         ModItemTransfer
66         DelItem
67     
68         CheckItemPreSave
69     
70         GetItemStatus
71         GetItemLocation
72         GetLostItems
73         GetItemsForInventory
74         GetItemsCount
75         GetItemInfosOf
76         GetItemsByBiblioitemnumber
77         GetItemsInfo
78         GetItemsLocationInfo
79         GetHostItemsInfo
80         GetItemnumbersForBiblio
81         get_itemnumbers_of
82         get_hostitemnumbers_of
83         GetItemnumberFromBarcode
84         GetBarcodeFromItemnumber
85         GetHiddenItemnumbers
86         ItemSafeToDelete
87         DelItemCheck
88     MoveItemFromBiblio
89     GetLatestAcquisitions
90
91         CartToShelf
92         ShelfToCart
93
94         GetAnalyticsCount
95         GetItemHolds
96
97         SearchItemsByField
98         SearchItems
99
100         PrepareItemrecordDisplay
101
102     );
103 }
104
105 =head1 NAME
106
107 C4::Items - item management functions
108
109 =head1 DESCRIPTION
110
111 This module contains an API for manipulating item 
112 records in Koha, and is used by cataloguing, circulation,
113 acquisitions, and serials management.
114
115 A Koha item record is stored in two places: the
116 items table and embedded in a MARC tag in the XML
117 version of the associated bib record in C<biblioitems.marcxml>.
118 This is done to allow the item information to be readily
119 indexed (e.g., by Zebra), but means that each item
120 modification transaction must keep the items table
121 and the MARC XML in sync at all times.
122
123 Consequently, all code that creates, modifies, or deletes
124 item records B<must> use an appropriate function from 
125 C<C4::Items>.  If no existing function is suitable, it is
126 better to add one to C<C4::Items> than to use add
127 one-off SQL statements to add or modify items.
128
129 The items table will be considered authoritative.  In other
130 words, if there is ever a discrepancy between the items
131 table and the MARC XML, the items table should be considered
132 accurate.
133
134 =head1 HISTORICAL NOTE
135
136 Most of the functions in C<C4::Items> were originally in
137 the C<C4::Biblio> module.
138
139 =head1 CORE EXPORTED FUNCTIONS
140
141 The following functions are meant for use by users
142 of C<C4::Items>
143
144 =cut
145
146 =head2 GetItem
147
148   $item = GetItem($itemnumber,$barcode,$serial);
149
150 Return item information, for a given itemnumber or barcode.
151 The return value is a hashref mapping item column
152 names to values.  If C<$serial> is true, include serial publication data.
153
154 =cut
155
156 sub GetItem {
157     my ($itemnumber,$barcode, $serial) = @_;
158     my $dbh = C4::Context->dbh;
159
160     my $item;
161     if ($itemnumber) {
162         $item = Koha::Items->find( $itemnumber );
163     } else {
164         $item = Koha::Items->find( { barcode => $barcode } );
165     }
166
167     return unless ( $item );
168
169     my $data = $item->unblessed();
170     $data->{itype} = $item->effective_itemtype(); # set the correct itype
171
172     if ($serial) {
173         my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
174         $ssth->execute( $data->{'itemnumber'} );
175         ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array();
176     }
177
178     return $data;
179 }    # sub GetItem
180
181 =head2 CartToShelf
182
183   CartToShelf($itemnumber);
184
185 Set the current shelving location of the item record
186 to its stored permanent shelving location.  This is
187 primarily used to indicate when an item whose current
188 location is a special processing ('PROC') or shelving cart
189 ('CART') location is back in the stacks.
190
191 =cut
192
193 sub CartToShelf {
194     my ( $itemnumber ) = @_;
195
196     unless ( $itemnumber ) {
197         croak "FAILED CartToShelf() - no itemnumber supplied";
198     }
199
200     my $item = GetItem($itemnumber);
201     if ( $item->{location} eq 'CART' ) {
202         $item->{location} = $item->{permanent_location};
203         ModItem($item, undef, $itemnumber);
204     }
205 }
206
207 =head2 ShelfToCart
208
209   ShelfToCart($itemnumber);
210
211 Set the current shelving location of the item
212 to shelving cart ('CART').
213
214 =cut
215
216 sub ShelfToCart {
217     my ( $itemnumber ) = @_;
218
219     unless ( $itemnumber ) {
220         croak "FAILED ShelfToCart() - no itemnumber supplied";
221     }
222
223     my $item = GetItem($itemnumber);
224     $item->{'location'} = 'CART';
225     ModItem($item, undef, $itemnumber);
226 }
227
228 =head2 AddItemFromMarc
229
230   my ($biblionumber, $biblioitemnumber, $itemnumber) 
231       = AddItemFromMarc($source_item_marc, $biblionumber);
232
233 Given a MARC::Record object containing an embedded item
234 record and a biblionumber, create a new item record.
235
236 =cut
237
238 sub AddItemFromMarc {
239     my ( $source_item_marc, $biblionumber ) = @_;
240     my $dbh = C4::Context->dbh;
241
242     # parse item hash from MARC
243     my $frameworkcode = GetFrameworkCode( $biblionumber );
244         my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
245         
246         my $localitemmarc=MARC::Record->new;
247         $localitemmarc->append_fields($source_item_marc->field($itemtag));
248     my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items');
249     my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
250     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
251 }
252
253 =head2 AddItem
254
255   my ($biblionumber, $biblioitemnumber, $itemnumber) 
256       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
257
258 Given a hash containing item column names as keys,
259 create a new Koha item record.
260
261 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
262 do not need to be supplied for general use; they exist
263 simply to allow them to be picked up from AddItemFromMarc.
264
265 The final optional parameter, C<$unlinked_item_subfields>, contains
266 an arrayref containing subfields present in the original MARC
267 representation of the item (e.g., from the item editor) that are
268 not mapped to C<items> columns directly but should instead
269 be stored in C<items.more_subfields_xml> and included in 
270 the biblio items tag for display and indexing.
271
272 =cut
273
274 sub AddItem {
275     my $item         = shift;
276     my $biblionumber = shift;
277
278     my $dbh           = @_ ? shift : C4::Context->dbh;
279     my $frameworkcode = @_ ? shift : GetFrameworkCode($biblionumber);
280     my $unlinked_item_subfields;
281     if (@_) {
282         $unlinked_item_subfields = shift;
283     }
284
285     # needs old biblionumber and biblioitemnumber
286     $item->{'biblionumber'} = $biblionumber;
287     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
288     $sth->execute( $item->{'biblionumber'} );
289     ( $item->{'biblioitemnumber'} ) = $sth->fetchrow;
290
291     _set_defaults_for_add($item);
292     _set_derived_columns_for_add($item);
293     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
294
295     # FIXME - checks here
296     unless ( $item->{itype} ) {    # default to biblioitem.itemtype if no itype
297         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
298         $itype_sth->execute( $item->{'biblionumber'} );
299         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
300     }
301
302     my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
303     return if $error;
304
305     $item->{'itemnumber'} = $itemnumber;
306
307     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
308
309     logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
310       if C4::Context->preference("CataloguingLog");
311
312     return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
313 }
314
315 =head2 AddItemBatchFromMarc
316
317   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
318              $biblionumber, $biblioitemnumber, $frameworkcode);
319
320 Efficiently create item records from a MARC biblio record with
321 embedded item fields.  This routine is suitable for batch jobs.
322
323 This API assumes that the bib record has already been
324 saved to the C<biblio> and C<biblioitems> tables.  It does
325 not expect that C<biblioitems.marcxml> are populated, but it
326 will do so via a call to ModBibiloMarc.
327
328 The goal of this API is to have a similar effect to using AddBiblio
329 and AddItems in succession, but without inefficient repeated
330 parsing of the MARC XML bib record.
331
332 This function returns an arrayref of new itemsnumbers and an arrayref of item
333 errors encountered during the processing.  Each entry in the errors
334 list is a hashref containing the following keys:
335
336 =over
337
338 =item item_sequence
339
340 Sequence number of original item tag in the MARC record.
341
342 =item item_barcode
343
344 Item barcode, provide to assist in the construction of
345 useful error messages.
346
347 =item error_code
348
349 Code representing the error condition.  Can be 'duplicate_barcode',
350 'invalid_homebranch', or 'invalid_holdingbranch'.
351
352 =item error_information
353
354 Additional information appropriate to the error condition.
355
356 =back
357
358 =cut
359
360 sub AddItemBatchFromMarc {
361     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
362     my $error;
363     my @itemnumbers = ();
364     my @errors = ();
365     my $dbh = C4::Context->dbh;
366
367     # We modify the record, so lets work on a clone so we don't change the
368     # original.
369     $record = $record->clone();
370     # loop through the item tags and start creating items
371     my @bad_item_fields = ();
372     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
373     my $item_sequence_num = 0;
374     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
375         $item_sequence_num++;
376         # we take the item field and stick it into a new
377         # MARC record -- this is required so far because (FIXME)
378         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
379         # and there is no TransformMarcFieldToKoha
380         my $temp_item_marc = MARC::Record->new();
381         $temp_item_marc->append_fields($item_field);
382     
383         # add biblionumber and biblioitemnumber
384         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
385         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
386         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
387         $item->{'biblionumber'} = $biblionumber;
388         $item->{'biblioitemnumber'} = $biblioitemnumber;
389
390         # check for duplicate barcode
391         my %item_errors = CheckItemPreSave($item);
392         if (%item_errors) {
393             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
394             push @bad_item_fields, $item_field;
395             next ITEMFIELD;
396         }
397
398         _set_defaults_for_add($item);
399         _set_derived_columns_for_add($item);
400         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
401         warn $error if $error;
402         push @itemnumbers, $itemnumber; # FIXME not checking error
403         $item->{'itemnumber'} = $itemnumber;
404
405         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
406
407         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
408         $item_field->replace_with($new_item_marc->field($itemtag));
409     }
410
411     # remove any MARC item fields for rejected items
412     foreach my $item_field (@bad_item_fields) {
413         $record->delete_field($item_field);
414     }
415
416     # update the MARC biblio
417  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
418
419     return (\@itemnumbers, \@errors);
420 }
421
422 =head2 ModItemFromMarc
423
424   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
425
426 This function updates an item record based on a supplied
427 C<MARC::Record> object containing an embedded item field.
428 This API is meant for the use of C<additem.pl>; for 
429 other purposes, C<ModItem> should be used.
430
431 This function uses the hash %default_values_for_mod_from_marc,
432 which contains default values for item fields to
433 apply when modifying an item.  This is needed because
434 if an item field's value is cleared, TransformMarcToKoha
435 does not include the column in the
436 hash that's passed to ModItem, which without
437 use of this hash makes it impossible to clear
438 an item field's value.  See bug 2466.
439
440 Note that only columns that can be directly
441 changed from the cataloging and serials
442 item editors are included in this hash.
443
444 Returns item record
445
446 =cut
447
448 sub _build_default_values_for_mod_marc {
449     my ($frameworkcode) = @_;
450
451     my $cache     = Koha::Caches->get_instance();
452     my $cache_key = "default_value_for_mod_marc-$frameworkcode";
453     my $cached    = $cache->get_from_cache($cache_key);
454     return $cached if $cached;
455
456     my $default_values = {
457         barcode                  => undef,
458         booksellerid             => undef,
459         ccode                    => undef,
460         'items.cn_source'        => undef,
461         coded_location_qualifier => undef,
462         copynumber               => undef,
463         damaged                  => 0,
464         enumchron                => undef,
465         holdingbranch            => undef,
466         homebranch               => undef,
467         itemcallnumber           => undef,
468         itemlost                 => 0,
469         itemnotes                => undef,
470         itemnotes_nonpublic      => undef,
471         itype                    => undef,
472         location                 => undef,
473         permanent_location       => undef,
474         materials                => undef,
475         new_status               => undef,
476         notforloan               => 0,
477         # paidfor => undef, # commented, see bug 12817
478         price                    => undef,
479         replacementprice         => undef,
480         replacementpricedate     => undef,
481         restricted               => undef,
482         stack                    => undef,
483         stocknumber              => undef,
484         uri                      => undef,
485         withdrawn                => 0,
486     };
487     my %default_values_for_mod_from_marc;
488     while ( my ( $field, $default_value ) = each %$default_values ) {
489         my $kohafield = $field;
490         $kohafield =~ s|^([^\.]+)$|items.$1|;
491         $default_values_for_mod_from_marc{$field} =
492           $default_value
493           if C4::Koha::IsKohaFieldLinked(
494             { kohafield => $kohafield, frameworkcode => $frameworkcode } );
495     }
496
497     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
498     return \%default_values_for_mod_from_marc;
499 }
500
501 sub ModItemFromMarc {
502     my $item_marc = shift;
503     my $biblionumber = shift;
504     my $itemnumber = shift;
505
506     my $dbh           = C4::Context->dbh;
507     my $frameworkcode = GetFrameworkCode($biblionumber);
508     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
509
510     my $localitemmarc = MARC::Record->new;
511     $localitemmarc->append_fields( $item_marc->field($itemtag) );
512     my $item = &TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
513     my $default_values = _build_default_values_for_mod_marc($frameworkcode);
514     foreach my $item_field ( keys %$default_values ) {
515         $item->{$item_field} = $default_values->{$item_field}
516           unless exists $item->{$item_field};
517     }
518     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
519
520     ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
521     return $item;
522 }
523
524 =head2 ModItem
525
526   ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
527
528 Change one or more columns in an item record and update
529 the MARC representation of the item.
530
531 The first argument is a hashref mapping from item column
532 names to the new values.  The second and third arguments
533 are the biblionumber and itemnumber, respectively.
534
535 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
536 an arrayref containing subfields present in the original MARC
537 representation of the item (e.g., from the item editor) that are
538 not mapped to C<items> columns directly but should instead
539 be stored in C<items.more_subfields_xml> and included in 
540 the biblio items tag for display and indexing.
541
542 If one of the changed columns is used to calculate
543 the derived value of a column such as C<items.cn_sort>, 
544 this routine will perform the necessary calculation
545 and set the value.
546
547 =cut
548
549 sub ModItem {
550     my $item = shift;
551     my $biblionumber = shift;
552     my $itemnumber = shift;
553
554     # if $biblionumber is undefined, get it from the current item
555     unless (defined $biblionumber) {
556         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
557     }
558
559     my $dbh           = @_ ? shift : C4::Context->dbh;
560     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
561     
562     my $unlinked_item_subfields;  
563     if (@_) {
564         $unlinked_item_subfields = shift;
565         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
566     };
567
568     $item->{'itemnumber'} = $itemnumber or return;
569
570     my @fields = qw( itemlost withdrawn );
571
572     # Only call GetItem if we need to set an "on" date field
573     if ( $item->{itemlost} || $item->{withdrawn} ) {
574         my $pre_mod_item = GetItem( $item->{'itemnumber'} );
575         for my $field (@fields) {
576             if (    defined( $item->{$field} )
577                 and not $pre_mod_item->{$field}
578                 and $item->{$field} )
579             {
580                 $item->{ $field . '_on' } =
581                   DateTime::Format::MySQL->format_datetime( dt_from_string() );
582             }
583         }
584     }
585
586     # If the field is defined but empty, we are removing and,
587     # and thus need to clear out the 'on' field as well
588     for my $field (@fields) {
589         if ( defined( $item->{$field} ) && !$item->{$field} ) {
590             $item->{ $field . '_on' } = undef;
591         }
592     }
593
594
595     _set_derived_columns_for_mod($item);
596     _do_column_fixes_for_mod($item);
597     # FIXME add checks
598     # duplicate barcode
599     # attempt to change itemnumber
600     # attempt to change biblionumber (if we want
601     # an API to relink an item to a different bib,
602     # it should be a separate function)
603
604     # update items table
605     _koha_modify_item($item);
606
607     # request that bib be reindexed so that searching on current
608     # item status is possible
609     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
610
611     logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
612 }
613
614 =head2 ModItemTransfer
615
616   ModItemTransfer($itenumber, $frombranch, $tobranch);
617
618 Marks an item as being transferred from one branch
619 to another.
620
621 =cut
622
623 sub ModItemTransfer {
624     my ( $itemnumber, $frombranch, $tobranch ) = @_;
625
626     my $dbh = C4::Context->dbh;
627
628     # Remove the 'shelving cart' location status if it is being used.
629     CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
630
631     #new entry in branchtransfers....
632     my $sth = $dbh->prepare(
633         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
634         VALUES (?, ?, NOW(), ?)");
635     $sth->execute($itemnumber, $frombranch, $tobranch);
636
637     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
638     ModDateLastSeen($itemnumber);
639     return;
640 }
641
642 =head2 ModDateLastSeen
643
644   ModDateLastSeen($itemnum);
645
646 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
647 C<$itemnum> is the item number
648
649 =cut
650
651 sub ModDateLastSeen {
652     my ($itemnumber) = @_;
653     
654     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
655     ModItem({ itemlost => 0, datelastseen => $today }, undef, $itemnumber);
656 }
657
658 =head2 DelItem
659
660   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
661
662 Exported function (core API) for deleting an item record in Koha.
663
664 =cut
665
666 sub DelItem {
667     my ( $params ) = @_;
668
669     my $itemnumber   = $params->{itemnumber};
670     my $biblionumber = $params->{biblionumber};
671
672     unless ($biblionumber) {
673         $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
674     }
675
676     # If there is no biblionumber for the given itemnumber, there is nothing to delete
677     return 0 unless $biblionumber;
678
679     # FIXME check the item has no current issues
680     my $deleted = _koha_delete_item( $itemnumber );
681
682     # get the MARC record
683     my $record = GetMarcBiblio($biblionumber);
684     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
685
686     #search item field code
687     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
688     return $deleted;
689 }
690
691 =head2 CheckItemPreSave
692
693     my $item_ref = TransformMarcToKoha($marc, 'items');
694     # do stuff
695     my %errors = CheckItemPreSave($item_ref);
696     if (exists $errors{'duplicate_barcode'}) {
697         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
698     } elsif (exists $errors{'invalid_homebranch'}) {
699         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
700     } elsif (exists $errors{'invalid_holdingbranch'}) {
701         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
702     } else {
703         print "item is OK";
704     }
705
706 Given a hashref containing item fields, determine if it can be
707 inserted or updated in the database.  Specifically, checks for
708 database integrity issues, and returns a hash containing any
709 of the following keys, if applicable.
710
711 =over 2
712
713 =item duplicate_barcode
714
715 Barcode, if it duplicates one already found in the database.
716
717 =item invalid_homebranch
718
719 Home branch, if not defined in branches table.
720
721 =item invalid_holdingbranch
722
723 Holding branch, if not defined in branches table.
724
725 =back
726
727 This function does NOT implement any policy-related checks,
728 e.g., whether current operator is allowed to save an
729 item that has a given branch code.
730
731 =cut
732
733 sub CheckItemPreSave {
734     my $item_ref = shift;
735
736     my %errors = ();
737
738     # check for duplicate barcode
739     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
740         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
741         if ($existing_itemnumber) {
742             if (!exists $item_ref->{'itemnumber'}                       # new item
743                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
744                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
745             }
746         }
747     }
748
749     # check for valid home branch
750     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
751         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
752         unless (defined $home_library) {
753             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
754         }
755     }
756
757     # check for valid holding branch
758     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
759         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
760         unless (defined $holding_library) {
761             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
762         }
763     }
764
765     return %errors;
766
767 }
768
769 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
770
771 The following functions provide various ways of 
772 getting an item record, a set of item records, or
773 lists of authorized values for certain item fields.
774
775 Some of the functions in this group are candidates
776 for refactoring -- for example, some of the code
777 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
778 has copy-and-paste work.
779
780 =cut
781
782 =head2 GetItemStatus
783
784   $itemstatushash = GetItemStatus($fwkcode);
785
786 Returns a list of valid values for the
787 C<items.notforloan> field.
788
789 NOTE: does B<not> return an individual item's
790 status.
791
792 Can be MARC dependent.
793 fwkcode is optional.
794 But basically could be can be loan or not
795 Create a status selector with the following code
796
797 =head3 in PERL SCRIPT
798
799  my $itemstatushash = getitemstatus;
800  my @itemstatusloop;
801  foreach my $thisstatus (keys %$itemstatushash) {
802      my %row =(value => $thisstatus,
803                  statusname => $itemstatushash->{$thisstatus}->{'statusname'},
804              );
805      push @itemstatusloop, \%row;
806  }
807  $template->param(statusloop=>\@itemstatusloop);
808
809 =head3 in TEMPLATE
810
811 <select name="statusloop" id="statusloop">
812     <option value="">Default</option>
813     [% FOREACH statusloo IN statusloop %]
814         [% IF ( statusloo.selected ) %]
815             <option value="[% statusloo.value %]" selected="selected">[% statusloo.statusname %]</option>
816         [% ELSE %]
817             <option value="[% statusloo.value %]">[% statusloo.statusname %]</option>
818         [% END %]
819     [% END %]
820 </select>
821
822 =cut
823
824 sub GetItemStatus {
825
826     # returns a reference to a hash of references to status...
827     my ($fwk) = @_;
828     my %itemstatus;
829     my $dbh = C4::Context->dbh;
830     my $sth;
831     $fwk = '' unless ($fwk);
832     my ( $tag, $subfield ) =
833       GetMarcFromKohaField( "items.notforloan", $fwk );
834     if ( $tag and $subfield ) {
835         my $sth =
836           $dbh->prepare(
837             "SELECT authorised_value
838             FROM marc_subfield_structure
839             WHERE tagfield=?
840                 AND tagsubfield=?
841                 AND frameworkcode=?
842             "
843           );
844         $sth->execute( $tag, $subfield, $fwk );
845         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
846             my $authvalsth =
847               $dbh->prepare(
848                 "SELECT authorised_value,lib
849                 FROM authorised_values 
850                 WHERE category=? 
851                 ORDER BY lib
852                 "
853               );
854             $authvalsth->execute($authorisedvaluecat);
855             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
856                 $itemstatus{$authorisedvalue} = $lib;
857             }
858             return \%itemstatus;
859         }
860         else {
861
862             #No authvalue list
863             # build default
864         }
865     }
866
867     #No authvalue list
868     #build default
869     $itemstatus{"1"} = "Not For Loan";
870     return \%itemstatus;
871 }
872
873 =head2 GetItemLocation
874
875   $itemlochash = GetItemLocation($fwk);
876
877 Returns a list of valid values for the
878 C<items.location> field.
879
880 NOTE: does B<not> return an individual item's
881 location.
882
883 where fwk stands for an optional framework code.
884 Create a location selector with the following code
885
886 =head3 in PERL SCRIPT
887
888   my $itemlochash = getitemlocation;
889   my @itemlocloop;
890   foreach my $thisloc (keys %$itemlochash) {
891       my $selected = 1 if $thisbranch eq $branch;
892       my %row =(locval => $thisloc,
893                   selected => $selected,
894                   locname => $itemlochash->{$thisloc},
895                );
896       push @itemlocloop, \%row;
897   }
898   $template->param(itemlocationloop => \@itemlocloop);
899
900 =head3 in TEMPLATE
901
902   <select name="location">
903       <option value="">Default</option>
904   <!-- TMPL_LOOP name="itemlocationloop" -->
905       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
906   <!-- /TMPL_LOOP -->
907   </select>
908
909 =cut
910
911 sub GetItemLocation {
912
913     # returns a reference to a hash of references to location...
914     my ($fwk) = @_;
915     my %itemlocation;
916     my $dbh = C4::Context->dbh;
917     my $sth;
918     $fwk = '' unless ($fwk);
919     my ( $tag, $subfield ) =
920       GetMarcFromKohaField( "items.location", $fwk );
921     if ( $tag and $subfield ) {
922         my $sth =
923           $dbh->prepare(
924             "SELECT authorised_value
925             FROM marc_subfield_structure 
926             WHERE tagfield=? 
927                 AND tagsubfield=? 
928                 AND frameworkcode=?"
929           );
930         $sth->execute( $tag, $subfield, $fwk );
931         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
932             my $authvalsth =
933               $dbh->prepare(
934                 "SELECT authorised_value,lib
935                 FROM authorised_values
936                 WHERE category=?
937                 ORDER BY lib"
938               );
939             $authvalsth->execute($authorisedvaluecat);
940             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
941                 $itemlocation{$authorisedvalue} = $lib;
942             }
943             return \%itemlocation;
944         }
945         else {
946
947             #No authvalue list
948             # build default
949         }
950     }
951
952     #No authvalue list
953     #build default
954     $itemlocation{"1"} = "Not For Loan";
955     return \%itemlocation;
956 }
957
958 =head2 GetLostItems
959
960   $items = GetLostItems( $where );
961
962 This function gets a list of lost items.
963
964 =over 2
965
966 =item input:
967
968 C<$where> is a hashref. it containts a field of the items table as key
969 and the value to match as value. For example:
970
971 { barcode    => 'abc123',
972   homebranch => 'CPL',    }
973
974 =item return:
975
976 C<$items> is a reference to an array full of hashrefs with columns
977 from the "items" table as keys.
978
979 =item usage in the perl script:
980
981   my $where = { barcode => '0001548' };
982   my $items = GetLostItems( $where );
983   $template->param( itemsloop => $items );
984
985 =back
986
987 =cut
988
989 sub GetLostItems {
990     # Getting input args.
991     my $where   = shift;
992     my $dbh     = C4::Context->dbh;
993
994     my $query   = "
995         SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
996                itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber
997         FROM   items
998             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
999             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
1000             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
1001         WHERE
1002                 authorised_values.category = 'LOST'
1003                 AND itemlost IS NOT NULL
1004                 AND itemlost <> 0
1005     ";
1006     my @query_parameters;
1007     foreach my $key (keys %$where) {
1008         $query .= " AND $key LIKE ?";
1009         push @query_parameters, "%$where->{$key}%";
1010     }
1011
1012     my $sth = $dbh->prepare($query);
1013     $sth->execute( @query_parameters );
1014     my $items = [];
1015     while ( my $row = $sth->fetchrow_hashref ){
1016         push @$items, $row;
1017     }
1018     return $items;
1019 }
1020
1021 =head2 GetItemsForInventory
1022
1023 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
1024   minlocation  => $minlocation,
1025   maxlocation  => $maxlocation,
1026   location     => $location,
1027   itemtype     => $itemtype,
1028   ignoreissued => $ignoreissued,
1029   datelastseen => $datelastseen,
1030   branchcode   => $branchcode,
1031   branch       => $branch,
1032   offset       => $offset,
1033   size         => $size,
1034   statushash   => $statushash,
1035 } );
1036
1037 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
1038
1039 The sub returns a reference to a list of hashes, each containing
1040 itemnumber, author, title, barcode, item callnumber, and date last
1041 seen. It is ordered by callnumber then title.
1042
1043 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
1044 the datelastseen can be used to specify that you want to see items not seen since a past date only.
1045 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
1046 $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.
1047
1048 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
1049
1050 =cut
1051
1052 sub GetItemsForInventory {
1053     my ( $parameters ) = @_;
1054     my $minlocation  = $parameters->{'minlocation'}  // '';
1055     my $maxlocation  = $parameters->{'maxlocation'}  // '';
1056     my $location     = $parameters->{'location'}     // '';
1057     my $itemtype     = $parameters->{'itemtype'}     // '';
1058     my $ignoreissued = $parameters->{'ignoreissued'} // '';
1059     my $datelastseen = $parameters->{'datelastseen'} // '';
1060     my $branchcode   = $parameters->{'branchcode'}   // '';
1061     my $branch       = $parameters->{'branch'}       // '';
1062     my $offset       = $parameters->{'offset'}       // '';
1063     my $size         = $parameters->{'size'}         // '';
1064     my $statushash   = $parameters->{'statushash'}   // '';
1065
1066     my $dbh = C4::Context->dbh;
1067     my ( @bind_params, @where_strings );
1068
1069     my $select_columns = q{
1070         SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
1071     };
1072     my $select_count = q{SELECT COUNT(*)};
1073     my $query = q{
1074         FROM items
1075         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
1076         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1077     };
1078     if ($statushash){
1079         for my $authvfield (keys %$statushash){
1080             if ( scalar @{$statushash->{$authvfield}} > 0 ){
1081                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1082                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1083             }
1084         }
1085     }
1086
1087     if ($minlocation) {
1088         push @where_strings, 'itemcallnumber >= ?';
1089         push @bind_params, $minlocation;
1090     }
1091
1092     if ($maxlocation) {
1093         push @where_strings, 'itemcallnumber <= ?';
1094         push @bind_params, $maxlocation;
1095     }
1096
1097     if ($datelastseen) {
1098         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
1099         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1100         push @bind_params, $datelastseen;
1101     }
1102
1103     if ( $location ) {
1104         push @where_strings, 'items.location = ?';
1105         push @bind_params, $location;
1106     }
1107
1108     if ( $branchcode ) {
1109         if($branch eq "homebranch"){
1110         push @where_strings, 'items.homebranch = ?';
1111         }else{
1112             push @where_strings, 'items.holdingbranch = ?';
1113         }
1114         push @bind_params, $branchcode;
1115     }
1116
1117     if ( $itemtype ) {
1118         push @where_strings, 'biblioitems.itemtype = ?';
1119         push @bind_params, $itemtype;
1120     }
1121
1122     if ( $ignoreissued) {
1123         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1124         push @where_strings, 'issues.date_due IS NULL';
1125     }
1126
1127     if ( @where_strings ) {
1128         $query .= 'WHERE ';
1129         $query .= join ' AND ', @where_strings;
1130     }
1131     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1132     my $count_query = $select_count . $query;
1133     $query .= " LIMIT $offset, $size" if ($offset and $size);
1134     $query = $select_columns . $query;
1135     my $sth = $dbh->prepare($query);
1136     $sth->execute( @bind_params );
1137
1138     my @results = ();
1139     my $tmpresults = $sth->fetchall_arrayref({});
1140     $sth = $dbh->prepare( $count_query );
1141     $sth->execute( @bind_params );
1142     my ($iTotalRecords) = $sth->fetchrow_array();
1143
1144     my @avs = Koha::AuthorisedValues->search(
1145         {   'marc_subfield_structures.kohafield' => { '>' => '' },
1146             'me.authorised_value'                => { '>' => '' },
1147         },
1148         {   join     => { category => 'marc_subfield_structures' },
1149             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
1150             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
1151             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
1152         }
1153     );
1154
1155     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
1156
1157     foreach my $row (@$tmpresults) {
1158
1159         # Auth values
1160         foreach (keys %$row) {
1161             if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
1162                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
1163             }
1164         }
1165         push @results, $row;
1166     }
1167
1168     return (\@results, $iTotalRecords);
1169 }
1170
1171 =head2 GetItemsCount
1172
1173   $count = &GetItemsCount( $biblionumber);
1174
1175 This function return count of item with $biblionumber
1176
1177 =cut
1178
1179 sub GetItemsCount {
1180     my ( $biblionumber ) = @_;
1181     my $dbh = C4::Context->dbh;
1182     my $query = "SELECT count(*)
1183           FROM  items 
1184           WHERE biblionumber=?";
1185     my $sth = $dbh->prepare($query);
1186     $sth->execute($biblionumber);
1187     my $count = $sth->fetchrow;  
1188     return ($count);
1189 }
1190
1191 =head2 GetItemInfosOf
1192
1193   GetItemInfosOf(@itemnumbers);
1194
1195 =cut
1196
1197 sub GetItemInfosOf {
1198     my @itemnumbers = @_;
1199
1200     my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1201
1202     my $query = "
1203         SELECT *
1204         FROM items
1205         WHERE itemnumber IN ($itemnumber_values)
1206     ";
1207     return get_infos_of( $query, 'itemnumber' );
1208 }
1209
1210 =head2 GetItemsByBiblioitemnumber
1211
1212   GetItemsByBiblioitemnumber($biblioitemnumber);
1213
1214 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1215 Called by C<C4::XISBN>
1216
1217 =cut
1218
1219 sub GetItemsByBiblioitemnumber {
1220     my ( $bibitem ) = @_;
1221     my $dbh = C4::Context->dbh;
1222     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1223     # Get all items attached to a biblioitem
1224     my $i = 0;
1225     my @results; 
1226     $sth->execute($bibitem) || die $sth->errstr;
1227     while ( my $data = $sth->fetchrow_hashref ) {  
1228         # Foreach item, get circulation information
1229         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1230                                    WHERE itemnumber = ?
1231                                    AND issues.borrowernumber = borrowers.borrowernumber"
1232         );
1233         $sth2->execute( $data->{'itemnumber'} );
1234         if ( my $data2 = $sth2->fetchrow_hashref ) {
1235             # if item is out, set the due date and who it is out too
1236             $data->{'date_due'}   = $data2->{'date_due'};
1237             $data->{'cardnumber'} = $data2->{'cardnumber'};
1238             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1239         }
1240         else {
1241             # set date_due to blank, so in the template we check itemlost, and withdrawn
1242             $data->{'date_due'} = '';                                                                                                         
1243         }    # else         
1244         # Find the last 3 people who borrowed this item.                  
1245         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1246                       AND old_issues.borrowernumber = borrowers.borrowernumber
1247                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1248         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1249         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1250         my $i2 = 0;
1251         while ( my $data2 = $sth2->fetchrow_hashref ) {
1252             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1253             $data->{"card$i2"}      = $data2->{'cardnumber'};
1254             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1255             $i2++;
1256         }
1257         push(@results,$data);
1258     } 
1259     return (\@results); 
1260 }
1261
1262 =head2 GetItemsInfo
1263
1264   @results = GetItemsInfo($biblionumber);
1265
1266 Returns information about items with the given biblionumber.
1267
1268 C<GetItemsInfo> returns a list of references-to-hash. Each element
1269 contains a number of keys. Most of them are attributes from the
1270 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1271 Koha database. Other keys include:
1272
1273 =over 2
1274
1275 =item C<$data-E<gt>{branchname}>
1276
1277 The name (not the code) of the branch to which the book belongs.
1278
1279 =item C<$data-E<gt>{datelastseen}>
1280
1281 This is simply C<items.datelastseen>, except that while the date is
1282 stored in YYYY-MM-DD format in the database, here it is converted to
1283 DD/MM/YYYY format. A NULL date is returned as C<//>.
1284
1285 =item C<$data-E<gt>{datedue}>
1286
1287 =item C<$data-E<gt>{class}>
1288
1289 This is the concatenation of C<biblioitems.classification>, the book's
1290 Dewey code, and C<biblioitems.subclass>.
1291
1292 =item C<$data-E<gt>{ocount}>
1293
1294 I think this is the number of copies of the book available.
1295
1296 =item C<$data-E<gt>{order}>
1297
1298 If this is set, it is set to C<One Order>.
1299
1300 =back
1301
1302 =cut
1303
1304 sub GetItemsInfo {
1305     my ( $biblionumber ) = @_;
1306     my $dbh   = C4::Context->dbh;
1307     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1308     require C4::Languages;
1309     my $language = C4::Languages::getlanguage();
1310     my $query = "
1311     SELECT items.*,
1312            biblio.*,
1313            biblioitems.volume,
1314            biblioitems.number,
1315            biblioitems.itemtype,
1316            biblioitems.isbn,
1317            biblioitems.issn,
1318            biblioitems.publicationyear,
1319            biblioitems.publishercode,
1320            biblioitems.volumedate,
1321            biblioitems.volumedesc,
1322            biblioitems.lccn,
1323            biblioitems.url,
1324            items.notforloan as itemnotforloan,
1325            issues.borrowernumber,
1326            issues.date_due as datedue,
1327            issues.onsite_checkout,
1328            borrowers.cardnumber,
1329            borrowers.surname,
1330            borrowers.firstname,
1331            borrowers.branchcode as bcode,
1332            serial.serialseq,
1333            serial.publisheddate,
1334            itemtypes.description,
1335            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
1336            itemtypes.notforloan as notforloan_per_itemtype,
1337            holding.branchurl,
1338            holding.branchcode,
1339            holding.branchname,
1340            holding.opac_info as holding_branch_opac_info,
1341            home.opac_info as home_branch_opac_info
1342     ";
1343     $query .= "
1344      FROM items
1345      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1346      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1347      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1348      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1349      LEFT JOIN issues USING (itemnumber)
1350      LEFT JOIN borrowers USING (borrowernumber)
1351      LEFT JOIN serialitems USING (itemnumber)
1352      LEFT JOIN serial USING (serialid)
1353      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1354      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1355     $query .= q|
1356     LEFT JOIN localization ON itemtypes.itemtype = localization.code
1357         AND localization.entity = 'itemtypes'
1358         AND localization.lang = ?
1359     |;
1360
1361     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
1362     my $sth = $dbh->prepare($query);
1363     $sth->execute($language, $biblionumber);
1364     my $i = 0;
1365     my @results;
1366     my $serial;
1367
1368     my $userenv = C4::Context->userenv;
1369     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
1370     while ( my $data = $sth->fetchrow_hashref ) {
1371         if ( $data->{borrowernumber} && $want_not_same_branch) {
1372             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
1373         }
1374
1375         $serial ||= $data->{'serial'};
1376
1377         my $av;
1378         # get notforloan complete status if applicable
1379         $av = Koha::AuthorisedValues->search_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
1380         $av = $av->count ? $av->next : undef;
1381         $data->{notforloanvalue}     = $av ? $av->lib : '';
1382         $data->{notforloanvalueopac} = $av ? $av->opac_description : '';
1383
1384         # get restricted status and description if applicable
1385         $av = Koha::AuthorisedValues->search_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
1386         $av = $av->count ? $av->next : undef;
1387         $data->{restricted}     = $av ? $av->lib : '';
1388         $data->{restrictedopac} = $av ? $av->opac_description : '';
1389
1390         # my stack procedures
1391         $av = Koha::AuthorisedValues->search_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
1392         $data->{stack}          = $av->count ? $av->next->lib : '';
1393
1394         # Find the last 3 people who borrowed this item.
1395         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1396                                     WHERE itemnumber = ?
1397                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1398                                     ORDER BY returndate DESC
1399                                     LIMIT 3");
1400         $sth2->execute($data->{'itemnumber'});
1401         my $ii = 0;
1402         while (my $data2 = $sth2->fetchrow_hashref()) {
1403             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1404             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1405             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1406             $ii++;
1407         }
1408
1409         $results[$i] = $data;
1410         $i++;
1411     }
1412
1413     return $serial
1414         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
1415         : @results;
1416 }
1417
1418 =head2 GetItemsLocationInfo
1419
1420   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1421
1422 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1423
1424 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1425
1426 =over 2
1427
1428 =item C<$data-E<gt>{homebranch}>
1429
1430 Branch Name of the item's homebranch
1431
1432 =item C<$data-E<gt>{holdingbranch}>
1433
1434 Branch Name of the item's holdingbranch
1435
1436 =item C<$data-E<gt>{location}>
1437
1438 Item's shelving location code
1439
1440 =item C<$data-E<gt>{location_intranet}>
1441
1442 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1443
1444 =item C<$data-E<gt>{location_opac}>
1445
1446 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1447 description is set.
1448
1449 =item C<$data-E<gt>{itemcallnumber}>
1450
1451 Item's itemcallnumber
1452
1453 =item C<$data-E<gt>{cn_sort}>
1454
1455 Item's call number normalized for sorting
1456
1457 =back
1458   
1459 =cut
1460
1461 sub GetItemsLocationInfo {
1462         my $biblionumber = shift;
1463         my @results;
1464
1465         my $dbh = C4::Context->dbh;
1466         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1467                             location, itemcallnumber, cn_sort
1468                      FROM items, branches as a, branches as b
1469                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1470                      AND biblionumber = ?
1471                      ORDER BY cn_sort ASC";
1472         my $sth = $dbh->prepare($query);
1473         $sth->execute($biblionumber);
1474
1475         while ( my $data = $sth->fetchrow_hashref ) {
1476              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
1477              $av = $av->count ? $av->next : undef;
1478              $data->{location_intranet} = $av ? $av->lib : '';
1479              $data->{location_opac}     = $av ? $av->opac_description : '';
1480              push @results, $data;
1481         }
1482         return @results;
1483 }
1484
1485 =head2 GetHostItemsInfo
1486
1487         $hostiteminfo = GetHostItemsInfo($hostfield);
1488         Returns the iteminfo for items linked to records via a host field
1489
1490 =cut
1491
1492 sub GetHostItemsInfo {
1493         my ($record) = @_;
1494         my @returnitemsInfo;
1495
1496         if (C4::Context->preference('marcflavour') eq 'MARC21' ||
1497         C4::Context->preference('marcflavour') eq 'NORMARC'){
1498             foreach my $hostfield ( $record->field('773') ) {
1499                 my $hostbiblionumber = $hostfield->subfield("0");
1500                 my $linkeditemnumber = $hostfield->subfield("9");
1501                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1502                 foreach my $hostitemInfo (@hostitemInfos){
1503                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1504                                 push (@returnitemsInfo,$hostitemInfo);
1505                                 last;
1506                         }
1507                 }
1508             }
1509         } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){
1510             foreach my $hostfield ( $record->field('461') ) {
1511                 my $hostbiblionumber = $hostfield->subfield("0");
1512                 my $linkeditemnumber = $hostfield->subfield("9");
1513                 my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1514                 foreach my $hostitemInfo (@hostitemInfos){
1515                         if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){
1516                                 push (@returnitemsInfo,$hostitemInfo);
1517                                 last;
1518                         }
1519                 }
1520             }
1521         }
1522         return @returnitemsInfo;
1523 }
1524
1525
1526 =head2 GetLastAcquisitions
1527
1528   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1529                                     'itemtypes' => ('BK','BD')}, 10);
1530
1531 =cut
1532
1533 sub  GetLastAcquisitions {
1534         my ($data,$max) = @_;
1535
1536         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1537         
1538         my $number_of_branches = @{$data->{branches}};
1539         my $number_of_itemtypes   = @{$data->{itemtypes}};
1540         
1541         
1542         my @where = ('WHERE 1 '); 
1543         $number_of_branches and push @where
1544            , 'AND holdingbranch IN (' 
1545            , join(',', ('?') x $number_of_branches )
1546            , ')'
1547          ;
1548         
1549         $number_of_itemtypes and push @where
1550            , "AND $itemtype IN (" 
1551            , join(',', ('?') x $number_of_itemtypes )
1552            , ')'
1553          ;
1554
1555         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1556                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1557                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1558                                     @where
1559                                     GROUP BY biblio.biblionumber 
1560                                     ORDER BY dateaccessioned DESC LIMIT $max";
1561
1562         my $dbh = C4::Context->dbh;
1563         my $sth = $dbh->prepare($query);
1564     
1565     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1566         
1567         my @results;
1568         while( my $row = $sth->fetchrow_hashref){
1569                 push @results, {date => $row->{dateaccessioned} 
1570                                                 , biblionumber => $row->{biblionumber}
1571                                                 , title => $row->{title}};
1572         }
1573         
1574         return @results;
1575 }
1576
1577 =head2 GetItemnumbersForBiblio
1578
1579   my $itemnumbers = GetItemnumbersForBiblio($biblionumber);
1580
1581 Given a single biblionumber, return an arrayref of all the corresponding itemnumbers
1582
1583 =cut
1584
1585 sub GetItemnumbersForBiblio {
1586     my $biblionumber = shift;
1587     my @items;
1588     my $dbh = C4::Context->dbh;
1589     my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?");
1590     $sth->execute($biblionumber);
1591     while (my $result = $sth->fetchrow_hashref) {
1592         push @items, $result->{'itemnumber'};
1593     }
1594     return \@items;
1595 }
1596
1597 =head2 get_itemnumbers_of
1598
1599   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1600
1601 Given a list of biblionumbers, return the list of corresponding itemnumbers
1602 for each biblionumber.
1603
1604 Return a reference on a hash where keys are biblionumbers and values are
1605 references on array of itemnumbers.
1606
1607 =cut
1608
1609 sub get_itemnumbers_of {
1610     my @biblionumbers = @_;
1611
1612     my $dbh = C4::Context->dbh;
1613
1614     my $query = '
1615         SELECT itemnumber,
1616             biblionumber
1617         FROM items
1618         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1619     ';
1620     my $sth = $dbh->prepare($query);
1621     $sth->execute(@biblionumbers);
1622
1623     my %itemnumbers_of;
1624
1625     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1626         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1627     }
1628
1629     return \%itemnumbers_of;
1630 }
1631
1632 =head2 get_hostitemnumbers_of
1633
1634   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1635
1636 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1637
1638 Return a reference on a hash where key is a biblionumber and values are
1639 references on array of itemnumbers.
1640
1641 =cut
1642
1643
1644 sub get_hostitemnumbers_of {
1645     my ($biblionumber) = @_;
1646     my $marcrecord = GetMarcBiblio($biblionumber);
1647
1648     return unless $marcrecord;
1649
1650     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1651
1652     my $marcflavor = C4::Context->preference('marcflavour');
1653     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1654         $tag      = '773';
1655         $biblio_s = '0';
1656         $item_s   = '9';
1657     }
1658     elsif ( $marcflavor eq 'UNIMARC' ) {
1659         $tag      = '461';
1660         $biblio_s = '0';
1661         $item_s   = '9';
1662     }
1663
1664     foreach my $hostfield ( $marcrecord->field($tag) ) {
1665         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1666         my $linkeditemnumber = $hostfield->subfield($item_s);
1667         my @itemnumbers;
1668         if ( my $itemnumbers =
1669             get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber} )
1670         {
1671             @itemnumbers = @$itemnumbers;
1672         }
1673         foreach my $itemnumber (@itemnumbers) {
1674             if ( $itemnumber eq $linkeditemnumber ) {
1675                 push( @returnhostitemnumbers, $itemnumber );
1676                 last;
1677             }
1678         }
1679     }
1680
1681     return @returnhostitemnumbers;
1682 }
1683
1684
1685 =head2 GetItemnumberFromBarcode
1686
1687   $result = GetItemnumberFromBarcode($barcode);
1688
1689 =cut
1690
1691 sub GetItemnumberFromBarcode {
1692     my ($barcode) = @_;
1693     my $dbh = C4::Context->dbh;
1694
1695     my $rq =
1696       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1697     $rq->execute($barcode);
1698     my ($result) = $rq->fetchrow;
1699     return ($result);
1700 }
1701
1702 =head2 GetBarcodeFromItemnumber
1703
1704   $result = GetBarcodeFromItemnumber($itemnumber);
1705
1706 =cut
1707
1708 sub GetBarcodeFromItemnumber {
1709     my ($itemnumber) = @_;
1710     my $dbh = C4::Context->dbh;
1711
1712     my $rq =
1713       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1714     $rq->execute($itemnumber);
1715     my ($result) = $rq->fetchrow;
1716     return ($result);
1717 }
1718
1719 =head2 GetHiddenItemnumbers
1720
1721     my @itemnumbers_to_hide = GetHiddenItemnumbers(@items);
1722
1723 Given a list of items it checks which should be hidden from the OPAC given
1724 the current configuration. Returns a list of itemnumbers corresponding to
1725 those that should be hidden.
1726
1727 =cut
1728
1729 sub GetHiddenItemnumbers {
1730     my (@items) = @_;
1731     my @resultitems;
1732
1733     my $yaml = C4::Context->preference('OpacHiddenItems');
1734     return () if (! $yaml =~ /\S/ );
1735     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1736     my $hidingrules;
1737     eval {
1738         $hidingrules = YAML::Load($yaml);
1739     };
1740     if ($@) {
1741         warn "Unable to parse OpacHiddenItems syspref : $@";
1742         return ();
1743     }
1744     my $dbh = C4::Context->dbh;
1745
1746     # For each item
1747     foreach my $item (@items) {
1748
1749         # We check each rule
1750         foreach my $field (keys %$hidingrules) {
1751             my $val;
1752             if (exists $item->{$field}) {
1753                 $val = $item->{$field};
1754             }
1755             else {
1756                 my $query = "SELECT $field from items where itemnumber = ?";
1757                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1758             }
1759             $val = '' unless defined $val;
1760
1761             # If the results matches the values in the yaml file
1762             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1763
1764                 # We add the itemnumber to the list
1765                 push @resultitems, $item->{'itemnumber'};
1766
1767                 # If at least one rule matched for an item, no need to test the others
1768                 last;
1769             }
1770         }
1771     }
1772     return @resultitems;
1773 }
1774
1775 =head1 LIMITED USE FUNCTIONS
1776
1777 The following functions, while part of the public API,
1778 are not exported.  This is generally because they are
1779 meant to be used by only one script for a specific
1780 purpose, and should not be used in any other context
1781 without careful thought.
1782
1783 =cut
1784
1785 =head2 GetMarcItem
1786
1787   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1788
1789 Returns MARC::Record of the item passed in parameter.
1790 This function is meant for use only in C<cataloguing/additem.pl>,
1791 where it is needed to support that script's MARC-like
1792 editor.
1793
1794 =cut
1795
1796 sub GetMarcItem {
1797     my ( $biblionumber, $itemnumber ) = @_;
1798
1799     # GetMarcItem has been revised so that it does the following:
1800     #  1. Gets the item information from the items table.
1801     #  2. Converts it to a MARC field for storage in the bib record.
1802     #
1803     # The previous behavior was:
1804     #  1. Get the bib record.
1805     #  2. Return the MARC tag corresponding to the item record.
1806     #
1807     # The difference is that one treats the items row as authoritative,
1808     # while the other treats the MARC representation as authoritative
1809     # under certain circumstances.
1810
1811     my $itemrecord = GetItem($itemnumber);
1812
1813     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1814     # Also, don't emit a subfield if the underlying field is blank.
1815
1816     
1817     return Item2Marc($itemrecord,$biblionumber);
1818
1819 }
1820 sub Item2Marc {
1821         my ($itemrecord,$biblionumber)=@_;
1822     my $mungeditem = { 
1823         map {  
1824             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1825         } keys %{ $itemrecord } 
1826     };
1827     my $itemmarc = TransformKohaToMarc($mungeditem);
1828     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1829
1830     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1831     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1832                 foreach my $field ($itemmarc->field($itemtag)){
1833             $field->add_subfields(@$unlinked_item_subfields);
1834         }
1835     }
1836         return $itemmarc;
1837 }
1838
1839 =head1 PRIVATE FUNCTIONS AND VARIABLES
1840
1841 The following functions are not meant to be called
1842 directly, but are documented in order to explain
1843 the inner workings of C<C4::Items>.
1844
1845 =cut
1846
1847 =head2 %derived_columns
1848
1849 This hash keeps track of item columns that
1850 are strictly derived from other columns in
1851 the item record and are not meant to be set
1852 independently.
1853
1854 Each key in the hash should be the name of a
1855 column (as named by TransformMarcToKoha).  Each
1856 value should be hashref whose keys are the
1857 columns on which the derived column depends.  The
1858 hashref should also contain a 'BUILDER' key
1859 that is a reference to a sub that calculates
1860 the derived value.
1861
1862 =cut
1863
1864 my %derived_columns = (
1865     'items.cn_sort' => {
1866         'itemcallnumber' => 1,
1867         'items.cn_source' => 1,
1868         'BUILDER' => \&_calc_items_cn_sort,
1869     }
1870 );
1871
1872 =head2 _set_derived_columns_for_add 
1873
1874   _set_derived_column_for_add($item);
1875
1876 Given an item hash representing a new item to be added,
1877 calculate any derived columns.  Currently the only
1878 such column is C<items.cn_sort>.
1879
1880 =cut
1881
1882 sub _set_derived_columns_for_add {
1883     my $item = shift;
1884
1885     foreach my $column (keys %derived_columns) {
1886         my $builder = $derived_columns{$column}->{'BUILDER'};
1887         my $source_values = {};
1888         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1889             next if $source_column eq 'BUILDER';
1890             $source_values->{$source_column} = $item->{$source_column};
1891         }
1892         $builder->($item, $source_values);
1893     }
1894 }
1895
1896 =head2 _set_derived_columns_for_mod 
1897
1898   _set_derived_column_for_mod($item);
1899
1900 Given an item hash representing a new item to be modified.
1901 calculate any derived columns.  Currently the only
1902 such column is C<items.cn_sort>.
1903
1904 This routine differs from C<_set_derived_columns_for_add>
1905 in that it needs to handle partial item records.  In other
1906 words, the caller of C<ModItem> may have supplied only one
1907 or two columns to be changed, so this function needs to
1908 determine whether any of the columns to be changed affect
1909 any of the derived columns.  Also, if a derived column
1910 depends on more than one column, but the caller is not
1911 changing all of then, this routine retrieves the unchanged
1912 values from the database in order to ensure a correct
1913 calculation.
1914
1915 =cut
1916
1917 sub _set_derived_columns_for_mod {
1918     my $item = shift;
1919
1920     foreach my $column (keys %derived_columns) {
1921         my $builder = $derived_columns{$column}->{'BUILDER'};
1922         my $source_values = {};
1923         my %missing_sources = ();
1924         my $must_recalc = 0;
1925         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1926             next if $source_column eq 'BUILDER';
1927             if (exists $item->{$source_column}) {
1928                 $must_recalc = 1;
1929                 $source_values->{$source_column} = $item->{$source_column};
1930             } else {
1931                 $missing_sources{$source_column} = 1;
1932             }
1933         }
1934         if ($must_recalc) {
1935             foreach my $source_column (keys %missing_sources) {
1936                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1937             }
1938             $builder->($item, $source_values);
1939         }
1940     }
1941 }
1942
1943 =head2 _do_column_fixes_for_mod
1944
1945   _do_column_fixes_for_mod($item);
1946
1947 Given an item hashref containing one or more
1948 columns to modify, fix up certain values.
1949 Specifically, set to 0 any passed value
1950 of C<notforloan>, C<damaged>, C<itemlost>, or
1951 C<withdrawn> that is either undefined or
1952 contains the empty string.
1953
1954 =cut
1955
1956 sub _do_column_fixes_for_mod {
1957     my $item = shift;
1958
1959     if (exists $item->{'notforloan'} and
1960         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1961         $item->{'notforloan'} = 0;
1962     }
1963     if (exists $item->{'damaged'} and
1964         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1965         $item->{'damaged'} = 0;
1966     }
1967     if (exists $item->{'itemlost'} and
1968         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1969         $item->{'itemlost'} = 0;
1970     }
1971     if (exists $item->{'withdrawn'} and
1972         (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
1973         $item->{'withdrawn'} = 0;
1974     }
1975     if (exists $item->{location}
1976         and $item->{location} ne 'CART'
1977         and $item->{location} ne 'PROC'
1978         and not $item->{permanent_location}
1979     ) {
1980         $item->{'permanent_location'} = $item->{'location'};
1981     }
1982     if (exists $item->{'timestamp'}) {
1983         delete $item->{'timestamp'};
1984     }
1985 }
1986
1987 =head2 _get_single_item_column
1988
1989   _get_single_item_column($column, $itemnumber);
1990
1991 Retrieves the value of a single column from an C<items>
1992 row specified by C<$itemnumber>.
1993
1994 =cut
1995
1996 sub _get_single_item_column {
1997     my $column = shift;
1998     my $itemnumber = shift;
1999     
2000     my $dbh = C4::Context->dbh;
2001     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
2002     $sth->execute($itemnumber);
2003     my ($value) = $sth->fetchrow();
2004     return $value; 
2005 }
2006
2007 =head2 _calc_items_cn_sort
2008
2009   _calc_items_cn_sort($item, $source_values);
2010
2011 Helper routine to calculate C<items.cn_sort>.
2012
2013 =cut
2014
2015 sub _calc_items_cn_sort {
2016     my $item = shift;
2017     my $source_values = shift;
2018
2019     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
2020 }
2021
2022 =head2 _set_defaults_for_add 
2023
2024   _set_defaults_for_add($item_hash);
2025
2026 Given an item hash representing an item to be added, set
2027 correct default values for columns whose default value
2028 is not handled by the DBMS.  This includes the following
2029 columns:
2030
2031 =over 2
2032
2033 =item * 
2034
2035 C<items.dateaccessioned>
2036
2037 =item *
2038
2039 C<items.notforloan>
2040
2041 =item *
2042
2043 C<items.damaged>
2044
2045 =item *
2046
2047 C<items.itemlost>
2048
2049 =item *
2050
2051 C<items.withdrawn>
2052
2053 =back
2054
2055 =cut
2056
2057 sub _set_defaults_for_add {
2058     my $item = shift;
2059     $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2060     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
2061 }
2062
2063 =head2 _koha_new_item
2064
2065   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
2066
2067 Perform the actual insert into the C<items> table.
2068
2069 =cut
2070
2071 sub _koha_new_item {
2072     my ( $item, $barcode ) = @_;
2073     my $dbh=C4::Context->dbh;  
2074     my $error;
2075     $item->{permanent_location} //= $item->{location};
2076     my $query =
2077            "INSERT INTO items SET
2078             biblionumber        = ?,
2079             biblioitemnumber    = ?,
2080             barcode             = ?,
2081             dateaccessioned     = ?,
2082             booksellerid        = ?,
2083             homebranch          = ?,
2084             price               = ?,
2085             replacementprice    = ?,
2086             replacementpricedate = ?,
2087             datelastborrowed    = ?,
2088             datelastseen        = ?,
2089             stack               = ?,
2090             notforloan          = ?,
2091             damaged             = ?,
2092             itemlost            = ?,
2093             withdrawn           = ?,
2094             itemcallnumber      = ?,
2095             coded_location_qualifier = ?,
2096             restricted          = ?,
2097             itemnotes           = ?,
2098             itemnotes_nonpublic = ?,
2099             holdingbranch       = ?,
2100             paidfor             = ?,
2101             location            = ?,
2102             permanent_location  = ?,
2103             onloan              = ?,
2104             issues              = ?,
2105             renewals            = ?,
2106             reserves            = ?,
2107             cn_source           = ?,
2108             cn_sort             = ?,
2109             ccode               = ?,
2110             itype               = ?,
2111             materials           = ?,
2112             uri                 = ?,
2113             enumchron           = ?,
2114             more_subfields_xml  = ?,
2115             copynumber          = ?,
2116             stocknumber         = ?,
2117             new_status          = ?
2118           ";
2119     my $sth = $dbh->prepare($query);
2120     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
2121    $sth->execute(
2122             $item->{'biblionumber'},
2123             $item->{'biblioitemnumber'},
2124             $barcode,
2125             $item->{'dateaccessioned'},
2126             $item->{'booksellerid'},
2127             $item->{'homebranch'},
2128             $item->{'price'},
2129             $item->{'replacementprice'},
2130             $item->{'replacementpricedate'} || $today,
2131             $item->{datelastborrowed},
2132             $item->{datelastseen} || $today,
2133             $item->{stack},
2134             $item->{'notforloan'},
2135             $item->{'damaged'},
2136             $item->{'itemlost'},
2137             $item->{'withdrawn'},
2138             $item->{'itemcallnumber'},
2139             $item->{'coded_location_qualifier'},
2140             $item->{'restricted'},
2141             $item->{'itemnotes'},
2142             $item->{'itemnotes_nonpublic'},
2143             $item->{'holdingbranch'},
2144             $item->{'paidfor'},
2145             $item->{'location'},
2146             $item->{'permanent_location'},
2147             $item->{'onloan'},
2148             $item->{'issues'},
2149             $item->{'renewals'},
2150             $item->{'reserves'},
2151             $item->{'items.cn_source'},
2152             $item->{'items.cn_sort'},
2153             $item->{'ccode'},
2154             $item->{'itype'},
2155             $item->{'materials'},
2156             $item->{'uri'},
2157             $item->{'enumchron'},
2158             $item->{'more_subfields_xml'},
2159             $item->{'copynumber'},
2160             $item->{'stocknumber'},
2161             $item->{'new_status'},
2162     );
2163
2164     my $itemnumber;
2165     if ( defined $sth->errstr ) {
2166         $error.="ERROR in _koha_new_item $query".$sth->errstr;
2167     }
2168     else {
2169         $itemnumber = $dbh->{'mysql_insertid'};
2170     }
2171
2172     return ( $itemnumber, $error );
2173 }
2174
2175 =head2 MoveItemFromBiblio
2176
2177   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2178
2179 Moves an item from a biblio to another
2180
2181 Returns undef if the move failed or the biblionumber of the destination record otherwise
2182
2183 =cut
2184
2185 sub MoveItemFromBiblio {
2186     my ($itemnumber, $frombiblio, $tobiblio) = @_;
2187     my $dbh = C4::Context->dbh;
2188     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
2189         SELECT biblioitemnumber
2190         FROM biblioitems
2191         WHERE biblionumber = ?
2192     |, undef, $tobiblio );
2193     my $return = $dbh->do(q|
2194         UPDATE items
2195         SET biblioitemnumber = ?,
2196             biblionumber = ?
2197         WHERE itemnumber = ?
2198             AND biblionumber = ?
2199     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
2200     if ($return == 1) {
2201         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
2202         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
2203             # Checking if the item we want to move is in an order 
2204         require C4::Acquisition;
2205         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
2206             if ($order) {
2207                     # Replacing the biblionumber within the order if necessary
2208                     $order->{'biblionumber'} = $tobiblio;
2209                 C4::Acquisition::ModOrder($order);
2210             }
2211
2212         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
2213         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
2214             $dbh->do( qq|
2215                 UPDATE $table_name
2216                 SET biblionumber = ?
2217                 WHERE itemnumber = ?
2218             |, undef, $tobiblio, $itemnumber );
2219         }
2220         return $tobiblio;
2221         }
2222     return;
2223 }
2224
2225 =head2 ItemSafeToDelete
2226
2227    ItemSafeToDelete( $biblionumber, $itemnumber);
2228
2229 Exported function (core API) for checking whether an item record is safe to delete.
2230
2231 returns 1 if the item is safe to delete,
2232
2233 "book_on_loan" if the item is checked out,
2234
2235 "not_same_branch" if the item is blocked by independent branches,
2236
2237 "book_reserved" if the there are holds aganst the item, or
2238
2239 "linked_analytics" if the item has linked analytic records.
2240
2241 =cut
2242
2243 sub ItemSafeToDelete {
2244     my ( $biblionumber, $itemnumber ) = @_;
2245     my $status;
2246     my $dbh = C4::Context->dbh;
2247
2248     my $error;
2249
2250     my $countanalytics = GetAnalyticsCount($itemnumber);
2251
2252     # check that there is no issue on this item before deletion.
2253     my $sth = $dbh->prepare(
2254         q{
2255         SELECT COUNT(*) FROM issues
2256         WHERE itemnumber = ?
2257     }
2258     );
2259     $sth->execute($itemnumber);
2260     my ($onloan) = $sth->fetchrow;
2261
2262     my $item = GetItem($itemnumber);
2263
2264     if ($onloan) {
2265         $status = "book_on_loan";
2266     }
2267     elsif ( defined C4::Context->userenv
2268         and !C4::Context->IsSuperLibrarian()
2269         and C4::Context->preference("IndependentBranches")
2270         and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
2271     {
2272         $status = "not_same_branch";
2273     }
2274     else {
2275         # check it doesn't have a waiting reserve
2276         $sth = $dbh->prepare(
2277             q{
2278             SELECT COUNT(*) FROM reserves
2279             WHERE (found = 'W' OR found = 'T')
2280             AND itemnumber = ?
2281         }
2282         );
2283         $sth->execute($itemnumber);
2284         my ($reserve) = $sth->fetchrow;
2285         if ($reserve) {
2286             $status = "book_reserved";
2287         }
2288         elsif ( $countanalytics > 0 ) {
2289             $status = "linked_analytics";
2290         }
2291         else {
2292             $status = 1;
2293         }
2294     }
2295     return $status;
2296 }
2297
2298 =head2 DelItemCheck
2299
2300    DelItemCheck( $biblionumber, $itemnumber);
2301
2302 Exported function (core API) for deleting an item record in Koha if there no current issue.
2303
2304 DelItemCheck wraps ItemSafeToDelete around DelItem.
2305
2306 =cut
2307
2308 sub DelItemCheck {
2309     my ( $biblionumber, $itemnumber ) = @_;
2310     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
2311
2312     if ( $status == 1 ) {
2313         DelItem(
2314             {
2315                 biblionumber => $biblionumber,
2316                 itemnumber   => $itemnumber
2317             }
2318         );
2319     }
2320     return $status;
2321 }
2322
2323 =head2 _koha_modify_item
2324
2325   my ($itemnumber,$error) =_koha_modify_item( $item );
2326
2327 Perform the actual update of the C<items> row.  Note that this
2328 routine accepts a hashref specifying the columns to update.
2329
2330 =cut
2331
2332 sub _koha_modify_item {
2333     my ( $item ) = @_;
2334     my $dbh=C4::Context->dbh;  
2335     my $error;
2336
2337     my $query = "UPDATE items SET ";
2338     my @bind;
2339     for my $key ( keys %$item ) {
2340         next if ( $key eq 'itemnumber' );
2341         $query.="$key=?,";
2342         push @bind, $item->{$key};
2343     }
2344     $query =~ s/,$//;
2345     $query .= " WHERE itemnumber=?";
2346     push @bind, $item->{'itemnumber'};
2347     my $sth = $dbh->prepare($query);
2348     $sth->execute(@bind);
2349     if ( $sth->err ) {
2350         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
2351         warn $error;
2352     }
2353     return ($item->{'itemnumber'},$error);
2354 }
2355
2356 =head2 _koha_delete_item
2357
2358   _koha_delete_item( $itemnum );
2359
2360 Internal function to delete an item record from the koha tables
2361
2362 =cut
2363
2364 sub _koha_delete_item {
2365     my ( $itemnum ) = @_;
2366
2367     my $dbh = C4::Context->dbh;
2368     # save the deleted item to deleteditems table
2369     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2370     $sth->execute($itemnum);
2371     my $data = $sth->fetchrow_hashref();
2372
2373     # There is no item to delete
2374     return 0 unless $data;
2375
2376     my $query = "INSERT INTO deleteditems SET ";
2377     my @bind  = ();
2378     foreach my $key ( keys %$data ) {
2379         next if ( $key eq 'timestamp' ); # timestamp will be set by db
2380         $query .= "$key = ?,";
2381         push( @bind, $data->{$key} );
2382     }
2383     $query =~ s/\,$//;
2384     $sth = $dbh->prepare($query);
2385     $sth->execute(@bind);
2386
2387     # delete from items table
2388     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2389     my $deleted = $sth->execute($itemnum);
2390     return ( $deleted == 1 ) ? 1 : 0;
2391 }
2392
2393 =head2 _marc_from_item_hash
2394
2395   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2396
2397 Given an item hash representing a complete item record,
2398 create a C<MARC::Record> object containing an embedded
2399 tag representing that item.
2400
2401 The third, optional parameter C<$unlinked_item_subfields> is
2402 an arrayref of subfields (not mapped to C<items> fields per the
2403 framework) to be added to the MARC representation
2404 of the item.
2405
2406 =cut
2407
2408 sub _marc_from_item_hash {
2409     my $item = shift;
2410     my $frameworkcode = shift;
2411     my $unlinked_item_subfields;
2412     if (@_) {
2413         $unlinked_item_subfields = shift;
2414     }
2415    
2416     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2417     # Also, don't emit a subfield if the underlying field is blank.
2418     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2419                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2420                                 : ()  } keys %{ $item } }; 
2421
2422     my $item_marc = MARC::Record->new();
2423     foreach my $item_field ( keys %{$mungeditem} ) {
2424         my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2425         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
2426         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2427         foreach my $value (@values){
2428             if ( my $field = $item_marc->field($tag) ) {
2429                     $field->add_subfields( $subfield => $value );
2430             } else {
2431                 my $add_subfields = [];
2432                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2433                     $add_subfields = $unlinked_item_subfields;
2434             }
2435             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2436             }
2437         }
2438     }
2439
2440     return $item_marc;
2441 }
2442
2443 =head2 _repack_item_errors
2444
2445 Add an error message hash generated by C<CheckItemPreSave>
2446 to a list of errors.
2447
2448 =cut
2449
2450 sub _repack_item_errors {
2451     my $item_sequence_num = shift;
2452     my $item_ref = shift;
2453     my $error_ref = shift;
2454
2455     my @repacked_errors = ();
2456
2457     foreach my $error_code (sort keys %{ $error_ref }) {
2458         my $repacked_error = {};
2459         $repacked_error->{'item_sequence'} = $item_sequence_num;
2460         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2461         $repacked_error->{'error_code'} = $error_code;
2462         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2463         push @repacked_errors, $repacked_error;
2464     } 
2465
2466     return @repacked_errors;
2467 }
2468
2469 =head2 _get_unlinked_item_subfields
2470
2471   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2472
2473 =cut
2474
2475 sub _get_unlinked_item_subfields {
2476     my $original_item_marc = shift;
2477     my $frameworkcode = shift;
2478
2479     my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
2480
2481     # assume that this record has only one field, and that that
2482     # field contains only the item information
2483     my $subfields = [];
2484     my @fields = $original_item_marc->fields();
2485     if ($#fields > -1) {
2486         my $field = $fields[0];
2487             my $tag = $field->tag();
2488         foreach my $subfield ($field->subfields()) {
2489             if (defined $subfield->[1] and
2490                 $subfield->[1] ne '' and
2491                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2492                 push @$subfields, $subfield->[0] => $subfield->[1];
2493             }
2494         }
2495     }
2496     return $subfields;
2497 }
2498
2499 =head2 _get_unlinked_subfields_xml
2500
2501   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2502
2503 =cut
2504
2505 sub _get_unlinked_subfields_xml {
2506     my $unlinked_item_subfields = shift;
2507
2508     my $xml;
2509     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2510         my $marc = MARC::Record->new();
2511         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2512         # used in the framework
2513         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2514         $marc->encoding("UTF-8");    
2515         $xml = $marc->as_xml("USMARC");
2516     }
2517
2518     return $xml;
2519 }
2520
2521 =head2 _parse_unlinked_item_subfields_from_xml
2522
2523   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2524
2525 =cut
2526
2527 sub  _parse_unlinked_item_subfields_from_xml {
2528     my $xml = shift;
2529     require C4::Charset;
2530     return unless defined $xml and $xml ne "";
2531     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
2532     my $unlinked_subfields = [];
2533     my @fields = $marc->fields();
2534     if ($#fields > -1) {
2535         foreach my $subfield ($fields[0]->subfields()) {
2536             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2537         }
2538     }
2539     return $unlinked_subfields;
2540 }
2541
2542 =head2 GetAnalyticsCount
2543
2544   $count= &GetAnalyticsCount($itemnumber)
2545
2546 counts Usage of itemnumber in Analytical bibliorecords. 
2547
2548 =cut
2549
2550 sub GetAnalyticsCount {
2551     my ($itemnumber) = @_;
2552
2553     ### ZOOM search here
2554     my $query;
2555     $query= "hi=".$itemnumber;
2556     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
2557     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
2558     return ($result);
2559 }
2560
2561 =head2 GetItemHolds
2562
2563   $holds = &GetItemHolds($biblionumber, $itemnumber);
2564
2565 This function return the count of holds with $biblionumber and $itemnumber
2566
2567 =cut
2568
2569 sub GetItemHolds {
2570     my ($biblionumber, $itemnumber) = @_;
2571     my $holds;
2572     my $dbh            = C4::Context->dbh;
2573     my $query          = "SELECT count(*)
2574         FROM  reserves
2575         WHERE biblionumber=? AND itemnumber=?";
2576     my $sth = $dbh->prepare($query);
2577     $sth->execute($biblionumber, $itemnumber);
2578     $holds = $sth->fetchrow;
2579     return $holds;
2580 }
2581
2582 =head2 SearchItemsByField
2583
2584     my $items = SearchItemsByField($field, $value);
2585
2586 SearchItemsByField will search for items on a specific given field.
2587 For instance you can search all items with a specific stocknumber like this:
2588
2589     my $items = SearchItemsByField('stocknumber', $stocknumber);
2590
2591 =cut
2592
2593 sub SearchItemsByField {
2594     my ($field, $value) = @_;
2595
2596     my $filters = {
2597         field => $field,
2598         query => $value,
2599     };
2600
2601     my ($results) = SearchItems($filters);
2602     return $results;
2603 }
2604
2605 sub _SearchItems_build_where_fragment {
2606     my ($filter) = @_;
2607
2608     my $dbh = C4::Context->dbh;
2609
2610     my $where_fragment;
2611     if (exists($filter->{conjunction})) {
2612         my (@where_strs, @where_args);
2613         foreach my $f (@{ $filter->{filters} }) {
2614             my $fragment = _SearchItems_build_where_fragment($f);
2615             if ($fragment) {
2616                 push @where_strs, $fragment->{str};
2617                 push @where_args, @{ $fragment->{args} };
2618             }
2619         }
2620         my $where_str = '';
2621         if (@where_strs) {
2622             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2623             $where_fragment = {
2624                 str => $where_str,
2625                 args => \@where_args,
2626             };
2627         }
2628     } else {
2629         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2630         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2631         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2632         my @operators = qw(= != > < >= <= like);
2633         my $field = $filter->{field};
2634         if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2635             my $op = $filter->{operator};
2636             my $query = $filter->{query};
2637
2638             if (!$op or (0 == grep /^$op$/, @operators)) {
2639                 $op = '='; # default operator
2640             }
2641
2642             my $column;
2643             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2644                 my $marcfield = $1;
2645                 my $marcsubfield = $2;
2646                 my ($kohafield) = $dbh->selectrow_array(q|
2647                     SELECT kohafield FROM marc_subfield_structure
2648                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
2649                 |, undef, $marcfield, $marcsubfield);
2650
2651                 if ($kohafield) {
2652                     $column = $kohafield;
2653                 } else {
2654                     # MARC field is not linked to a DB field so we need to use
2655                     # ExtractValue on biblioitems.marcxml or
2656                     # items.more_subfields_xml, depending on the MARC field.
2657                     my $xpath;
2658                     my $sqlfield;
2659                     my ($itemfield) = GetMarcFromKohaField('items.itemnumber');
2660                     if ($marcfield eq $itemfield) {
2661                         $sqlfield = 'more_subfields_xml';
2662                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
2663                     } else {
2664                         $sqlfield = 'marcxml';
2665                         if ($marcfield < 10) {
2666                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2667                         } else {
2668                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2669                         }
2670                     }
2671                     $column = "ExtractValue($sqlfield, '$xpath')";
2672                 }
2673             } else {
2674                 $column = $field;
2675             }
2676
2677             if (ref $query eq 'ARRAY') {
2678                 if ($op eq '=') {
2679                     $op = 'IN';
2680                 } elsif ($op eq '!=') {
2681                     $op = 'NOT IN';
2682                 }
2683                 $where_fragment = {
2684                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2685                     args => $query,
2686                 };
2687             } else {
2688                 $where_fragment = {
2689                     str => "$column $op ?",
2690                     args => [ $query ],
2691                 };
2692             }
2693         }
2694     }
2695
2696     return $where_fragment;
2697 }
2698
2699 =head2 SearchItems
2700
2701     my ($items, $total) = SearchItems($filter, $params);
2702
2703 Perform a search among items
2704
2705 $filter is a reference to a hash which can be a filter, or a combination of filters.
2706
2707 A filter has the following keys:
2708
2709 =over 2
2710
2711 =item * field: the name of a SQL column in table items
2712
2713 =item * query: the value to search in this column
2714
2715 =item * operator: comparison operator. Can be one of = != > < >= <= like
2716
2717 =back
2718
2719 A combination of filters hash the following keys:
2720
2721 =over 2
2722
2723 =item * conjunction: 'AND' or 'OR'
2724
2725 =item * filters: array ref of filters
2726
2727 =back
2728
2729 $params is a reference to a hash that can contain the following parameters:
2730
2731 =over 2
2732
2733 =item * rows: Number of items to return. 0 returns everything (default: 0)
2734
2735 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2736                (default: 1)
2737
2738 =item * sortby: A SQL column name in items table to sort on
2739
2740 =item * sortorder: 'ASC' or 'DESC'
2741
2742 =back
2743
2744 =cut
2745
2746 sub SearchItems {
2747     my ($filter, $params) = @_;
2748
2749     $filter //= {};
2750     $params //= {};
2751     return unless ref $filter eq 'HASH';
2752     return unless ref $params eq 'HASH';
2753
2754     # Default parameters
2755     $params->{rows} ||= 0;
2756     $params->{page} ||= 1;
2757     $params->{sortby} ||= 'itemnumber';
2758     $params->{sortorder} ||= 'ASC';
2759
2760     my ($where_str, @where_args);
2761     my $where_fragment = _SearchItems_build_where_fragment($filter);
2762     if ($where_fragment) {
2763         $where_str = $where_fragment->{str};
2764         @where_args = @{ $where_fragment->{args} };
2765     }
2766
2767     my $dbh = C4::Context->dbh;
2768     my $query = q{
2769         SELECT SQL_CALC_FOUND_ROWS items.*
2770         FROM items
2771           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2772           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2773     };
2774     if (defined $where_str and $where_str ne '') {
2775         $query .= qq{ WHERE $where_str };
2776     }
2777
2778     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2779     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2780     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2781     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2782         ? $params->{sortby} : 'itemnumber';
2783     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2784     $query .= qq{ ORDER BY $sortby $sortorder };
2785
2786     my $rows = $params->{rows};
2787     my @limit_args;
2788     if ($rows > 0) {
2789         my $offset = $rows * ($params->{page}-1);
2790         $query .= qq { LIMIT ?, ? };
2791         push @limit_args, $offset, $rows;
2792     }
2793
2794     my $sth = $dbh->prepare($query);
2795     my $rv = $sth->execute(@where_args, @limit_args);
2796
2797     return unless ($rv);
2798     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2799
2800     return ($sth->fetchall_arrayref({}), $total_rows);
2801 }
2802
2803
2804 =head1  OTHER FUNCTIONS
2805
2806 =head2 _find_value
2807
2808   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2809
2810 Find the given $subfield in the given $tag in the given
2811 MARC::Record $record.  If the subfield is found, returns
2812 the (indicators, value) pair; otherwise, (undef, undef) is
2813 returned.
2814
2815 PROPOSITION :
2816 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2817 I suggest we export it from this module.
2818
2819 =cut
2820
2821 sub _find_value {
2822     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2823     my @result;
2824     my $indicator;
2825     if ( $tagfield < 10 ) {
2826         if ( $record->field($tagfield) ) {
2827             push @result, $record->field($tagfield)->data();
2828         } else {
2829             push @result, "";
2830         }
2831     } else {
2832         foreach my $field ( $record->field($tagfield) ) {
2833             my @subfields = $field->subfields();
2834             foreach my $subfield (@subfields) {
2835                 if ( @$subfield[0] eq $insubfield ) {
2836                     push @result, @$subfield[1];
2837                     $indicator = $field->indicator(1) . $field->indicator(2);
2838                 }
2839             }
2840         }
2841     }
2842     return ( $indicator, @result );
2843 }
2844
2845
2846 =head2 PrepareItemrecordDisplay
2847
2848   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2849
2850 Returns a hash with all the fields for Display a given item data in a template
2851
2852 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2853
2854 =cut
2855
2856 sub PrepareItemrecordDisplay {
2857
2858     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2859
2860     my $dbh = C4::Context->dbh;
2861     $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2862     my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2863
2864     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2865     # a shared data structure. No plugin (including custom ones) should change
2866     # its contents. See also GetMarcStructure.
2867     my $tagslib = &GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2868
2869     # return nothing if we don't have found an existing framework.
2870     return q{} unless $tagslib;
2871     my $itemrecord;
2872     if ($itemnum) {
2873         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2874     }
2875     my @loop_data;
2876
2877     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2878     my $query = qq{
2879         SELECT authorised_value,lib FROM authorised_values
2880     };
2881     $query .= qq{
2882         LEFT JOIN authorised_values_branches ON ( id = av_id )
2883     } if $branch_limit;
2884     $query .= qq{
2885         WHERE category = ?
2886     };
2887     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2888     $query .= qq{ ORDER BY lib};
2889     my $authorised_values_sth = $dbh->prepare( $query );
2890     foreach my $tag ( sort keys %{$tagslib} ) {
2891         if ( $tag ne '' ) {
2892
2893             # loop through each subfield
2894             my $cntsubf;
2895             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2896                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2897                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2898                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2899                 my %subfield_data;
2900                 $subfield_data{tag}           = $tag;
2901                 $subfield_data{subfield}      = $subfield;
2902                 $subfield_data{countsubfield} = $cntsubf++;
2903                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2904                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2905
2906                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2907                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2908                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2909                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2910                 $subfield_data{hidden}     = "display:none"
2911                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2912                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2913                 my ( $x, $defaultvalue );
2914                 if ($itemrecord) {
2915                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2916                 }
2917                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2918                 if ( !defined $defaultvalue ) {
2919                     $defaultvalue = q||;
2920                 } else {
2921                     $defaultvalue =~ s/"/&quot;/g;
2922                 }
2923
2924                 # search for itemcallnumber if applicable
2925                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2926                     && C4::Context->preference('itemcallnumber') ) {
2927                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2928                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2929                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2930                         $defaultvalue = $field->subfield($CNsubfield);
2931                     }
2932                 }
2933                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2934                     && $defaultvalues
2935                     && $defaultvalues->{'callnumber'} ) {
2936                     if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
2937                         # if the item record exists, only use default value if the item has no callnumber
2938                         $defaultvalue = $defaultvalues->{callnumber};
2939                     } elsif ( !$itemrecord and $defaultvalues ) {
2940                         # if the item record *doesn't* exists, always use the default value
2941                         $defaultvalue = $defaultvalues->{callnumber};
2942                     }
2943                 }
2944                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2945                     && $defaultvalues
2946                     && $defaultvalues->{'branchcode'} ) {
2947                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2948                         $defaultvalue = $defaultvalues->{branchcode};
2949                     }
2950                 }
2951                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2952                     && $defaultvalues
2953                     && $defaultvalues->{'location'} ) {
2954
2955                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2956                         # if the item record exists, only use default value if the item has no locationr
2957                         $defaultvalue = $defaultvalues->{location};
2958                     } elsif ( !$itemrecord and $defaultvalues ) {
2959                         # if the item record *doesn't* exists, always use the default value
2960                         $defaultvalue = $defaultvalues->{location};
2961                     }
2962                 }
2963                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2964                     my @authorised_values;
2965                     my %authorised_lib;
2966
2967                     # builds list, depending on authorised value...
2968                     #---- branch
2969                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2970                         if (   ( C4::Context->preference("IndependentBranches") )
2971                             && !C4::Context->IsSuperLibrarian() ) {
2972                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2973                             $sth->execute( C4::Context->userenv->{branch} );
2974                             push @authorised_values, ""
2975                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2976                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2977                                 push @authorised_values, $branchcode;
2978                                 $authorised_lib{$branchcode} = $branchname;
2979                             }
2980                         } else {
2981                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2982                             $sth->execute;
2983                             push @authorised_values, ""
2984                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2985                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2986                                 push @authorised_values, $branchcode;
2987                                 $authorised_lib{$branchcode} = $branchname;
2988                             }
2989                         }
2990
2991                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2992                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2993                             $defaultvalue = $defaultvalues->{branchcode};
2994                         }
2995
2996                         #----- itemtypes
2997                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2998                         my $itemtypes = GetItemTypes( style => 'array' );
2999                         push @authorised_values, ""
3000                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3001                         for my $itemtype ( @$itemtypes ) {
3002                             push @authorised_values, $itemtype->{itemtype};
3003                             $authorised_lib{$itemtype->{itemtype}} = $itemtype->{translated_description};
3004                         }
3005                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
3006                             $defaultvalue = $defaultvalues->{'itemtype'};
3007                         }
3008
3009                         #---- class_sources
3010                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
3011                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3012
3013                         my $class_sources = GetClassSources();
3014                         my $default_source = C4::Context->preference("DefaultClassificationSource");
3015
3016                         foreach my $class_source (sort keys %$class_sources) {
3017                             next unless $class_sources->{$class_source}->{'used'} or
3018                                         ($class_source eq $default_source);
3019                             push @authorised_values, $class_source;
3020                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
3021                         }
3022
3023                         $defaultvalue = $default_source;
3024
3025                         #---- "true" authorised value
3026                     } else {
3027                         $authorised_values_sth->execute(
3028                             $tagslib->{$tag}->{$subfield}->{authorised_value},
3029                             $branch_limit ? $branch_limit : ()
3030                         );
3031                         push @authorised_values, ""
3032                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3033                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
3034                             push @authorised_values, $value;
3035                             $authorised_lib{$value} = $lib;
3036                         }
3037                     }
3038                     $subfield_data{marc_value} = {
3039                         type    => 'select',
3040                         values  => \@authorised_values,
3041                         default => "$defaultvalue",
3042                         labels  => \%authorised_lib,
3043                     };
3044                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
3045                 # it is a plugin
3046                     require Koha::FrameworkPlugin;
3047                     my $plugin = Koha::FrameworkPlugin->new({
3048                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
3049                         item_style => 1,
3050                     });
3051                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
3052                     $plugin->build( $pars );
3053                     if( !$plugin->errstr ) {
3054                         #TODO Move html to template; see report 12176/13397
3055                         my $tab= $plugin->noclick? '-1': '';
3056                         my $class= $plugin->noclick? ' disabled': '';
3057                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
3058                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
3059                     } else {
3060                         warn $plugin->errstr;
3061                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" />); # supply default input form
3062                     }
3063                 }
3064                 elsif ( $tag eq '' ) {       # it's an hidden field
3065                     $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
3066                 }
3067                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
3068                     $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
3069                 }
3070                 elsif ( length($defaultvalue) > 100
3071                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
3072                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
3073                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
3074                                   500 <= $tag && $tag < 600                     )
3075                           ) {
3076                     # oversize field (textarea)
3077                     $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
3078                 } else {
3079                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
3080                 }
3081                 push( @loop_data, \%subfield_data );
3082             }
3083         }
3084     }
3085     my $itemnumber;
3086     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
3087         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
3088     }
3089     return {
3090         'itemtagfield'    => $itemtagfield,
3091         'itemtagsubfield' => $itemtagsubfield,
3092         'itemnumber'      => $itemnumber,
3093         'iteminformation' => \@loop_data
3094     };
3095 }
3096
3097 sub ToggleNewStatus {
3098     my ( $params ) = @_;
3099     my @rules = @{ $params->{rules} };
3100     my $report_only = $params->{report_only};
3101
3102     my $dbh = C4::Context->dbh;
3103     my @errors;
3104     my @item_columns = map { "items.$_" } Koha::Items->columns;
3105     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
3106     my $report;
3107     for my $rule ( @rules ) {
3108         my $age = $rule->{age};
3109         my $conditions = $rule->{conditions};
3110         my $substitutions = $rule->{substitutions};
3111         my @params;
3112
3113         my $query = q|
3114             SELECT items.biblionumber, items.itemnumber
3115             FROM items
3116             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
3117             WHERE 1
3118         |;
3119         for my $condition ( @$conditions ) {
3120             if (
3121                  grep {/^$condition->{field}$/} @item_columns
3122               or grep {/^$condition->{field}$/} @biblioitem_columns
3123             ) {
3124                 if ( $condition->{value} =~ /\|/ ) {
3125                     my @values = split /\|/, $condition->{value};
3126                     $query .= qq| AND $condition->{field} IN (|
3127                         . join( ',', ('?') x scalar @values )
3128                         . q|)|;
3129                     push @params, @values;
3130                 } else {
3131                     $query .= qq| AND $condition->{field} = ?|;
3132                     push @params, $condition->{value};
3133                 }
3134             }
3135         }
3136         if ( defined $age ) {
3137             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
3138             push @params, $age;
3139         }
3140         my $sth = $dbh->prepare($query);
3141         $sth->execute( @params );
3142         while ( my $values = $sth->fetchrow_hashref ) {
3143             my $biblionumber = $values->{biblionumber};
3144             my $itemnumber = $values->{itemnumber};
3145             my $item = C4::Items::GetItem( $itemnumber );
3146             for my $substitution ( @$substitutions ) {
3147                 next unless $substitution->{field};
3148                 C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
3149                     unless $report_only;
3150                 push @{ $report->{$itemnumber} }, $substitution;
3151             }
3152         }
3153     }
3154
3155     return $report;
3156 }
3157
3158
3159 1;