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