Bug 5304: GetItemsInfo() - moved issues and serials query from the results loop to...
[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         if( ! $data->{'itype'} ) {
178                 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems  WHERE biblionumber = ?");
179                 $sth->execute($data->{'biblionumber'});
180                 ($data->{'itype'}) = $sth->fetchrow_array;
181         }
182     return $data;
183 }    # sub GetItem
184
185 =head2 CartToShelf
186
187   CartToShelf($itemnumber);
188
189 Set the current shelving location of the item record
190 to its stored permanent shelving location.  This is
191 primarily used to indicate when an item whose current
192 location is a special processing ('PROC') or shelving cart
193 ('CART') location is back in the stacks.
194
195 =cut
196
197 sub CartToShelf {
198     my ( $itemnumber ) = @_;
199
200     unless ( $itemnumber ) {
201         croak "FAILED CartToShelf() - no itemnumber supplied";
202     }
203
204     my $item = GetItem($itemnumber);
205     if ( $item->{location} eq 'CART' ) {
206         $item->{location} = $item->{permanent_location};
207         ModItem($item, undef, $itemnumber);
208     }
209 }
210
211 =head2 ShelfToCart
212
213   ShelfToCart($itemnumber);
214
215 Set the current shelving location of the item
216 to shelving cart ('CART').
217
218 =cut
219
220 sub ShelfToCart {
221     my ( $itemnumber ) = @_;
222
223     unless ( $itemnumber ) {
224         croak "FAILED ShelfToCart() - no itemnumber supplied";
225     }
226
227     my $item = GetItem($itemnumber);
228     $item->{'location'} = 'CART';
229     ModItem($item, undef, $itemnumber);
230 }
231
232 =head2 AddItemFromMarc
233
234   my ($biblionumber, $biblioitemnumber, $itemnumber) 
235       = AddItemFromMarc($source_item_marc, $biblionumber);
236
237 Given a MARC::Record object containing an embedded item
238 record and a biblionumber, create a new item record.
239
240 =cut
241
242 sub AddItemFromMarc {
243     my ( $source_item_marc, $biblionumber ) = @_;
244     my $dbh = C4::Context->dbh;
245
246     # parse item hash from MARC
247     my $frameworkcode = GetFrameworkCode( $biblionumber );
248         my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
249         
250         my $localitemmarc=MARC::Record->new;
251         $localitemmarc->append_fields($source_item_marc->field($itemtag));
252     my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
253     my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
254     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
255 }
256
257 =head2 AddItem
258
259   my ($biblionumber, $biblioitemnumber, $itemnumber) 
260       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
261
262 Given a hash containing item column names as keys,
263 create a new Koha item record.
264
265 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
266 do not need to be supplied for general use; they exist
267 simply to allow them to be picked up from AddItemFromMarc.
268
269 The final optional parameter, C<$unlinked_item_subfields>, contains
270 an arrayref containing subfields present in the original MARC
271 representation of the item (e.g., from the item editor) that are
272 not mapped to C<items> columns directly but should instead
273 be stored in C<items.more_subfields_xml> and included in 
274 the biblio items tag for display and indexing.
275
276 =cut
277
278 sub AddItem {
279     my $item = shift;
280     my $biblionumber = shift;
281
282     my $dbh           = @_ ? shift : C4::Context->dbh;
283     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
284     my $unlinked_item_subfields;  
285     if (@_) {
286         $unlinked_item_subfields = shift
287     };
288
289     # needs old biblionumber and biblioitemnumber
290     $item->{'biblionumber'} = $biblionumber;
291     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
292     $sth->execute( $item->{'biblionumber'} );
293     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
294
295     _set_defaults_for_add($item);
296     _set_derived_columns_for_add($item);
297     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
298     # FIXME - checks here
299     unless ( $item->{itype} ) {  # default to biblioitem.itemtype if no itype
300         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
301         $itype_sth->execute( $item->{'biblionumber'} );
302         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
303     }
304
305         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
306     $item->{'itemnumber'} = $itemnumber;
307
308     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
309    
310     logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
311     
312     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
313 }
314
315 =head2 AddItemBatchFromMarc
316
317   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
318              $biblionumber, $biblioitemnumber, $frameworkcode);
319
320 Efficiently create item records from a MARC biblio record with
321 embedded item fields.  This routine is suitable for batch jobs.
322
323 This API assumes that the bib record has already been
324 saved to the C<biblio> and C<biblioitems> tables.  It does
325 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
326 are populated, but it will do so via a call to ModBibiloMarc.
327
328 The goal of this API is to have a similar effect to using AddBiblio
329 and AddItems in succession, but without inefficient repeated
330 parsing of the MARC XML bib record.
331
332 This function returns an arrayref of new itemsnumbers and an arrayref of item
333 errors encountered during the processing.  Each entry in the errors
334 list is a hashref containing the following keys:
335
336 =over
337
338 =item item_sequence
339
340 Sequence number of original item tag in the MARC record.
341
342 =item item_barcode
343
344 Item barcode, provide to assist in the construction of
345 useful error messages.
346
347 =item error_code
348
349 Code representing the error condition.  Can be 'duplicate_barcode',
350 'invalid_homebranch', or 'invalid_holdingbranch'.
351
352 =item error_information
353
354 Additional information appropriate to the error condition.
355
356 =back
357
358 =cut
359
360 sub AddItemBatchFromMarc {
361     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
362     my $error;
363     my @itemnumbers = ();
364     my @errors = ();
365     my $dbh = C4::Context->dbh;
366
367     # We modify the record, so lets work on a clone so we don't change the
368     # original.
369     $record = $record->clone();
370     # loop through the item tags and start creating items
371     my @bad_item_fields = ();
372     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
373     my $item_sequence_num = 0;
374     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
375         $item_sequence_num++;
376         # we take the item field and stick it into a new
377         # MARC record -- this is required so far because (FIXME)
378         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
379         # and there is no TransformMarcFieldToKoha
380         my $temp_item_marc = MARC::Record->new();
381         $temp_item_marc->append_fields($item_field);
382     
383         # add biblionumber and biblioitemnumber
384         my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
385         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
386         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
387         $item->{'biblionumber'} = $biblionumber;
388         $item->{'biblioitemnumber'} = $biblioitemnumber;
389
390         # check for duplicate barcode
391         my %item_errors = CheckItemPreSave($item);
392         if (%item_errors) {
393             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
394             push @bad_item_fields, $item_field;
395             next ITEMFIELD;
396         }
397
398         _set_defaults_for_add($item);
399         _set_derived_columns_for_add($item);
400         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
401         warn $error if $error;
402         push @itemnumbers, $itemnumber; # FIXME not checking error
403         $item->{'itemnumber'} = $itemnumber;
404
405         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
406
407         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
408         $item_field->replace_with($new_item_marc->field($itemtag));
409     }
410
411     # remove any MARC item fields for rejected items
412     foreach my $item_field (@bad_item_fields) {
413         $record->delete_field($item_field);
414     }
415
416     # update the MARC biblio
417  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
418
419     return (\@itemnumbers, \@errors);
420 }
421
422 =head2 ModItemFromMarc
423
424   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
425
426 This function updates an item record based on a supplied
427 C<MARC::Record> object containing an embedded item field.
428 This API is meant for the use of C<additem.pl>; for 
429 other purposes, C<ModItem> should be used.
430
431 This function uses the hash %default_values_for_mod_from_marc,
432 which contains default values for item fields to
433 apply when modifying an item.  This is needed beccause
434 if an item field's value is cleared, TransformMarcToKoha
435 does not include the column in the
436 hash that's passed to ModItem, which without
437 use of this hash makes it impossible to clear
438 an item field's value.  See bug 2466.
439
440 Note that only columns that can be directly
441 changed from the cataloging and serials
442 item editors are included in this hash.
443
444 Returns item record
445
446 =cut
447
448 our %default_values_for_mod_from_marc;
449
450 sub _build_default_values_for_mod_marc {
451     my ($frameworkcode) = @_;
452     return $default_values_for_mod_from_marc{$frameworkcode}
453       if exists $default_values_for_mod_from_marc{$frameworkcode};
454     my $marc_structure = C4::Biblio::GetMarcStructure( 1, $frameworkcode );
455     my $default_values = {
456         barcode                  => undef,
457         booksellerid             => undef,
458         ccode                    => undef,
459         'items.cn_source'        => undef,
460         coded_location_qualifier => undef,
461         copynumber               => undef,
462         damaged                  => 0,
463         enumchron                => undef,
464         holdingbranch            => undef,
465         homebranch               => undef,
466         itemcallnumber           => undef,
467         itemlost                 => 0,
468         itemnotes                => undef,
469         itype                    => undef,
470         location                 => undef,
471         permanent_location       => undef,
472         materials                => undef,
473         notforloan               => 0,
474         # paidfor => undef, # commented, see bug 12817
475         price                    => undef,
476         replacementprice         => undef,
477         replacementpricedate     => undef,
478         restricted               => undef,
479         stack                    => undef,
480         stocknumber              => undef,
481         uri                      => undef,
482         withdrawn                => 0,
483     };
484     while ( my ( $field, $default_value ) = each %$default_values ) {
485         $field =~ s|[^\.]*\.?(.*)|items.$1|;
486         $default_values_for_mod_from_marc{$frameworkcode}{$field} =
487           $default_value
488           if C4::Koha::IsKohaFieldLinked(
489             { kohafield => $field, frameworkcode => $frameworkcode } );
490     }
491     return $default_values_for_mod_from_marc{$frameworkcode};
492 }
493
494 sub ModItemFromMarc {
495     my $item_marc = shift;
496     my $biblionumber = shift;
497     my $itemnumber = shift;
498
499     my $dbh           = C4::Context->dbh;
500     my $frameworkcode = GetFrameworkCode($biblionumber);
501     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
502
503     my $localitemmarc = MARC::Record->new;
504     $localitemmarc->append_fields( $item_marc->field($itemtag) );
505     my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items' );
506     my $default_values = _build_default_values_for_mod_marc();
507     foreach my $item_field ( keys %$default_values ) {
508         $item->{$item_field} = $default_values->{$item_field}
509           unless exists $item->{$item_field};
510     }
511     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
512
513     ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
514     return $item;
515 }
516
517 =head2 ModItem
518
519   ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
520
521 Change one or more columns in an item record and update
522 the MARC representation of the item.
523
524 The first argument is a hashref mapping from item column
525 names to the new values.  The second and third arguments
526 are the biblionumber and itemnumber, respectively.
527
528 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
529 an arrayref containing subfields present in the original MARC
530 representation of the item (e.g., from the item editor) that are
531 not mapped to C<items> columns directly but should instead
532 be stored in C<items.more_subfields_xml> and included in 
533 the biblio items tag for display and indexing.
534
535 If one of the changed columns is used to calculate
536 the derived value of a column such as C<items.cn_sort>, 
537 this routine will perform the necessary calculation
538 and set the value.
539
540 =cut
541
542 sub ModItem {
543     my $item = shift;
544     my $biblionumber = shift;
545     my $itemnumber = shift;
546
547     # if $biblionumber is undefined, get it from the current item
548     unless (defined $biblionumber) {
549         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
550     }
551
552     my $dbh           = @_ ? shift : C4::Context->dbh;
553     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
554     
555     my $unlinked_item_subfields;  
556     if (@_) {
557         $unlinked_item_subfields = shift;
558         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
559     };
560
561     $item->{'itemnumber'} = $itemnumber or return;
562
563     my @fields = qw( itemlost withdrawn );
564
565     # Only call GetItem if we need to set an "on" date field
566     if ( $item->{itemlost} || $item->{withdrawn} ) {
567         my $pre_mod_item = GetItem( $item->{'itemnumber'} );
568         for my $field (@fields) {
569             if (    defined( $item->{$field} )
570                 and not $pre_mod_item->{$field}
571                 and $item->{$field} )
572             {
573                 $item->{ $field . '_on' } =
574                   DateTime::Format::MySQL->format_datetime( dt_from_string() );
575             }
576         }
577     }
578
579     # If the field is defined but empty, we are removing and,
580     # and thus need to clear out the 'on' field as well
581     for my $field (@fields) {
582         if ( defined( $item->{$field} ) && !$item->{$field} ) {
583             $item->{ $field . '_on' } = undef;
584         }
585     }
586
587
588     _set_derived_columns_for_mod($item);
589     _do_column_fixes_for_mod($item);
590     # FIXME add checks
591     # duplicate barcode
592     # attempt to change itemnumber
593     # attempt to change biblionumber (if we want
594     # an API to relink an item to a different bib,
595     # it should be a separate function)
596
597     # update items table
598     _koha_modify_item($item);
599
600     # request that bib be reindexed so that searching on current
601     # item status is possible
602     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
603
604     logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog");
605 }
606
607 =head2 ModItemTransfer
608
609   ModItemTransfer($itenumber, $frombranch, $tobranch);
610
611 Marks an item as being transferred from one branch
612 to another.
613
614 =cut
615
616 sub ModItemTransfer {
617     my ( $itemnumber, $frombranch, $tobranch ) = @_;
618
619     my $dbh = C4::Context->dbh;
620
621     # Remove the 'shelving cart' location status if it is being used.
622     CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
623
624     #new entry in branchtransfers....
625     my $sth = $dbh->prepare(
626         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
627         VALUES (?, ?, NOW(), ?)");
628     $sth->execute($itemnumber, $frombranch, $tobranch);
629
630     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
631     ModDateLastSeen($itemnumber);
632     return;
633 }
634
635 =head2 ModDateLastSeen
636
637   ModDateLastSeen($itemnum);
638
639 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
640 C<$itemnum> is the item number
641
642 =cut
643
644 sub ModDateLastSeen {
645     my ($itemnumber) = @_;
646     
647     my $today = C4::Dates->new();    
648     ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
649 }
650
651 =head2 DelItem
652
653   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
654
655 Exported function (core API) for deleting an item record in Koha.
656
657 =cut
658
659 sub DelItem {
660     my ( $params ) = @_;
661
662     my $itemnumber   = $params->{itemnumber};
663     my $biblionumber = $params->{biblionumber};
664
665     unless ($biblionumber) {
666         $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber);
667     }
668
669     # If there is no biblionumber for the given itemnumber, there is nothing to delete
670     return 0 unless $biblionumber;
671
672     # FIXME check the item has no current issues
673     my $deleted = _koha_delete_item( $itemnumber );
674
675     # get the MARC record
676     my $record = GetMarcBiblio($biblionumber);
677     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
678
679     #search item field code
680     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
681     return $deleted;
682 }
683
684 =head2 CheckItemPreSave
685
686     my $item_ref = TransformMarcToKoha($marc, 'items');
687     # do stuff
688     my %errors = CheckItemPreSave($item_ref);
689     if (exists $errors{'duplicate_barcode'}) {
690         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
691     } elsif (exists $errors{'invalid_homebranch'}) {
692         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
693     } elsif (exists $errors{'invalid_holdingbranch'}) {
694         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
695     } else {
696         print "item is OK";
697     }
698
699 Given a hashref containing item fields, determine if it can be
700 inserted or updated in the database.  Specifically, checks for
701 database integrity issues, and returns a hash containing any
702 of the following keys, if applicable.
703
704 =over 2
705
706 =item duplicate_barcode
707
708 Barcode, if it duplicates one already found in the database.
709
710 =item invalid_homebranch
711
712 Home branch, if not defined in branches table.
713
714 =item invalid_holdingbranch
715
716 Holding branch, if not defined in branches table.
717
718 =back
719
720 This function does NOT implement any policy-related checks,
721 e.g., whether current operator is allowed to save an
722 item that has a given branch code.
723
724 =cut
725
726 sub CheckItemPreSave {
727     my $item_ref = shift;
728     require C4::Branch;
729
730     my %errors = ();
731
732     # check for duplicate barcode
733     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
734         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
735         if ($existing_itemnumber) {
736             if (!exists $item_ref->{'itemnumber'}                       # new item
737                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
738                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
739             }
740         }
741     }
742
743     # check for valid home branch
744     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
745         my $branch_name = C4::Branch::GetBranchName($item_ref->{'homebranch'});
746         unless (defined $branch_name) {
747             # relies on fact that branches.branchname is a non-NULL column,
748             # so GetBranchName returns undef only if branch does not exist
749             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
750         }
751     }
752
753     # check for valid holding branch
754     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
755         my $branch_name = C4::Branch::GetBranchName($item_ref->{'holdingbranch'});
756         unless (defined $branch_name) {
757             # relies on fact that branches.branchname is a non-NULL column,
758             # so GetBranchName returns undef only if branch does not exist
759             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
760         }
761     }
762
763     return %errors;
764
765 }
766
767 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
768
769 The following functions provide various ways of 
770 getting an item record, a set of item records, or
771 lists of authorized values for certain item fields.
772
773 Some of the functions in this group are candidates
774 for refactoring -- for example, some of the code
775 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
776 has copy-and-paste work.
777
778 =cut
779
780 =head2 GetItemStatus
781
782   $itemstatushash = GetItemStatus($fwkcode);
783
784 Returns a list of valid values for the
785 C<items.notforloan> field.
786
787 NOTE: does B<not> return an individual item's
788 status.
789
790 Can be MARC dependant.
791 fwkcode is optional.
792 But basically could be can be loan or not
793 Create a status selector with the following code
794
795 =head3 in PERL SCRIPT
796
797  my $itemstatushash = getitemstatus;
798  my @itemstatusloop;
799  foreach my $thisstatus (keys %$itemstatushash) {
800      my %row =(value => $thisstatus,
801                  statusname => $itemstatushash->{$thisstatus}->{'statusname'},
802              );
803      push @itemstatusloop, \%row;
804  }
805  $template->param(statusloop=>\@itemstatusloop);
806
807 =head3 in TEMPLATE
808
809 <select name="statusloop" id="statusloop">
810     <option value="">Default</option>
811     [% FOREACH statusloo IN statusloop %]
812         [% IF ( statusloo.selected ) %]
813             <option value="[% statusloo.value %]" selected="selected">[% statusloo.statusname %]</option>
814         [% ELSE %]
815             <option value="[% statusloo.value %]">[% statusloo.statusname %]</option>
816         [% END %]
817     [% END %]
818 </select>
819
820 =cut
821
822 sub GetItemStatus {
823
824     # returns a reference to a hash of references to status...
825     my ($fwk) = @_;
826     my %itemstatus;
827     my $dbh = C4::Context->dbh;
828     my $sth;
829     $fwk = '' unless ($fwk);
830     my ( $tag, $subfield ) =
831       GetMarcFromKohaField( "items.notforloan", $fwk );
832     if ( $tag and $subfield ) {
833         my $sth =
834           $dbh->prepare(
835             "SELECT authorised_value
836             FROM marc_subfield_structure
837             WHERE tagfield=?
838                 AND tagsubfield=?
839                 AND frameworkcode=?
840             "
841           );
842         $sth->execute( $tag, $subfield, $fwk );
843         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
844             my $authvalsth =
845               $dbh->prepare(
846                 "SELECT authorised_value,lib
847                 FROM authorised_values 
848                 WHERE category=? 
849                 ORDER BY lib
850                 "
851               );
852             $authvalsth->execute($authorisedvaluecat);
853             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
854                 $itemstatus{$authorisedvalue} = $lib;
855             }
856             return \%itemstatus;
857             exit 1;
858         }
859         else {
860
861             #No authvalue list
862             # build default
863         }
864     }
865
866     #No authvalue list
867     #build default
868     $itemstatus{"1"} = "Not For Loan";
869     return \%itemstatus;
870 }
871
872 =head2 GetItemLocation
873
874   $itemlochash = GetItemLocation($fwk);
875
876 Returns a list of valid values for the
877 C<items.location> field.
878
879 NOTE: does B<not> return an individual item's
880 location.
881
882 where fwk stands for an optional framework code.
883 Create a location selector with the following code
884
885 =head3 in PERL SCRIPT
886
887   my $itemlochash = getitemlocation;
888   my @itemlocloop;
889   foreach my $thisloc (keys %$itemlochash) {
890       my $selected = 1 if $thisbranch eq $branch;
891       my %row =(locval => $thisloc,
892                   selected => $selected,
893                   locname => $itemlochash->{$thisloc},
894                );
895       push @itemlocloop, \%row;
896   }
897   $template->param(itemlocationloop => \@itemlocloop);
898
899 =head3 in TEMPLATE
900
901   <select name="location">
902       <option value="">Default</option>
903   <!-- TMPL_LOOP name="itemlocationloop" -->
904       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
905   <!-- /TMPL_LOOP -->
906   </select>
907
908 =cut
909
910 sub GetItemLocation {
911
912     # returns a reference to a hash of references to location...
913     my ($fwk) = @_;
914     my %itemlocation;
915     my $dbh = C4::Context->dbh;
916     my $sth;
917     $fwk = '' unless ($fwk);
918     my ( $tag, $subfield ) =
919       GetMarcFromKohaField( "items.location", $fwk );
920     if ( $tag and $subfield ) {
921         my $sth =
922           $dbh->prepare(
923             "SELECT authorised_value
924             FROM marc_subfield_structure 
925             WHERE tagfield=? 
926                 AND tagsubfield=? 
927                 AND frameworkcode=?"
928           );
929         $sth->execute( $tag, $subfield, $fwk );
930         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
931             my $authvalsth =
932               $dbh->prepare(
933                 "SELECT authorised_value,lib
934                 FROM authorised_values
935                 WHERE category=?
936                 ORDER BY lib"
937               );
938             $authvalsth->execute($authorisedvaluecat);
939             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
940                 $itemlocation{$authorisedvalue} = $lib;
941             }
942             return \%itemlocation;
943             exit 1;
944         }
945         else {
946
947             #No authvalue list
948             # build default
949         }
950     }
951
952     #No authvalue list
953     #build default
954     $itemlocation{"1"} = "Not For Loan";
955     return \%itemlocation;
956 }
957
958 =head2 GetLostItems
959
960   $items = GetLostItems( $where, $orderby );
961
962 This function gets a list of lost items.
963
964 =over 2
965
966 =item input:
967
968 C<$where> is a hashref. it containts a field of the items table as key
969 and the value to match as value. For example:
970
971 { barcode    => 'abc123',
972   homebranch => 'CPL',    }
973
974 C<$orderby> is a field of the items table by which the resultset
975 should be orderd.
976
977 =item return:
978
979 C<$items> is a reference to an array full of hashrefs with columns
980 from the "items" table as keys.
981
982 =item usage in the perl script:
983
984   my $where = { barcode => '0001548' };
985   my $items = GetLostItems( $where, "homebranch" );
986   $template->param( itemsloop => $items );
987
988 =back
989
990 =cut
991
992 sub GetLostItems {
993     # Getting input args.
994     my $where   = shift;
995     my $orderby = shift;
996     my $dbh     = C4::Context->dbh;
997
998     my $query   = "
999         SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch,
1000                itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber
1001         FROM   items
1002             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
1003             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
1004             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
1005         WHERE
1006                 authorised_values.category = 'LOST'
1007                 AND itemlost IS NOT NULL
1008                 AND itemlost <> 0
1009     ";
1010     my @query_parameters;
1011     foreach my $key (keys %$where) {
1012         $query .= " AND $key LIKE ?";
1013         push @query_parameters, "%$where->{$key}%";
1014     }
1015     my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
1016     
1017     if ( defined $orderby && grep($orderby, @ordervalues)) {
1018         $query .= ' ORDER BY '.$orderby;
1019     }
1020
1021     my $sth = $dbh->prepare($query);
1022     $sth->execute( @query_parameters );
1023     my $items = [];
1024     while ( my $row = $sth->fetchrow_hashref ){
1025         push @$items, $row;
1026     }
1027     return $items;
1028 }
1029
1030 =head2 GetItemsForInventory
1031
1032 ($itemlist, $iTotalRecords)  = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype, $ignoreissued, $datelastseen, $branchcode, $offset, $size, $statushash);
1033
1034 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
1035
1036 The sub returns a reference to a list of hashes, each containing
1037 itemnumber, author, title, barcode, item callnumber, and date last
1038 seen. It is ordered by callnumber then title.
1039
1040 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
1041 the datelastseen can be used to specify that you want to see items not seen since a past date only.
1042 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
1043 $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.
1044
1045 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
1046
1047 =cut
1048
1049 sub GetItemsForInventory {
1050     my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branchcode, $branch, $offset, $size, $statushash ) = @_;
1051     my $dbh = C4::Context->dbh;
1052     my ( @bind_params, @where_strings );
1053
1054     my $select_columns = q{
1055         SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
1056     };
1057     my $select_count = q{SELECT COUNT(*)};
1058     my $query = q{
1059         FROM items
1060         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
1061         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
1062     };
1063     if ($statushash){
1064         for my $authvfield (keys %$statushash){
1065             if ( scalar @{$statushash->{$authvfield}} > 0 ){
1066                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
1067                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
1068             }
1069         }
1070     }
1071
1072     if ($minlocation) {
1073         push @where_strings, 'itemcallnumber >= ?';
1074         push @bind_params, $minlocation;
1075     }
1076
1077     if ($maxlocation) {
1078         push @where_strings, 'itemcallnumber <= ?';
1079         push @bind_params, $maxlocation;
1080     }
1081
1082     if ($datelastseen) {
1083         $datelastseen = format_date_in_iso($datelastseen);  
1084         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
1085         push @bind_params, $datelastseen;
1086     }
1087
1088     if ( $location ) {
1089         push @where_strings, 'items.location = ?';
1090         push @bind_params, $location;
1091     }
1092
1093     if ( $branchcode ) {
1094         if($branch eq "homebranch"){
1095         push @where_strings, 'items.homebranch = ?';
1096         }else{
1097             push @where_strings, 'items.holdingbranch = ?';
1098         }
1099         push @bind_params, $branchcode;
1100     }
1101
1102     if ( $itemtype ) {
1103         push @where_strings, 'biblioitems.itemtype = ?';
1104         push @bind_params, $itemtype;
1105     }
1106
1107     if ( $ignoreissued) {
1108         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1109         push @where_strings, 'issues.date_due IS NULL';
1110     }
1111
1112     if ( @where_strings ) {
1113         $query .= 'WHERE ';
1114         $query .= join ' AND ', @where_strings;
1115     }
1116     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1117     my $count_query = $select_count . $query;
1118     $query .= " LIMIT $offset, $size" if ($offset and $size);
1119     $query = $select_columns . $query;
1120     my $sth = $dbh->prepare($query);
1121     $sth->execute( @bind_params );
1122
1123     my @results = ();
1124     my $tmpresults = $sth->fetchall_arrayref({});
1125     $sth = $dbh->prepare( $count_query );
1126     $sth->execute( @bind_params );
1127     my ($iTotalRecords) = $sth->fetchrow_array();
1128
1129     foreach my $row (@$tmpresults) {
1130
1131         # Auth values
1132         foreach (keys %$row) {
1133             # If the koha field is mapped to a marc field
1134             my ($f, $sf) = GetMarcFromKohaField("items.$_", $row->{'frameworkcode'});
1135             if ($f and $sf) {
1136                 # We replace the code with it's description
1137                 my $authvals = C4::Koha::GetKohaAuthorisedValuesFromField($f, $sf, $row->{'frameworkcode'});
1138                 $row->{$_} = $authvals->{$row->{$_}} if defined $authvals->{$row->{$_}};
1139             }
1140         }
1141         push @results, $row;
1142     }
1143
1144     return (\@results, $iTotalRecords);
1145 }
1146
1147 =head2 GetItemsCount
1148
1149   $count = &GetItemsCount( $biblionumber);
1150
1151 This function return count of item with $biblionumber
1152
1153 =cut
1154
1155 sub GetItemsCount {
1156     my ( $biblionumber ) = @_;
1157     my $dbh = C4::Context->dbh;
1158     my $query = "SELECT count(*)
1159           FROM  items 
1160           WHERE biblionumber=?";
1161     my $sth = $dbh->prepare($query);
1162     $sth->execute($biblionumber);
1163     my $count = $sth->fetchrow;  
1164     return ($count);
1165 }
1166
1167 =head2 GetItemInfosOf
1168
1169   GetItemInfosOf(@itemnumbers);
1170
1171 =cut
1172
1173 sub GetItemInfosOf {
1174     my @itemnumbers = @_;
1175
1176     my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''";
1177
1178     my $query = "
1179         SELECT *
1180         FROM items
1181         WHERE itemnumber IN ($itemnumber_values)
1182     ";
1183     return get_infos_of( $query, 'itemnumber' );
1184 }
1185
1186 =head2 GetItemsByBiblioitemnumber
1187
1188   GetItemsByBiblioitemnumber($biblioitemnumber);
1189
1190 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1191 Called by C<C4::XISBN>
1192
1193 =cut
1194
1195 sub GetItemsByBiblioitemnumber {
1196     my ( $bibitem ) = @_;
1197     my $dbh = C4::Context->dbh;
1198     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1199     # Get all items attached to a biblioitem
1200     my $i = 0;
1201     my @results; 
1202     $sth->execute($bibitem) || die $sth->errstr;
1203     while ( my $data = $sth->fetchrow_hashref ) {  
1204         # Foreach item, get circulation information
1205         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1206                                    WHERE itemnumber = ?
1207                                    AND issues.borrowernumber = borrowers.borrowernumber"
1208         );
1209         $sth2->execute( $data->{'itemnumber'} );
1210         if ( my $data2 = $sth2->fetchrow_hashref ) {
1211             # if item is out, set the due date and who it is out too
1212             $data->{'date_due'}   = $data2->{'date_due'};
1213             $data->{'cardnumber'} = $data2->{'cardnumber'};
1214             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1215         }
1216         else {
1217             # set date_due to blank, so in the template we check itemlost, and withdrawn
1218             $data->{'date_due'} = '';                                                                                                         
1219         }    # else         
1220         # Find the last 3 people who borrowed this item.                  
1221         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1222                       AND old_issues.borrowernumber = borrowers.borrowernumber
1223                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1224         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1225         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1226         my $i2 = 0;
1227         while ( my $data2 = $sth2->fetchrow_hashref ) {
1228             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1229             $data->{"card$i2"}      = $data2->{'cardnumber'};
1230             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1231             $i2++;
1232         }
1233         push(@results,$data);
1234     } 
1235     return (\@results); 
1236 }
1237
1238 =head2 GetItemsInfo
1239
1240   @results = GetItemsInfo($biblionumber);
1241
1242 Returns information about items with the given biblionumber.
1243
1244 C<GetItemsInfo> returns a list of references-to-hash. Each element
1245 contains a number of keys. Most of them are attributes from the
1246 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1247 Koha database. Other keys include:
1248
1249 =over 2
1250
1251 =item C<$data-E<gt>{branchname}>
1252
1253 The name (not the code) of the branch to which the book belongs.
1254
1255 =item C<$data-E<gt>{datelastseen}>
1256
1257 This is simply C<items.datelastseen>, except that while the date is
1258 stored in YYYY-MM-DD format in the database, here it is converted to
1259 DD/MM/YYYY format. A NULL date is returned as C<//>.
1260
1261 =item C<$data-E<gt>{datedue}>
1262
1263 =item C<$data-E<gt>{class}>
1264
1265 This is the concatenation of C<biblioitems.classification>, the book's
1266 Dewey code, and C<biblioitems.subclass>.
1267
1268 =item C<$data-E<gt>{ocount}>
1269
1270 I think this is the number of copies of the book available.
1271
1272 =item C<$data-E<gt>{order}>
1273
1274 If this is set, it is set to C<One Order>.
1275
1276 =back
1277
1278 =cut
1279
1280 sub GetItemsInfo {
1281     my ( $biblionumber ) = @_;
1282     my $dbh   = C4::Context->dbh;
1283     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1284     my $query = "
1285     SELECT items.*,
1286            biblio.*,
1287            biblioitems.volume,
1288            biblioitems.number,
1289            biblioitems.itemtype,
1290            biblioitems.isbn,
1291            biblioitems.issn,
1292            biblioitems.publicationyear,
1293            biblioitems.publishercode,
1294            biblioitems.volumedate,
1295            biblioitems.volumedesc,
1296            biblioitems.lccn,
1297            biblioitems.url,
1298            items.notforloan as itemnotforloan,
1299            issues.borrowernumber,
1300            issues.date_due as datedue,
1301            borrowers.cardnumber,
1302            borrowers.surname,
1303            borrowers.firstname,
1304            borrowers.branchcode as bcode,
1305            serial.serialseq,
1306            serial.publisheddate,
1307            itemtypes.description,
1308            itemtypes.notforloan as notforloan_per_itemtype,
1309            holding.branchurl,
1310            holding.branchname,
1311            holding.opac_info as holding_branch_opac_info,
1312            home.opac_info as home_branch_opac_info
1313     ";
1314     $query .= ", issues.onsite_checkout"
1315         if C4::Context->preference("OnSiteCheckouts");
1316     $query .= "
1317      FROM items
1318      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
1319      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
1320      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1321      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1322      LEFT JOIN issues USING (itemnumber)
1323      LEFT JOIN borrowers USING (borrowernumber)
1324      LEFT JOIN serialitems USING (itemnumber)
1325      LEFT JOIN serial USING (serialid)
1326      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1327      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1328     $query .= " LEFT JOIN issues ON issues.itemnumber = items.itemnumber"
1329         if C4::Context->preference("OnSiteCheckouts");
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 $where_fragment;
2605     if (exists($filter->{conjunction})) {
2606         my (@where_strs, @where_args);
2607         foreach my $f (@{ $filter->{filters} }) {
2608             my $fragment = _SearchItems_build_where_fragment($f);
2609             if ($fragment) {
2610                 push @where_strs, $fragment->{str};
2611                 push @where_args, @{ $fragment->{args} };
2612             }
2613         }
2614         my $where_str = '';
2615         if (@where_strs) {
2616             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
2617             $where_fragment = {
2618                 str => $where_str,
2619                 args => \@where_args,
2620             };
2621         }
2622     } else {
2623         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2624         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2625         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2626         my @operators = qw(= != > < >= <= like);
2627         my $field = $filter->{field};
2628         if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
2629             my $op = $filter->{operator};
2630             my $query = $filter->{query};
2631
2632             if (!$op or (0 == grep /^$op$/, @operators)) {
2633                 $op = '='; # default operator
2634             }
2635
2636             my $column;
2637             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
2638                 my $marcfield = $1;
2639                 my $marcsubfield = $2;
2640                 my $xpath;
2641                 if ($marcfield < 10) {
2642                     $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
2643                 } else {
2644                     $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
2645                 }
2646                 $column = "ExtractValue(marcxml, '$xpath')";
2647             } else {
2648                 $column = $field;
2649             }
2650
2651             if (ref $query eq 'ARRAY') {
2652                 if ($op eq '=') {
2653                     $op = 'IN';
2654                 } elsif ($op eq '!=') {
2655                     $op = 'NOT IN';
2656                 }
2657                 $where_fragment = {
2658                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
2659                     args => $query,
2660                 };
2661             } else {
2662                 $where_fragment = {
2663                     str => "$column $op ?",
2664                     args => [ $query ],
2665                 };
2666             }
2667         }
2668     }
2669
2670     return $where_fragment;
2671 }
2672
2673 =head2 SearchItems
2674
2675     my ($items, $total) = SearchItems($filter, $params);
2676
2677 Perform a search among items
2678
2679 $filter is a reference to a hash which can be a filter, or a combination of filters.
2680
2681 A filter has the following keys:
2682
2683 =over 2
2684
2685 =item * field: the name of a SQL column in table items
2686
2687 =item * query: the value to search in this column
2688
2689 =item * operator: comparison operator. Can be one of = != > < >= <= like
2690
2691 =back
2692
2693 A combination of filters hash the following keys:
2694
2695 =over 2
2696
2697 =item * conjunction: 'AND' or 'OR'
2698
2699 =item * filters: array ref of filters
2700
2701 =back
2702
2703 $params is a reference to a hash that can contain the following parameters:
2704
2705 =over 2
2706
2707 =item * rows: Number of items to return. 0 returns everything (default: 0)
2708
2709 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
2710                (default: 1)
2711
2712 =item * sortby: A SQL column name in items table to sort on
2713
2714 =item * sortorder: 'ASC' or 'DESC'
2715
2716 =back
2717
2718 =cut
2719
2720 sub SearchItems {
2721     my ($filter, $params) = @_;
2722
2723     $filter //= {};
2724     $params //= {};
2725     return unless ref $filter eq 'HASH';
2726     return unless ref $params eq 'HASH';
2727
2728     # Default parameters
2729     $params->{rows} ||= 0;
2730     $params->{page} ||= 1;
2731     $params->{sortby} ||= 'itemnumber';
2732     $params->{sortorder} ||= 'ASC';
2733
2734     my ($where_str, @where_args);
2735     my $where_fragment = _SearchItems_build_where_fragment($filter);
2736     if ($where_fragment) {
2737         $where_str = $where_fragment->{str};
2738         @where_args = @{ $where_fragment->{args} };
2739     }
2740
2741     my $dbh = C4::Context->dbh;
2742     my $query = q{
2743         SELECT SQL_CALC_FOUND_ROWS items.*
2744         FROM items
2745           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
2746           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
2747     };
2748     if (defined $where_str and $where_str ne '') {
2749         $query .= qq{ WHERE $where_str };
2750     }
2751
2752     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2753     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2754     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2755     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2756         ? $params->{sortby} : 'itemnumber';
2757     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2758     $query .= qq{ ORDER BY $sortby $sortorder };
2759
2760     my $rows = $params->{rows};
2761     my @limit_args;
2762     if ($rows > 0) {
2763         my $offset = $rows * ($params->{page}-1);
2764         $query .= qq { LIMIT ?, ? };
2765         push @limit_args, $offset, $rows;
2766     }
2767
2768     my $sth = $dbh->prepare($query);
2769     my $rv = $sth->execute(@where_args, @limit_args);
2770
2771     return unless ($rv);
2772     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2773
2774     return ($sth->fetchall_arrayref({}), $total_rows);
2775 }
2776
2777
2778 =head1  OTHER FUNCTIONS
2779
2780 =head2 _find_value
2781
2782   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2783
2784 Find the given $subfield in the given $tag in the given
2785 MARC::Record $record.  If the subfield is found, returns
2786 the (indicators, value) pair; otherwise, (undef, undef) is
2787 returned.
2788
2789 PROPOSITION :
2790 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2791 I suggest we export it from this module.
2792
2793 =cut
2794
2795 sub _find_value {
2796     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2797     my @result;
2798     my $indicator;
2799     if ( $tagfield < 10 ) {
2800         if ( $record->field($tagfield) ) {
2801             push @result, $record->field($tagfield)->data();
2802         } else {
2803             push @result, "";
2804         }
2805     } else {
2806         foreach my $field ( $record->field($tagfield) ) {
2807             my @subfields = $field->subfields();
2808             foreach my $subfield (@subfields) {
2809                 if ( @$subfield[0] eq $insubfield ) {
2810                     push @result, @$subfield[1];
2811                     $indicator = $field->indicator(1) . $field->indicator(2);
2812                 }
2813             }
2814         }
2815     }
2816     return ( $indicator, @result );
2817 }
2818
2819
2820 =head2 PrepareItemrecordDisplay
2821
2822   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2823
2824 Returns a hash with all the fields for Display a given item data in a template
2825
2826 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2827
2828 =cut
2829
2830 sub PrepareItemrecordDisplay {
2831
2832     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2833
2834     my $dbh = C4::Context->dbh;
2835     $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum;
2836     my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
2837     my $tagslib = &GetMarcStructure( 1, $frameworkcode );
2838
2839     # return nothing if we don't have found an existing framework.
2840     return q{} unless $tagslib;
2841     my $itemrecord;
2842     if ($itemnum) {
2843         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2844     }
2845     my @loop_data;
2846
2847     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2848     my $query = qq{
2849         SELECT authorised_value,lib FROM authorised_values
2850     };
2851     $query .= qq{
2852         LEFT JOIN authorised_values_branches ON ( id = av_id )
2853     } if $branch_limit;
2854     $query .= qq{
2855         WHERE category = ?
2856     };
2857     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2858     $query .= qq{ ORDER BY lib};
2859     my $authorised_values_sth = $dbh->prepare( $query );
2860     foreach my $tag ( sort keys %{$tagslib} ) {
2861         my $previous_tag = '';
2862         if ( $tag ne '' ) {
2863
2864             # loop through each subfield
2865             my $cntsubf;
2866             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2867                 next if ( subfield_is_koha_internal_p($subfield) );
2868                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2869                 my %subfield_data;
2870                 $subfield_data{tag}           = $tag;
2871                 $subfield_data{subfield}      = $subfield;
2872                 $subfield_data{countsubfield} = $cntsubf++;
2873                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2874                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2875
2876                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2877                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2878                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2879                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2880                 $subfield_data{hidden}     = "display:none"
2881                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2882                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2883                 my ( $x, $defaultvalue );
2884                 if ($itemrecord) {
2885                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2886                 }
2887                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2888                 if ( !defined $defaultvalue ) {
2889                     $defaultvalue = q||;
2890                 } else {
2891                     $defaultvalue =~ s/"/&quot;/g;
2892                 }
2893
2894                 # search for itemcallnumber if applicable
2895                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2896                     && C4::Context->preference('itemcallnumber') ) {
2897                     my $CNtag      = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
2898                     my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
2899                     if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
2900                         $defaultvalue = $field->subfield($CNsubfield);
2901                     }
2902                 }
2903                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2904                     && $defaultvalues
2905                     && $defaultvalues->{'callnumber'} ) {
2906                     if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){
2907                         # if the item record exists, only use default value if the item has no callnumber
2908                         $defaultvalue = $defaultvalues->{callnumber};
2909                     } elsif ( !$itemrecord and $defaultvalues ) {
2910                         # if the item record *doesn't* exists, always use the default value
2911                         $defaultvalue = $defaultvalues->{callnumber};
2912                     }
2913                 }
2914                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2915                     && $defaultvalues
2916                     && $defaultvalues->{'branchcode'} ) {
2917                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2918                         $defaultvalue = $defaultvalues->{branchcode};
2919                     }
2920                 }
2921                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2922                     && $defaultvalues
2923                     && $defaultvalues->{'location'} ) {
2924
2925                     if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) {
2926                         # if the item record exists, only use default value if the item has no locationr
2927                         $defaultvalue = $defaultvalues->{location};
2928                     } elsif ( !$itemrecord and $defaultvalues ) {
2929                         # if the item record *doesn't* exists, always use the default value
2930                         $defaultvalue = $defaultvalues->{location};
2931                     }
2932                 }
2933                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2934                     my @authorised_values;
2935                     my %authorised_lib;
2936
2937                     # builds list, depending on authorised value...
2938                     #---- branch
2939                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2940                         if (   ( C4::Context->preference("IndependentBranches") )
2941                             && !C4::Context->IsSuperLibrarian() ) {
2942                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2943                             $sth->execute( C4::Context->userenv->{branch} );
2944                             push @authorised_values, ""
2945                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2946                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2947                                 push @authorised_values, $branchcode;
2948                                 $authorised_lib{$branchcode} = $branchname;
2949                             }
2950                         } else {
2951                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2952                             $sth->execute;
2953                             push @authorised_values, ""
2954                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2955                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2956                                 push @authorised_values, $branchcode;
2957                                 $authorised_lib{$branchcode} = $branchname;
2958                             }
2959                         }
2960
2961                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2962                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2963                             $defaultvalue = $defaultvalues->{branchcode};
2964                         }
2965
2966                         #----- itemtypes
2967                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2968                         my $sth = $dbh->prepare( "SELECT itemtype,description FROM itemtypes ORDER BY description" );
2969                         $sth->execute;
2970                         push @authorised_values, ""
2971                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2972                         while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) {
2973                             push @authorised_values, $itemtype;
2974                             $authorised_lib{$itemtype} = $description;
2975                         }
2976                         #---- class_sources
2977                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2978                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2979
2980                         my $class_sources = GetClassSources();
2981                         my $default_source = C4::Context->preference("DefaultClassificationSource");
2982
2983                         foreach my $class_source (sort keys %$class_sources) {
2984                             next unless $class_sources->{$class_source}->{'used'} or
2985                                         ($class_source eq $default_source);
2986                             push @authorised_values, $class_source;
2987                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2988                         }
2989
2990                         $defaultvalue = $default_source;
2991
2992                         #---- "true" authorised value
2993                     } else {
2994                         $authorised_values_sth->execute(
2995                             $tagslib->{$tag}->{$subfield}->{authorised_value},
2996                             $branch_limit ? $branch_limit : ()
2997                         );
2998                         push @authorised_values, ""
2999                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
3000                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
3001                             push @authorised_values, $value;
3002                             $authorised_lib{$value} = $lib;
3003                         }
3004                     }
3005                     $subfield_data{marc_value} = {
3006                         type    => 'select',
3007                         values  => \@authorised_values,
3008                         default => "$defaultvalue",
3009                         labels  => \%authorised_lib,
3010                     };
3011                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
3012                         # opening plugin
3013                         my $plugin = C4::Context->intranetdir . "/cataloguing/value_builder/" . $tagslib->{$tag}->{$subfield}->{'value_builder'};
3014                         if (do $plugin) {
3015                             my $extended_param = plugin_parameters( $dbh, undef, $tagslib, $subfield_data{id}, undef );
3016                             my ( $function_name, $javascript ) = plugin_javascript( $dbh, undef, $tagslib, $subfield_data{id}, undef );
3017                             $subfield_data{random}     = int(rand(1000000));    # why do we need 2 different randoms?
3018                             $subfield_data{marc_value} = qq[<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255"
3019                                 onfocus="Focus$function_name($subfield_data{random}, '$subfield_data{id}');"
3020                                  onblur=" Blur$function_name($subfield_data{random}, '$subfield_data{id}');" />
3021                                 <a href="#" class="buttonDot" onclick="Clic$function_name('$subfield_data{id}'); return false;" title="Tag Editor">...</a>
3022                                 $javascript];
3023                         } else {
3024                             warn "Plugin Failed: $plugin";
3025                             $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
3026                         }
3027                 }
3028                 elsif ( $tag eq '' ) {       # it's an hidden field
3029                     $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" />);
3030                 }
3031                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
3032                     $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" />);
3033                 }
3034                 elsif ( length($defaultvalue) > 100
3035                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
3036                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
3037                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
3038                                   500 <= $tag && $tag < 600                     )
3039                           ) {
3040                     # oversize field (textarea)
3041                     $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");
3042                 } else {
3043                     $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
3044                 }
3045                 push( @loop_data, \%subfield_data );
3046             }
3047         }
3048     }
3049     my $itemnumber;
3050     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
3051         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
3052     }
3053     return {
3054         'itemtagfield'    => $itemtagfield,
3055         'itemtagsubfield' => $itemtagsubfield,
3056         'itemnumber'      => $itemnumber,
3057         'iteminformation' => \@loop_data
3058     };
3059 }
3060
3061 1;