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