bug 5579: correctly signal when to reindex bibs
[koha_fer] / 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 C4::Branch;
33 require C4::Reserves;
34 use C4::Charset;
35 use C4::Acquisition;
36 use List::MoreUtils qw/any/;
37
38 use vars qw($VERSION @ISA @EXPORT);
39
40 BEGIN {
41     $VERSION = 3.01;
42
43         require Exporter;
44     @ISA = qw( Exporter );
45
46     # function exports
47     @EXPORT = qw(
48         GetItem
49         AddItemFromMarc
50         AddItem
51         AddItemBatchFromMarc
52         ModItemFromMarc
53                 Item2Marc
54         ModItem
55         ModDateLastSeen
56         ModItemTransfer
57         DelItem
58     
59         CheckItemPreSave
60     
61         GetItemStatus
62         GetItemLocation
63         GetLostItems
64         GetItemsForInventory
65         GetItemsCount
66         GetItemInfosOf
67         GetItemsByBiblioitemnumber
68         GetItemsInfo
69         GetItemsLocationInfo
70         get_itemnumbers_of
71         GetItemnumberFromBarcode
72         GetBarcodeFromItemnumber
73       GetHiddenItemnumbers
74
75                 DelItemCheck
76                 MoveItemFromBiblio 
77                 GetLatestAcquisitions
78         CartToShelf
79     );
80 }
81
82 =head1 NAME
83
84 C4::Items - item management functions
85
86 =head1 DESCRIPTION
87
88 This module contains an API for manipulating item 
89 records in Koha, and is used by cataloguing, circulation,
90 acquisitions, and serials management.
91
92 A Koha item record is stored in two places: the
93 items table and embedded in a MARC tag in the XML
94 version of the associated bib record in C<biblioitems.marcxml>.
95 This is done to allow the item information to be readily
96 indexed (e.g., by Zebra), but means that each item
97 modification transaction must keep the items table
98 and the MARC XML in sync at all times.
99
100 Consequently, all code that creates, modifies, or deletes
101 item records B<must> use an appropriate function from 
102 C<C4::Items>.  If no existing function is suitable, it is
103 better to add one to C<C4::Items> than to use add
104 one-off SQL statements to add or modify items.
105
106 The items table will be considered authoritative.  In other
107 words, if there is ever a discrepancy between the items
108 table and the MARC XML, the items table should be considered
109 accurate.
110
111 =head1 HISTORICAL NOTE
112
113 Most of the functions in C<C4::Items> were originally in
114 the C<C4::Biblio> module.
115
116 =head1 CORE EXPORTED FUNCTIONS
117
118 The following functions are meant for use by users
119 of C<C4::Items>
120
121 =cut
122
123 =head2 GetItem
124
125   $item = GetItem($itemnumber,$barcode,$serial);
126
127 Return item information, for a given itemnumber or barcode.
128 The return value is a hashref mapping item column
129 names to values.  If C<$serial> is true, include serial publication data.
130
131 =cut
132
133 sub GetItem {
134     my ($itemnumber,$barcode, $serial) = @_;
135     my $dbh = C4::Context->dbh;
136         my $data;
137     if ($itemnumber) {
138         my $sth = $dbh->prepare("
139             SELECT * FROM items 
140             WHERE itemnumber = ?");
141         $sth->execute($itemnumber);
142         $data = $sth->fetchrow_hashref;
143     } else {
144         my $sth = $dbh->prepare("
145             SELECT * FROM items 
146             WHERE barcode = ?"
147             );
148         $sth->execute($barcode);                
149         $data = $sth->fetchrow_hashref;
150     }
151     if ( $serial) {      
152     my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
153         $ssth->execute($data->{'itemnumber'}) ;
154         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
155     }
156         #if we don't have an items.itype, use biblioitems.itemtype.
157         if( ! $data->{'itype'} ) {
158                 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems  WHERE biblionumber = ?");
159                 $sth->execute($data->{'biblionumber'});
160                 ($data->{'itype'}) = $sth->fetchrow_array;
161         }
162     return $data;
163 }    # sub GetItem
164
165 =head2 CartToShelf
166
167   CartToShelf($itemnumber);
168
169 Set the current shelving location of the item record
170 to its stored permanent shelving location.  This is
171 primarily used to indicate when an item whose current
172 location is a special processing ('PROC') or shelving cart
173 ('CART') location is back in the stacks.
174
175 =cut
176
177 sub CartToShelf {
178     my ( $itemnumber ) = @_;
179
180     unless ( $itemnumber ) {
181         croak "FAILED CartToShelf() - no itemnumber supplied";
182     }
183
184     my $item = GetItem($itemnumber);
185     $item->{location} = $item->{permanent_location};
186     ModItem($item, undef, $itemnumber);
187 }
188
189 =head2 AddItemFromMarc
190
191   my ($biblionumber, $biblioitemnumber, $itemnumber) 
192       = AddItemFromMarc($source_item_marc, $biblionumber);
193
194 Given a MARC::Record object containing an embedded item
195 record and a biblionumber, create a new item record.
196
197 =cut
198
199 sub AddItemFromMarc {
200     my ( $source_item_marc, $biblionumber ) = @_;
201     my $dbh = C4::Context->dbh;
202
203     # parse item hash from MARC
204     my $frameworkcode = GetFrameworkCode( $biblionumber );
205         my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
206         
207         my $localitemmarc=MARC::Record->new;
208         $localitemmarc->append_fields($source_item_marc->field($itemtag));
209     my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
210     my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
211     return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
212 }
213
214 =head2 AddItem
215
216   my ($biblionumber, $biblioitemnumber, $itemnumber) 
217       = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
218
219 Given a hash containing item column names as keys,
220 create a new Koha item record.
221
222 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
223 do not need to be supplied for general use; they exist
224 simply to allow them to be picked up from AddItemFromMarc.
225
226 The final optional parameter, C<$unlinked_item_subfields>, contains
227 an arrayref containing subfields present in the original MARC
228 representation of the item (e.g., from the item editor) that are
229 not mapped to C<items> columns directly but should instead
230 be stored in C<items.more_subfields_xml> and included in 
231 the biblio items tag for display and indexing.
232
233 =cut
234
235 sub AddItem {
236     my $item = shift;
237     my $biblionumber = shift;
238
239     my $dbh           = @_ ? shift : C4::Context->dbh;
240     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
241     my $unlinked_item_subfields;  
242     if (@_) {
243         $unlinked_item_subfields = shift
244     };
245
246     # needs old biblionumber and biblioitemnumber
247     $item->{'biblionumber'} = $biblionumber;
248     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
249     $sth->execute( $item->{'biblionumber'} );
250     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
251
252     _set_defaults_for_add($item);
253     _set_derived_columns_for_add($item);
254     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
255     # FIXME - checks here
256     unless ( $item->{itype} ) {  # default to biblioitem.itemtype if no itype
257         my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
258         $itype_sth->execute( $item->{'biblionumber'} );
259         ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
260     }
261
262         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
263     $item->{'itemnumber'} = $itemnumber;
264
265     # create MARC tag representing item and add to bib
266     #my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
267     #_add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
268     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver", undef, undef );
269    
270     logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
271     
272     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
273 }
274
275 =head2 AddItemBatchFromMarc
276
277   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
278              $biblionumber, $biblioitemnumber, $frameworkcode);
279
280 Efficiently create item records from a MARC biblio record with
281 embedded item fields.  This routine is suitable for batch jobs.
282
283 This API assumes that the bib record has already been
284 saved to the C<biblio> and C<biblioitems> tables.  It does
285 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
286 are populated, but it will do so via a call to ModBibiloMarc.
287
288 The goal of this API is to have a similar effect to using AddBiblio
289 and AddItems in succession, but without inefficient repeated
290 parsing of the MARC XML bib record.
291
292 This function returns an arrayref of new itemsnumbers and an arrayref of item
293 errors encountered during the processing.  Each entry in the errors
294 list is a hashref containing the following keys:
295
296 =over
297
298 =item item_sequence
299
300 Sequence number of original item tag in the MARC record.
301
302 =item item_barcode
303
304 Item barcode, provide to assist in the construction of
305 useful error messages.
306
307 =item error_condition
308
309 Code representing the error condition.  Can be 'duplicate_barcode',
310 'invalid_homebranch', or 'invalid_holdingbranch'.
311
312 =item error_information
313
314 Additional information appropriate to the error condition.
315
316 =back
317
318 =cut
319
320 sub AddItemBatchFromMarc {
321     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
322     my $error;
323     my @itemnumbers = ();
324     my @errors = ();
325     my $dbh = C4::Context->dbh;
326
327     # loop through the item tags and start creating items
328     my @bad_item_fields = ();
329     my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
330     my $item_sequence_num = 0;
331     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
332         $item_sequence_num++;
333         # we take the item field and stick it into a new
334         # MARC record -- this is required so far because (FIXME)
335         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
336         # and there is no TransformMarcFieldToKoha
337         my $temp_item_marc = MARC::Record->new();
338         $temp_item_marc->append_fields($item_field);
339     
340         # add biblionumber and biblioitemnumber
341         my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
342         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
343         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
344         $item->{'biblionumber'} = $biblionumber;
345         $item->{'biblioitemnumber'} = $biblioitemnumber;
346
347         # check for duplicate barcode
348         my %item_errors = CheckItemPreSave($item);
349         if (%item_errors) {
350             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
351             push @bad_item_fields, $item_field;
352             next ITEMFIELD;
353         }
354
355         _set_defaults_for_add($item);
356         _set_derived_columns_for_add($item);
357         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
358         warn $error if $error;
359         push @itemnumbers, $itemnumber; # FIXME not checking error
360         $item->{'itemnumber'} = $itemnumber;
361
362         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
363
364         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
365         $item_field->replace_with($new_item_marc->field($itemtag));
366     }
367
368     # remove any MARC item fields for rejected items
369     foreach my $item_field (@bad_item_fields) {
370         $record->delete_field($item_field);
371     }
372
373     # update the MARC biblio
374  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
375
376     return (\@itemnumbers, \@errors);
377 }
378
379 =head2 ModItemFromMarc
380
381   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
382
383 This function updates an item record based on a supplied
384 C<MARC::Record> object containing an embedded item field.
385 This API is meant for the use of C<additem.pl>; for 
386 other purposes, C<ModItem> should be used.
387
388 This function uses the hash %default_values_for_mod_from_marc,
389 which contains default values for item fields to
390 apply when modifying an item.  This is needed beccause
391 if an item field's value is cleared, TransformMarcToKoha
392 does not include the column in the
393 hash that's passed to ModItem, which without
394 use of this hash makes it impossible to clear
395 an item field's value.  See bug 2466.
396
397 Note that only columns that can be directly
398 changed from the cataloging and serials
399 item editors are included in this hash.
400
401 =cut
402
403 my %default_values_for_mod_from_marc = (
404     barcode              => undef, 
405     booksellerid         => undef, 
406     ccode                => undef, 
407     'items.cn_source'    => undef, 
408     copynumber           => undef, 
409     damaged              => 0,
410 #    dateaccessioned      => undef,
411     enumchron            => undef, 
412     holdingbranch        => undef, 
413     homebranch           => undef, 
414     itemcallnumber       => undef, 
415     itemlost             => 0,
416     itemnotes            => undef, 
417     itype                => undef, 
418     location             => undef, 
419     materials            => undef, 
420     notforloan           => 0,
421     paidfor              => undef, 
422     price                => undef, 
423     replacementprice     => undef, 
424     replacementpricedate => undef, 
425     restricted           => undef, 
426     stack                => undef, 
427     stocknumber          => undef, 
428     uri                  => undef, 
429     wthdrawn             => 0,
430 );
431
432 sub ModItemFromMarc {
433     my $item_marc = shift;
434     my $biblionumber = shift;
435     my $itemnumber = shift;
436
437     my $dbh           = C4::Context->dbh;
438     my $frameworkcode = GetFrameworkCode($biblionumber);
439     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
440
441     my $localitemmarc = MARC::Record->new;
442     $localitemmarc->append_fields( $item_marc->field($itemtag) );
443     my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items' );
444     foreach my $item_field ( keys %default_values_for_mod_from_marc ) {
445         $item->{$item_field} = $default_values_for_mod_from_marc{$item_field} unless (exists $item->{$item_field});
446     }
447     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
448
449     return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); 
450 }
451
452 =head2 ModItem
453
454   ModItem({ column => $newvalue }, $biblionumber, 
455                   $itemnumber[, $original_item_marc]);
456
457 Change one or more columns in an item record and update
458 the MARC representation of the item.
459
460 The first argument is a hashref mapping from item column
461 names to the new values.  The second and third arguments
462 are the biblionumber and itemnumber, respectively.
463
464 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
465 an arrayref containing subfields present in the original MARC
466 representation of the item (e.g., from the item editor) that are
467 not mapped to C<items> columns directly but should instead
468 be stored in C<items.more_subfields_xml> and included in 
469 the biblio items tag for display and indexing.
470
471 If one of the changed columns is used to calculate
472 the derived value of a column such as C<items.cn_sort>, 
473 this routine will perform the necessary calculation
474 and set the value.
475
476 =cut
477
478 sub ModItem {
479     my $item = shift;
480     my $biblionumber = shift;
481     my $itemnumber = shift;
482
483     # if $biblionumber is undefined, get it from the current item
484     unless (defined $biblionumber) {
485         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
486     }
487
488     my $dbh           = @_ ? shift : C4::Context->dbh;
489     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
490     
491     my $unlinked_item_subfields;  
492     if (@_) {
493         $unlinked_item_subfields = shift;
494         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
495     };
496
497     $item->{'itemnumber'} = $itemnumber or return undef;
498     _set_derived_columns_for_mod($item);
499     _do_column_fixes_for_mod($item);
500     # FIXME add checks
501     # duplicate barcode
502     # attempt to change itemnumber
503     # attempt to change biblionumber (if we want
504     # an API to relink an item to a different bib,
505     # it should be a separate function)
506
507     # update items table
508     _koha_modify_item($item);
509
510     # update biblio MARC XML
511     my $whole_item = GetItem($itemnumber) or die "FAILED GetItem($itemnumber)";
512     ModZebra( $whole_item->{biblionumber}, "specialUpdate", "biblioserver", undef, undef );
513
514     unless (defined $unlinked_item_subfields) {
515         $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'});
516     }
517     my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode, $unlinked_item_subfields) 
518         or die "FAILED _marc_from_item_hash($whole_item, $frameworkcode)";
519     
520     #_replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
521         ($new_item_marc       eq '0') and die "$new_item_marc is '0', not hashref";  # logaction line would crash anyway
522     logaction("CATALOGUING", "MODIFY", $itemnumber, $new_item_marc->as_formatted) if C4::Context->preference("CataloguingLog");
523 }
524
525 =head2 ModItemTransfer
526
527   ModItemTransfer($itenumber, $frombranch, $tobranch);
528
529 Marks an item as being transferred from one branch
530 to another.
531
532 =cut
533
534 sub ModItemTransfer {
535     my ( $itemnumber, $frombranch, $tobranch ) = @_;
536
537     my $dbh = C4::Context->dbh;
538
539     #new entry in branchtransfers....
540     my $sth = $dbh->prepare(
541         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
542         VALUES (?, ?, NOW(), ?)");
543     $sth->execute($itemnumber, $frombranch, $tobranch);
544
545     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
546     ModDateLastSeen($itemnumber);
547     return;
548 }
549
550 =head2 ModDateLastSeen
551
552   ModDateLastSeen($itemnum);
553
554 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
555 C<$itemnum> is the item number
556
557 =cut
558
559 sub ModDateLastSeen {
560     my ($itemnumber) = @_;
561     
562     my $today = C4::Dates->new();    
563     ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
564 }
565
566 =head2 DelItem
567
568   DelItem($dbh, $biblionumber, $itemnumber);
569
570 Exported function (core API) for deleting an item record in Koha.
571
572 =cut
573
574 sub DelItem {
575     my ( $dbh, $biblionumber, $itemnumber ) = @_;
576     
577     # FIXME check the item has no current issues
578     
579     _koha_delete_item( $dbh, $itemnumber );
580
581     # get the MARC record
582     my $record = GetMarcBiblio($biblionumber);
583     ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
584
585     # backup the record
586     my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
587     $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
588
589     #search item field code
590     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
591 }
592
593 =head2 CheckItemPreSave
594
595     my $item_ref = TransformMarcToKoha($marc, 'items');
596     # do stuff
597     my %errors = CheckItemPreSave($item_ref);
598     if (exists $errors{'duplicate_barcode'}) {
599         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
600     } elsif (exists $errors{'invalid_homebranch'}) {
601         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
602     } elsif (exists $errors{'invalid_holdingbranch'}) {
603         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
604     } else {
605         print "item is OK";
606     }
607
608 Given a hashref containing item fields, determine if it can be
609 inserted or updated in the database.  Specifically, checks for
610 database integrity issues, and returns a hash containing any
611 of the following keys, if applicable.
612
613 =over 2
614
615 =item duplicate_barcode
616
617 Barcode, if it duplicates one already found in the database.
618
619 =item invalid_homebranch
620
621 Home branch, if not defined in branches table.
622
623 =item invalid_holdingbranch
624
625 Holding branch, if not defined in branches table.
626
627 =back
628
629 This function does NOT implement any policy-related checks,
630 e.g., whether current operator is allowed to save an
631 item that has a given branch code.
632
633 =cut
634
635 sub CheckItemPreSave {
636     my $item_ref = shift;
637
638     my %errors = ();
639
640     # check for duplicate barcode
641     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
642         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
643         if ($existing_itemnumber) {
644             if (!exists $item_ref->{'itemnumber'}                       # new item
645                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
646                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
647             }
648         }
649     }
650
651     # check for valid home branch
652     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
653         my $branch_name = GetBranchName($item_ref->{'homebranch'});
654         unless (defined $branch_name) {
655             # relies on fact that branches.branchname is a non-NULL column,
656             # so GetBranchName returns undef only if branch does not exist
657             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
658         }
659     }
660
661     # check for valid holding branch
662     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
663         my $branch_name = GetBranchName($item_ref->{'holdingbranch'});
664         unless (defined $branch_name) {
665             # relies on fact that branches.branchname is a non-NULL column,
666             # so GetBranchName returns undef only if branch does not exist
667             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
668         }
669     }
670
671     return %errors;
672
673 }
674
675 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
676
677 The following functions provide various ways of 
678 getting an item record, a set of item records, or
679 lists of authorized values for certain item fields.
680
681 Some of the functions in this group are candidates
682 for refactoring -- for example, some of the code
683 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
684 has copy-and-paste work.
685
686 =cut
687
688 =head2 GetItemStatus
689
690   $itemstatushash = GetItemStatus($fwkcode);
691
692 Returns a list of valid values for the
693 C<items.notforloan> field.
694
695 NOTE: does B<not> return an individual item's
696 status.
697
698 Can be MARC dependant.
699 fwkcode is optional.
700 But basically could be can be loan or not
701 Create a status selector with the following code
702
703 =head3 in PERL SCRIPT
704
705  my $itemstatushash = getitemstatus;
706  my @itemstatusloop;
707  foreach my $thisstatus (keys %$itemstatushash) {
708      my %row =(value => $thisstatus,
709                  statusname => $itemstatushash->{$thisstatus}->{'statusname'},
710              );
711      push @itemstatusloop, \%row;
712  }
713  $template->param(statusloop=>\@itemstatusloop);
714
715 =head3 in TEMPLATE
716
717  <select name="statusloop">
718      <option value="">Default</option>
719  <!-- TMPL_LOOP name="statusloop" -->
720      <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
721  <!-- /TMPL_LOOP -->
722  </select>
723
724 =cut
725
726 sub GetItemStatus {
727
728     # returns a reference to a hash of references to status...
729     my ($fwk) = @_;
730     my %itemstatus;
731     my $dbh = C4::Context->dbh;
732     my $sth;
733     $fwk = '' unless ($fwk);
734     my ( $tag, $subfield ) =
735       GetMarcFromKohaField( "items.notforloan", $fwk );
736     if ( $tag and $subfield ) {
737         my $sth =
738           $dbh->prepare(
739             "SELECT authorised_value
740             FROM marc_subfield_structure
741             WHERE tagfield=?
742                 AND tagsubfield=?
743                 AND frameworkcode=?
744             "
745           );
746         $sth->execute( $tag, $subfield, $fwk );
747         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
748             my $authvalsth =
749               $dbh->prepare(
750                 "SELECT authorised_value,lib
751                 FROM authorised_values 
752                 WHERE category=? 
753                 ORDER BY lib
754                 "
755               );
756             $authvalsth->execute($authorisedvaluecat);
757             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
758                 $itemstatus{$authorisedvalue} = $lib;
759             }
760             return \%itemstatus;
761             exit 1;
762         }
763         else {
764
765             #No authvalue list
766             # build default
767         }
768     }
769
770     #No authvalue list
771     #build default
772     $itemstatus{"1"} = "Not For Loan";
773     return \%itemstatus;
774 }
775
776 =head2 GetItemLocation
777
778   $itemlochash = GetItemLocation($fwk);
779
780 Returns a list of valid values for the
781 C<items.location> field.
782
783 NOTE: does B<not> return an individual item's
784 location.
785
786 where fwk stands for an optional framework code.
787 Create a location selector with the following code
788
789 =head3 in PERL SCRIPT
790
791   my $itemlochash = getitemlocation;
792   my @itemlocloop;
793   foreach my $thisloc (keys %$itemlochash) {
794       my $selected = 1 if $thisbranch eq $branch;
795       my %row =(locval => $thisloc,
796                   selected => $selected,
797                   locname => $itemlochash->{$thisloc},
798                );
799       push @itemlocloop, \%row;
800   }
801   $template->param(itemlocationloop => \@itemlocloop);
802
803 =head3 in TEMPLATE
804
805   <select name="location">
806       <option value="">Default</option>
807   <!-- TMPL_LOOP name="itemlocationloop" -->
808       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
809   <!-- /TMPL_LOOP -->
810   </select>
811
812 =cut
813
814 sub GetItemLocation {
815
816     # returns a reference to a hash of references to location...
817     my ($fwk) = @_;
818     my %itemlocation;
819     my $dbh = C4::Context->dbh;
820     my $sth;
821     $fwk = '' unless ($fwk);
822     my ( $tag, $subfield ) =
823       GetMarcFromKohaField( "items.location", $fwk );
824     if ( $tag and $subfield ) {
825         my $sth =
826           $dbh->prepare(
827             "SELECT authorised_value
828             FROM marc_subfield_structure 
829             WHERE tagfield=? 
830                 AND tagsubfield=? 
831                 AND frameworkcode=?"
832           );
833         $sth->execute( $tag, $subfield, $fwk );
834         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
835             my $authvalsth =
836               $dbh->prepare(
837                 "SELECT authorised_value,lib
838                 FROM authorised_values
839                 WHERE category=?
840                 ORDER BY lib"
841               );
842             $authvalsth->execute($authorisedvaluecat);
843             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
844                 $itemlocation{$authorisedvalue} = $lib;
845             }
846             return \%itemlocation;
847             exit 1;
848         }
849         else {
850
851             #No authvalue list
852             # build default
853         }
854     }
855
856     #No authvalue list
857     #build default
858     $itemlocation{"1"} = "Not For Loan";
859     return \%itemlocation;
860 }
861
862 =head2 GetLostItems
863
864   $items = GetLostItems( $where, $orderby );
865
866 This function gets a list of lost items.
867
868 =over 2
869
870 =item input:
871
872 C<$where> is a hashref. it containts a field of the items table as key
873 and the value to match as value. For example:
874
875 { barcode    => 'abc123',
876   homebranch => 'CPL',    }
877
878 C<$orderby> is a field of the items table by which the resultset
879 should be orderd.
880
881 =item return:
882
883 C<$items> is a reference to an array full of hashrefs with columns
884 from the "items" table as keys.
885
886 =item usage in the perl script:
887
888   my $where = { barcode => '0001548' };
889   my $items = GetLostItems( $where, "homebranch" );
890   $template->param( itemsloop => $items );
891
892 =back
893
894 =cut
895
896 sub GetLostItems {
897     # Getting input args.
898     my $where   = shift;
899     my $orderby = shift;
900     my $dbh     = C4::Context->dbh;
901
902     my $query   = "
903         SELECT *
904         FROM   items
905             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
906             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
907             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
908         WHERE
909                 authorised_values.category = 'LOST'
910                 AND itemlost IS NOT NULL
911                 AND itemlost <> 0
912     ";
913     my @query_parameters;
914     foreach my $key (keys %$where) {
915         $query .= " AND $key LIKE ?";
916         push @query_parameters, "%$where->{$key}%";
917     }
918     my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
919     
920     if ( defined $orderby && grep($orderby, @ordervalues)) {
921         $query .= ' ORDER BY '.$orderby;
922     }
923
924     my $sth = $dbh->prepare($query);
925     $sth->execute( @query_parameters );
926     my $items = [];
927     while ( my $row = $sth->fetchrow_hashref ){
928         push @$items, $row;
929     }
930     return $items;
931 }
932
933 =head2 GetItemsForInventory
934
935   $itemlist = GetItemsForInventory($minlocation, $maxlocation, 
936                  $location, $itemtype $datelastseen, $branch, 
937                  $offset, $size, $statushash);
938
939 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
940
941 The sub returns a reference to a list of hashes, each containing
942 itemnumber, author, title, barcode, item callnumber, and date last
943 seen. It is ordered by callnumber then title.
944
945 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
946 the datelastseen can be used to specify that you want to see items not seen since a past date only.
947 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
948 $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.
949
950 =cut
951
952 sub GetItemsForInventory {
953     my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branchcode, $branch, $offset, $size, $statushash ) = @_;
954     my $dbh = C4::Context->dbh;
955     my ( @bind_params, @where_strings );
956
957     my $query = <<'END_SQL';
958 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
959 FROM items
960   LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
961   LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
962 END_SQL
963     if ($statushash){
964         for my $authvfield (keys %$statushash){
965             if ( scalar @{$statushash->{$authvfield}} > 0 ){
966                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
967                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
968             }
969         }
970     }
971
972     if ($minlocation) {
973         push @where_strings, 'itemcallnumber >= ?';
974         push @bind_params, $minlocation;
975     }
976
977     if ($maxlocation) {
978         push @where_strings, 'itemcallnumber <= ?';
979         push @bind_params, $maxlocation;
980     }
981
982     if ($datelastseen) {
983         $datelastseen = format_date_in_iso($datelastseen);  
984         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
985         push @bind_params, $datelastseen;
986     }
987
988     if ( $location ) {
989         push @where_strings, 'items.location = ?';
990         push @bind_params, $location;
991     }
992
993     if ( $branchcode ) {
994         if($branch eq "homebranch"){
995         push @where_strings, 'items.homebranch = ?';
996         }else{
997             push @where_strings, 'items.holdingbranch = ?';
998         }
999         push @bind_params, $branchcode;
1000     }
1001     
1002     if ( $itemtype ) {
1003         push @where_strings, 'biblioitems.itemtype = ?';
1004         push @bind_params, $itemtype;
1005     }
1006
1007     if ( $ignoreissued) {
1008         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1009         push @where_strings, 'issues.date_due IS NULL';
1010     }
1011
1012     if ( @where_strings ) {
1013         $query .= 'WHERE ';
1014         $query .= join ' AND ', @where_strings;
1015     }
1016     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1017     my $sth = $dbh->prepare($query);
1018     $sth->execute( @bind_params );
1019
1020     my @results;
1021     $size--;
1022     while ( my $row = $sth->fetchrow_hashref ) {
1023         $offset-- if ($offset);
1024         $row->{datelastseen}=format_date($row->{datelastseen});
1025         if ( ( !$offset ) && $size ) {
1026             push @results, $row;
1027             $size--;
1028         }
1029     }
1030     return \@results;
1031 }
1032
1033 =head2 GetItemsCount
1034
1035   $count = &GetItemsCount( $biblionumber);
1036
1037 This function return count of item with $biblionumber
1038
1039 =cut
1040
1041 sub GetItemsCount {
1042     my ( $biblionumber ) = @_;
1043     my $dbh = C4::Context->dbh;
1044     my $query = "SELECT count(*)
1045           FROM  items 
1046           WHERE biblionumber=?";
1047     my $sth = $dbh->prepare($query);
1048     $sth->execute($biblionumber);
1049     my $count = $sth->fetchrow;  
1050     return ($count);
1051 }
1052
1053 =head2 GetItemInfosOf
1054
1055   GetItemInfosOf(@itemnumbers);
1056
1057 =cut
1058
1059 sub GetItemInfosOf {
1060     my @itemnumbers = @_;
1061
1062     my $query = '
1063         SELECT *
1064         FROM items
1065         WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
1066     ';
1067     return get_infos_of( $query, 'itemnumber' );
1068 }
1069
1070 =head2 GetItemsByBiblioitemnumber
1071
1072   GetItemsByBiblioitemnumber($biblioitemnumber);
1073
1074 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1075 Called by C<C4::XISBN>
1076
1077 =cut
1078
1079 sub GetItemsByBiblioitemnumber {
1080     my ( $bibitem ) = @_;
1081     my $dbh = C4::Context->dbh;
1082     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1083     # Get all items attached to a biblioitem
1084     my $i = 0;
1085     my @results; 
1086     $sth->execute($bibitem) || die $sth->errstr;
1087     while ( my $data = $sth->fetchrow_hashref ) {  
1088         # Foreach item, get circulation information
1089         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1090                                    WHERE itemnumber = ?
1091                                    AND issues.borrowernumber = borrowers.borrowernumber"
1092         );
1093         $sth2->execute( $data->{'itemnumber'} );
1094         if ( my $data2 = $sth2->fetchrow_hashref ) {
1095             # if item is out, set the due date and who it is out too
1096             $data->{'date_due'}   = $data2->{'date_due'};
1097             $data->{'cardnumber'} = $data2->{'cardnumber'};
1098             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1099         }
1100         else {
1101             # set date_due to blank, so in the template we check itemlost, and wthdrawn 
1102             $data->{'date_due'} = '';                                                                                                         
1103         }    # else         
1104         # Find the last 3 people who borrowed this item.                  
1105         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1106                       AND old_issues.borrowernumber = borrowers.borrowernumber
1107                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1108         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1109         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1110         my $i2 = 0;
1111         while ( my $data2 = $sth2->fetchrow_hashref ) {
1112             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1113             $data->{"card$i2"}      = $data2->{'cardnumber'};
1114             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1115             $i2++;
1116         }
1117         push(@results,$data);
1118     } 
1119     return (\@results); 
1120 }
1121
1122 =head2 GetItemsInfo
1123
1124   @results = GetItemsInfo($biblionumber, $type);
1125
1126 Returns information about books with the given biblionumber.
1127
1128 C<$type> may be either C<intra> or anything else. If it is not set to
1129 C<intra>, then the search will exclude lost, very overdue, and
1130 withdrawn items.
1131
1132 C<GetItemsInfo> returns a list of references-to-hash. Each element
1133 contains a number of keys. Most of them are table items from the
1134 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1135 Koha database. Other keys include:
1136
1137 =over 2
1138
1139 =item C<$data-E<gt>{branchname}>
1140
1141 The name (not the code) of the branch to which the book belongs.
1142
1143 =item C<$data-E<gt>{datelastseen}>
1144
1145 This is simply C<items.datelastseen>, except that while the date is
1146 stored in YYYY-MM-DD format in the database, here it is converted to
1147 DD/MM/YYYY format. A NULL date is returned as C<//>.
1148
1149 =item C<$data-E<gt>{datedue}>
1150
1151 =item C<$data-E<gt>{class}>
1152
1153 This is the concatenation of C<biblioitems.classification>, the book's
1154 Dewey code, and C<biblioitems.subclass>.
1155
1156 =item C<$data-E<gt>{ocount}>
1157
1158 I think this is the number of copies of the book available.
1159
1160 =item C<$data-E<gt>{order}>
1161
1162 If this is set, it is set to C<One Order>.
1163
1164 =back
1165
1166 =cut
1167
1168 sub GetItemsInfo {
1169     my ( $biblionumber, $type ) = @_;
1170     my $dbh   = C4::Context->dbh;
1171     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1172     my $query = "
1173     SELECT items.*,
1174            biblio.*,
1175            biblioitems.volume,
1176            biblioitems.number,
1177            biblioitems.itemtype,
1178            biblioitems.isbn,
1179            biblioitems.issn,
1180            biblioitems.publicationyear,
1181            biblioitems.publishercode,
1182            biblioitems.volumedate,
1183            biblioitems.volumedesc,
1184            biblioitems.lccn,
1185            biblioitems.url,
1186            items.notforloan as itemnotforloan,
1187            itemtypes.description,
1188            itemtypes.notforloan as notforloan_per_itemtype,
1189            branchurl
1190      FROM items
1191      LEFT JOIN branches ON items.homebranch = branches.branchcode
1192      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1193      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1194      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1195      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1196     $query .= " WHERE items.biblionumber = ? ORDER BY branches.branchname,items.dateaccessioned desc" ;
1197     my $sth = $dbh->prepare($query);
1198     $sth->execute($biblionumber);
1199     my $i = 0;
1200     my @results;
1201     my $serial;
1202
1203     my $isth    = $dbh->prepare(
1204         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
1205         FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
1206         WHERE  itemnumber = ?"
1207        );
1208         my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? "); 
1209         while ( my $data = $sth->fetchrow_hashref ) {
1210         my $datedue = '';
1211         my $count_reserves;
1212         $isth->execute( $data->{'itemnumber'} );
1213         if ( my $idata = $isth->fetchrow_hashref ) {
1214             $data->{borrowernumber} = $idata->{borrowernumber};
1215             $data->{cardnumber}     = $idata->{cardnumber};
1216             $data->{surname}     = $idata->{surname};
1217             $data->{firstname}     = $idata->{firstname};
1218             $datedue                = $idata->{'date_due'};
1219         if (C4::Context->preference("IndependantBranches")){
1220         my $userenv = C4::Context->userenv;
1221         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) { 
1222             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1223         }
1224         }
1225         }
1226                 if ( $data->{'serial'}) {       
1227                         $ssth->execute($data->{'itemnumber'}) ;
1228                         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1229                         $serial = 1;
1230         }
1231                 if ( $datedue eq '' ) {
1232             my ( $restype, $reserves ) =
1233               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
1234 # Previous conditional check with if ($restype) is not needed because a true
1235 # result for one item will result in subsequent items defaulting to this true
1236 # value.
1237             $count_reserves = $restype;
1238         }
1239         #get branch information.....
1240         my $bsth = $dbh->prepare(
1241             "SELECT * FROM branches WHERE branchcode = ?
1242         "
1243         );
1244         $bsth->execute( $data->{'holdingbranch'} );
1245         if ( my $bdata = $bsth->fetchrow_hashref ) {
1246             $data->{'branchname'} = $bdata->{'branchname'};
1247         }
1248         $data->{'datedue'}        = $datedue;
1249         $data->{'count_reserves'} = $count_reserves;
1250
1251         # get notforloan complete status if applicable
1252         my $sthnflstatus = $dbh->prepare(
1253             'SELECT authorised_value
1254             FROM   marc_subfield_structure
1255             WHERE  kohafield="items.notforloan"
1256         '
1257         );
1258
1259         $sthnflstatus->execute;
1260         my ($authorised_valuecode) = $sthnflstatus->fetchrow;
1261         if ($authorised_valuecode) {
1262             $sthnflstatus = $dbh->prepare(
1263                 "SELECT lib FROM authorised_values
1264                  WHERE  category=?
1265                  AND authorised_value=?"
1266             );
1267             $sthnflstatus->execute( $authorised_valuecode,
1268                 $data->{itemnotforloan} );
1269             my ($lib) = $sthnflstatus->fetchrow;
1270             $data->{notforloanvalue} = $lib;
1271         }
1272
1273         # get restricted status and description if applicable
1274         my $restrictedstatus = $dbh->prepare(
1275             'SELECT authorised_value
1276             FROM   marc_subfield_structure
1277             WHERE  kohafield="items.restricted"
1278         '
1279         );
1280
1281         $restrictedstatus->execute;
1282         ($authorised_valuecode) = $restrictedstatus->fetchrow;
1283         if ($authorised_valuecode) {
1284             $restrictedstatus = $dbh->prepare(
1285                 "SELECT lib,lib_opac FROM authorised_values
1286                  WHERE  category=?
1287                  AND authorised_value=?"
1288             );
1289             $restrictedstatus->execute( $authorised_valuecode,
1290                 $data->{restricted} );
1291
1292             if ( my $rstdata = $restrictedstatus->fetchrow_hashref ) {
1293                 $data->{restricted} = $rstdata->{'lib'};
1294                 $data->{restrictedopac} = $rstdata->{'lib_opac'};
1295             }
1296         }
1297
1298         # my stack procedures
1299         my $stackstatus = $dbh->prepare(
1300             'SELECT authorised_value
1301              FROM   marc_subfield_structure
1302              WHERE  kohafield="items.stack"
1303         '
1304         );
1305         $stackstatus->execute;
1306
1307         ($authorised_valuecode) = $stackstatus->fetchrow;
1308         if ($authorised_valuecode) {
1309             $stackstatus = $dbh->prepare(
1310                 "SELECT lib
1311                  FROM   authorised_values
1312                  WHERE  category=?
1313                  AND    authorised_value=?
1314             "
1315             );
1316             $stackstatus->execute( $authorised_valuecode, $data->{stack} );
1317             my ($lib) = $stackstatus->fetchrow;
1318             $data->{stack} = $lib;
1319         }
1320         # Find the last 3 people who borrowed this item.
1321         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1322                                     WHERE itemnumber = ?
1323                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1324                                     ORDER BY returndate DESC
1325                                     LIMIT 3");
1326         $sth2->execute($data->{'itemnumber'});
1327         my $ii = 0;
1328         while (my $data2 = $sth2->fetchrow_hashref()) {
1329             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1330             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1331             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1332             $ii++;
1333         }
1334
1335         $results[$i] = $data;
1336         $i++;
1337     }
1338         if($serial) {
1339                 return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
1340         } else {
1341         return (@results);
1342         }
1343 }
1344
1345 =head2 GetItemsLocationInfo
1346
1347   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1348
1349 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1350
1351 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1352
1353 =over 2
1354
1355 =item C<$data-E<gt>{homebranch}>
1356
1357 Branch Name of the item's homebranch
1358
1359 =item C<$data-E<gt>{holdingbranch}>
1360
1361 Branch Name of the item's holdingbranch
1362
1363 =item C<$data-E<gt>{location}>
1364
1365 Item's shelving location code
1366
1367 =item C<$data-E<gt>{location_intranet}>
1368
1369 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1370
1371 =item C<$data-E<gt>{location_opac}>
1372
1373 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1374 description is set.
1375
1376 =item C<$data-E<gt>{itemcallnumber}>
1377
1378 Item's itemcallnumber
1379
1380 =item C<$data-E<gt>{cn_sort}>
1381
1382 Item's call number normalized for sorting
1383
1384 =back
1385   
1386 =cut
1387
1388 sub GetItemsLocationInfo {
1389         my $biblionumber = shift;
1390         my @results;
1391
1392         my $dbh = C4::Context->dbh;
1393         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1394                             location, itemcallnumber, cn_sort
1395                      FROM items, branches as a, branches as b
1396                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1397                      AND biblionumber = ?
1398                      ORDER BY cn_sort ASC";
1399         my $sth = $dbh->prepare($query);
1400         $sth->execute($biblionumber);
1401
1402         while ( my $data = $sth->fetchrow_hashref ) {
1403              $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
1404              $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
1405              push @results, $data;
1406         }
1407         return @results;
1408 }
1409
1410
1411 =head2 GetLastAcquisitions
1412
1413   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1414                                     'itemtypes' => ('BK','BD')}, 10);
1415
1416 =cut
1417
1418 sub  GetLastAcquisitions {
1419         my ($data,$max) = @_;
1420
1421         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1422         
1423         my $number_of_branches = @{$data->{branches}};
1424         my $number_of_itemtypes   = @{$data->{itemtypes}};
1425         
1426         
1427         my @where = ('WHERE 1 '); 
1428         $number_of_branches and push @where
1429            , 'AND holdingbranch IN (' 
1430            , join(',', ('?') x $number_of_branches )
1431            , ')'
1432          ;
1433         
1434         $number_of_itemtypes and push @where
1435            , "AND $itemtype IN (" 
1436            , join(',', ('?') x $number_of_itemtypes )
1437            , ')'
1438          ;
1439
1440         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1441                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1442                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1443                                     @where
1444                                     GROUP BY biblio.biblionumber 
1445                                     ORDER BY dateaccessioned DESC LIMIT $max";
1446
1447         my $dbh = C4::Context->dbh;
1448         my $sth = $dbh->prepare($query);
1449     
1450     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1451         
1452         my @results;
1453         while( my $row = $sth->fetchrow_hashref){
1454                 push @results, {date => $row->{dateaccessioned} 
1455                                                 , biblionumber => $row->{biblionumber}
1456                                                 , title => $row->{title}};
1457         }
1458         
1459         return @results;
1460 }
1461
1462 =head2 get_itemnumbers_of
1463
1464   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1465
1466 Given a list of biblionumbers, return the list of corresponding itemnumbers
1467 for each biblionumber.
1468
1469 Return a reference on a hash where keys are biblionumbers and values are
1470 references on array of itemnumbers.
1471
1472 =cut
1473
1474 sub get_itemnumbers_of {
1475     my @biblionumbers = @_;
1476
1477     my $dbh = C4::Context->dbh;
1478
1479     my $query = '
1480         SELECT itemnumber,
1481             biblionumber
1482         FROM items
1483         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1484     ';
1485     my $sth = $dbh->prepare($query);
1486     $sth->execute(@biblionumbers);
1487
1488     my %itemnumbers_of;
1489
1490     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1491         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1492     }
1493
1494     return \%itemnumbers_of;
1495 }
1496
1497 =head2 GetItemnumberFromBarcode
1498
1499   $result = GetItemnumberFromBarcode($barcode);
1500
1501 =cut
1502
1503 sub GetItemnumberFromBarcode {
1504     my ($barcode) = @_;
1505     my $dbh = C4::Context->dbh;
1506
1507     my $rq =
1508       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1509     $rq->execute($barcode);
1510     my ($result) = $rq->fetchrow;
1511     return ($result);
1512 }
1513
1514 =head2 GetBarcodeFromItemnumber
1515
1516   $result = GetBarcodeFromItemnumber($itemnumber);
1517
1518 =cut
1519
1520 sub GetBarcodeFromItemnumber {
1521     my ($itemnumber) = @_;
1522     my $dbh = C4::Context->dbh;
1523
1524     my $rq =
1525       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1526     $rq->execute($itemnumber);
1527     my ($result) = $rq->fetchrow;
1528     return ($result);
1529 }
1530
1531 =head2 GetHiddenItemnumbers
1532
1533 =over 4
1534
1535 $result = GetHiddenItemnumbers(@items);
1536
1537 =back
1538
1539 =cut
1540
1541 sub GetHiddenItemnumbers {
1542     my (@items) = @_;
1543     my @resultitems;
1544
1545     my $yaml = C4::Context->preference('OpacHiddenItems');
1546     my $hidingrules;
1547     eval {
1548         $hidingrules = YAML::Load($yaml);
1549     };
1550     if ($@) {
1551         warn "Unable to parse OpacHiddenItems syspref : $@";
1552         return ();
1553     } else {
1554     my $dbh = C4::Context->dbh;
1555
1556         # For each item
1557         foreach my $item (@items) {
1558
1559             # We check each rule
1560             foreach my $field (keys %$hidingrules) {
1561                 my $query = "SELECT $field from items where itemnumber = ?";
1562                 my $sth = $dbh->prepare($query);        
1563                 $sth->execute($item->{'itemnumber'});
1564                 my ($result) = $sth->fetchrow;
1565
1566                 # If the results matches the values in the yaml file
1567                 if (any { $result eq $_ } @{$hidingrules->{$field}}) {
1568
1569                     # We add the itemnumber to the list
1570                     push @resultitems, $item->{'itemnumber'};       
1571
1572                     # If at least one rule matched for an item, no need to test the others
1573                     last;
1574                 }
1575             }
1576         }
1577         return @resultitems;
1578     }
1579
1580  }
1581
1582 =head3 get_item_authorised_values
1583
1584 find the types and values for all authorised values assigned to this item.
1585
1586 parameters: itemnumber
1587
1588 returns: a hashref malling the authorised value to the value set for this itemnumber
1589
1590     $authorised_values = {
1591              'CCODE'      => undef,
1592              'DAMAGED'    => '0',
1593              'LOC'        => '3',
1594              'LOST'       => '0'
1595              'NOT_LOAN'   => '0',
1596              'RESTRICTED' => undef,
1597              'STACK'      => undef,
1598              'WITHDRAWN'  => '0',
1599              'branches'   => 'CPL',
1600              'cn_source'  => undef,
1601              'itemtypes'  => 'SER',
1602            };
1603
1604 Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
1605
1606 =cut
1607
1608 sub get_item_authorised_values {
1609     my $itemnumber = shift;
1610
1611     # assume that these entries in the authorised_value table are item level.
1612     my $query = q(SELECT distinct authorised_value, kohafield
1613                     FROM marc_subfield_structure
1614                     WHERE kohafield like 'item%'
1615                       AND authorised_value != '' );
1616
1617     my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
1618     my $iteminfo = GetItem( $itemnumber );
1619     # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
1620     my $return;
1621     foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
1622         my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
1623         $field =~ s/^items\.//;
1624         if ( exists $iteminfo->{ $field } ) {
1625             $return->{ $this_authorised_value } = $iteminfo->{ $field };
1626         }
1627     }
1628     # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
1629     return $return;
1630 }
1631
1632 =head3 get_authorised_value_images
1633
1634 find a list of icons that are appropriate for display based on the
1635 authorised values for a biblio.
1636
1637 parameters: listref of authorised values, such as comes from
1638 get_item_authorised_values or
1639 from C4::Biblio::get_biblio_authorised_values
1640
1641 returns: listref of hashrefs for each image. Each hashref looks like this:
1642
1643       { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
1644         label    => '',
1645         category => '',
1646         value    => '', }
1647
1648 Notes: Currently, I put on the full path to the images on the staff
1649 side. This should either be configurable or not done at all. Since I
1650 have to deal with 'intranet' or 'opac' in
1651 get_biblio_authorised_values, perhaps I should be passing it in.
1652
1653 =cut
1654
1655 sub get_authorised_value_images {
1656     my $authorised_values = shift;
1657
1658     my @imagelist;
1659
1660     my $authorised_value_list = GetAuthorisedValues();
1661     # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
1662     foreach my $this_authorised_value ( @$authorised_value_list ) {
1663         if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
1664              && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
1665             # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
1666             if ( defined $this_authorised_value->{'imageurl'} ) {
1667                 push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
1668                                    label    => $this_authorised_value->{'lib'},
1669                                    category => $this_authorised_value->{'category'},
1670                                    value    => $this_authorised_value->{'authorised_value'}, };
1671             }
1672         }
1673     }
1674
1675     # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
1676     return \@imagelist;
1677
1678 }
1679
1680 =head1 LIMITED USE FUNCTIONS
1681
1682 The following functions, while part of the public API,
1683 are not exported.  This is generally because they are
1684 meant to be used by only one script for a specific
1685 purpose, and should not be used in any other context
1686 without careful thought.
1687
1688 =cut
1689
1690 =head2 GetMarcItem
1691
1692   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1693
1694 Returns MARC::Record of the item passed in parameter.
1695 This function is meant for use only in C<cataloguing/additem.pl>,
1696 where it is needed to support that script's MARC-like
1697 editor.
1698
1699 =cut
1700
1701 sub GetMarcItem {
1702     my ( $biblionumber, $itemnumber ) = @_;
1703
1704     # GetMarcItem has been revised so that it does the following:
1705     #  1. Gets the item information from the items table.
1706     #  2. Converts it to a MARC field for storage in the bib record.
1707     #
1708     # The previous behavior was:
1709     #  1. Get the bib record.
1710     #  2. Return the MARC tag corresponding to the item record.
1711     #
1712     # The difference is that one treats the items row as authoritative,
1713     # while the other treats the MARC representation as authoritative
1714     # under certain circumstances.
1715
1716     my $itemrecord = GetItem($itemnumber);
1717
1718     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1719     # Also, don't emit a subfield if the underlying field is blank.
1720
1721     
1722     return Item2Marc($itemrecord,$biblionumber);
1723
1724 }
1725 sub Item2Marc {
1726         my ($itemrecord,$biblionumber)=@_;
1727     my $mungeditem = { 
1728         map {  
1729             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1730         } keys %{ $itemrecord } 
1731     };
1732     my $itemmarc = TransformKohaToMarc($mungeditem);
1733     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1734
1735     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1736     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1737                 foreach my $field ($itemmarc->field($itemtag)){
1738             $field->add_subfields(@$unlinked_item_subfields);
1739         }
1740     }
1741         return $itemmarc;
1742 }
1743
1744 =head1 PRIVATE FUNCTIONS AND VARIABLES
1745
1746 The following functions are not meant to be called
1747 directly, but are documented in order to explain
1748 the inner workings of C<C4::Items>.
1749
1750 =cut
1751
1752 =head2 %derived_columns
1753
1754 This hash keeps track of item columns that
1755 are strictly derived from other columns in
1756 the item record and are not meant to be set
1757 independently.
1758
1759 Each key in the hash should be the name of a
1760 column (as named by TransformMarcToKoha).  Each
1761 value should be hashref whose keys are the
1762 columns on which the derived column depends.  The
1763 hashref should also contain a 'BUILDER' key
1764 that is a reference to a sub that calculates
1765 the derived value.
1766
1767 =cut
1768
1769 my %derived_columns = (
1770     'items.cn_sort' => {
1771         'itemcallnumber' => 1,
1772         'items.cn_source' => 1,
1773         'BUILDER' => \&_calc_items_cn_sort,
1774     }
1775 );
1776
1777 =head2 _set_derived_columns_for_add 
1778
1779   _set_derived_column_for_add($item);
1780
1781 Given an item hash representing a new item to be added,
1782 calculate any derived columns.  Currently the only
1783 such column is C<items.cn_sort>.
1784
1785 =cut
1786
1787 sub _set_derived_columns_for_add {
1788     my $item = shift;
1789
1790     foreach my $column (keys %derived_columns) {
1791         my $builder = $derived_columns{$column}->{'BUILDER'};
1792         my $source_values = {};
1793         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1794             next if $source_column eq 'BUILDER';
1795             $source_values->{$source_column} = $item->{$source_column};
1796         }
1797         $builder->($item, $source_values);
1798     }
1799 }
1800
1801 =head2 _set_derived_columns_for_mod 
1802
1803   _set_derived_column_for_mod($item);
1804
1805 Given an item hash representing a new item to be modified.
1806 calculate any derived columns.  Currently the only
1807 such column is C<items.cn_sort>.
1808
1809 This routine differs from C<_set_derived_columns_for_add>
1810 in that it needs to handle partial item records.  In other
1811 words, the caller of C<ModItem> may have supplied only one
1812 or two columns to be changed, so this function needs to
1813 determine whether any of the columns to be changed affect
1814 any of the derived columns.  Also, if a derived column
1815 depends on more than one column, but the caller is not
1816 changing all of then, this routine retrieves the unchanged
1817 values from the database in order to ensure a correct
1818 calculation.
1819
1820 =cut
1821
1822 sub _set_derived_columns_for_mod {
1823     my $item = shift;
1824
1825     foreach my $column (keys %derived_columns) {
1826         my $builder = $derived_columns{$column}->{'BUILDER'};
1827         my $source_values = {};
1828         my %missing_sources = ();
1829         my $must_recalc = 0;
1830         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1831             next if $source_column eq 'BUILDER';
1832             if (exists $item->{$source_column}) {
1833                 $must_recalc = 1;
1834                 $source_values->{$source_column} = $item->{$source_column};
1835             } else {
1836                 $missing_sources{$source_column} = 1;
1837             }
1838         }
1839         if ($must_recalc) {
1840             foreach my $source_column (keys %missing_sources) {
1841                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1842             }
1843             $builder->($item, $source_values);
1844         }
1845     }
1846 }
1847
1848 =head2 _do_column_fixes_for_mod
1849
1850   _do_column_fixes_for_mod($item);
1851
1852 Given an item hashref containing one or more
1853 columns to modify, fix up certain values.
1854 Specifically, set to 0 any passed value
1855 of C<notforloan>, C<damaged>, C<itemlost>, or
1856 C<wthdrawn> that is either undefined or
1857 contains the empty string.
1858
1859 =cut
1860
1861 sub _do_column_fixes_for_mod {
1862     my $item = shift;
1863
1864     if (exists $item->{'notforloan'} and
1865         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1866         $item->{'notforloan'} = 0;
1867     }
1868     if (exists $item->{'damaged'} and
1869         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1870         $item->{'damaged'} = 0;
1871     }
1872     if (exists $item->{'itemlost'} and
1873         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1874         $item->{'itemlost'} = 0;
1875     }
1876     if (exists $item->{'wthdrawn'} and
1877         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1878         $item->{'wthdrawn'} = 0;
1879     }
1880     if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
1881         $item->{'permanent_location'} = $item->{'location'};
1882     }
1883     if (exists $item->{'timestamp'}) {
1884         delete $item->{'timestamp'};
1885     }
1886 }
1887
1888 =head2 _get_single_item_column
1889
1890   _get_single_item_column($column, $itemnumber);
1891
1892 Retrieves the value of a single column from an C<items>
1893 row specified by C<$itemnumber>.
1894
1895 =cut
1896
1897 sub _get_single_item_column {
1898     my $column = shift;
1899     my $itemnumber = shift;
1900     
1901     my $dbh = C4::Context->dbh;
1902     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1903     $sth->execute($itemnumber);
1904     my ($value) = $sth->fetchrow();
1905     return $value; 
1906 }
1907
1908 =head2 _calc_items_cn_sort
1909
1910   _calc_items_cn_sort($item, $source_values);
1911
1912 Helper routine to calculate C<items.cn_sort>.
1913
1914 =cut
1915
1916 sub _calc_items_cn_sort {
1917     my $item = shift;
1918     my $source_values = shift;
1919
1920     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1921 }
1922
1923 =head2 _set_defaults_for_add 
1924
1925   _set_defaults_for_add($item_hash);
1926
1927 Given an item hash representing an item to be added, set
1928 correct default values for columns whose default value
1929 is not handled by the DBMS.  This includes the following
1930 columns:
1931
1932 =over 2
1933
1934 =item * 
1935
1936 C<items.dateaccessioned>
1937
1938 =item *
1939
1940 C<items.notforloan>
1941
1942 =item *
1943
1944 C<items.damaged>
1945
1946 =item *
1947
1948 C<items.itemlost>
1949
1950 =item *
1951
1952 C<items.wthdrawn>
1953
1954 =back
1955
1956 =cut
1957
1958 sub _set_defaults_for_add {
1959     my $item = shift;
1960     $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
1961     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
1962 }
1963
1964 =head2 _koha_new_item
1965
1966   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1967
1968 Perform the actual insert into the C<items> table.
1969
1970 =cut
1971
1972 sub _koha_new_item {
1973     my ( $item, $barcode ) = @_;
1974     my $dbh=C4::Context->dbh;  
1975     my $error;
1976     my $query =
1977            "INSERT INTO items SET
1978             biblionumber        = ?,
1979             biblioitemnumber    = ?,
1980             barcode             = ?,
1981             dateaccessioned     = ?,
1982             booksellerid        = ?,
1983             homebranch          = ?,
1984             price               = ?,
1985             replacementprice    = ?,
1986             replacementpricedate = NOW(),
1987             datelastborrowed    = ?,
1988             datelastseen        = NOW(),
1989             stack               = ?,
1990             notforloan          = ?,
1991             damaged             = ?,
1992             itemlost            = ?,
1993             wthdrawn            = ?,
1994             itemcallnumber      = ?,
1995             restricted          = ?,
1996             itemnotes           = ?,
1997             holdingbranch       = ?,
1998             paidfor             = ?,
1999             location            = ?,
2000             onloan              = ?,
2001             issues              = ?,
2002             renewals            = ?,
2003             reserves            = ?,
2004             cn_source           = ?,
2005             cn_sort             = ?,
2006             ccode               = ?,
2007             itype               = ?,
2008             materials           = ?,
2009             uri = ?,
2010             enumchron           = ?,
2011             more_subfields_xml  = ?,
2012             copynumber          = ?,
2013             stocknumber         = ?
2014           ";
2015     my $sth = $dbh->prepare($query);
2016    $sth->execute(
2017             $item->{'biblionumber'},
2018             $item->{'biblioitemnumber'},
2019             $barcode,
2020             $item->{'dateaccessioned'},
2021             $item->{'booksellerid'},
2022             $item->{'homebranch'},
2023             $item->{'price'},
2024             $item->{'replacementprice'},
2025             $item->{datelastborrowed},
2026             $item->{stack},
2027             $item->{'notforloan'},
2028             $item->{'damaged'},
2029             $item->{'itemlost'},
2030             $item->{'wthdrawn'},
2031             $item->{'itemcallnumber'},
2032             $item->{'restricted'},
2033             $item->{'itemnotes'},
2034             $item->{'holdingbranch'},
2035             $item->{'paidfor'},
2036             $item->{'location'},
2037             $item->{'onloan'},
2038             $item->{'issues'},
2039             $item->{'renewals'},
2040             $item->{'reserves'},
2041             $item->{'items.cn_source'},
2042             $item->{'items.cn_sort'},
2043             $item->{'ccode'},
2044             $item->{'itype'},
2045             $item->{'materials'},
2046             $item->{'uri'},
2047             $item->{'enumchron'},
2048             $item->{'more_subfields_xml'},
2049             $item->{'copynumber'},
2050             $item->{'stocknumber'},
2051     );
2052     my $itemnumber = $dbh->{'mysql_insertid'};
2053     if ( defined $sth->errstr ) {
2054         $error.="ERROR in _koha_new_item $query".$sth->errstr;
2055     }
2056     return ( $itemnumber, $error );
2057 }
2058
2059 =head2 MoveItemFromBiblio
2060
2061   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2062
2063 Moves an item from a biblio to another
2064
2065 Returns undef if the move failed or the biblionumber of the destination record otherwise
2066
2067 =cut
2068
2069 sub MoveItemFromBiblio {
2070     my ($itemnumber, $frombiblio, $tobiblio) = @_;
2071     my $dbh = C4::Context->dbh;
2072     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
2073     $sth->execute( $tobiblio );
2074     my ( $tobiblioitem ) = $sth->fetchrow();
2075     $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
2076     my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
2077     if ($return == 1) {
2078         ModZebra( $tobiblio, "specialUpdate", "biblioserver", undef, undef );
2079         ModZebra( $frombiblio, "specialUpdate", "biblioserver", undef, undef );
2080             # Checking if the item we want to move is in an order 
2081         my $order = GetOrderFromItemnumber($itemnumber);
2082             if ($order) {
2083                     # Replacing the biblionumber within the order if necessary
2084                     $order->{'biblionumber'} = $tobiblio;
2085                 ModOrder($order);
2086             }
2087         return $tobiblio;
2088         }
2089     return;
2090 }
2091
2092 =head2 DelItemCheck
2093
2094    DelItemCheck($dbh, $biblionumber, $itemnumber);
2095
2096 Exported function (core API) for deleting an item record in Koha if there no current issue.
2097
2098 =cut
2099
2100 sub DelItemCheck {
2101     my ( $dbh, $biblionumber, $itemnumber ) = @_;
2102     my $error;
2103
2104     # check that there is no issue on this item before deletion.
2105     my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
2106     $sth->execute($itemnumber);
2107
2108     my $item = GetItem($itemnumber);
2109     my $onloan = $sth->fetchrow;
2110     if ($onloan) {
2111         $error = "book_on_loan";
2112     }
2113     elsif (C4::Context->preference("IndependantBranches") and (C4::Context->userenv->{branch} ne $item->{C4::Context->preference("HomeOrHoldingBranch")||'homebranch'})){
2114         $error = "not_same_branch";
2115     } 
2116     else {
2117         if ($onloan){ 
2118             $error = "book_on_loan" 
2119         }
2120         else {
2121             # check it doesnt have a waiting reserve
2122             $sth=$dbh->prepare("SELECT * FROM reserves WHERE (found = 'W' or found = 'T') AND itemnumber = ?");
2123             $sth->execute($itemnumber);
2124             my $reserve=$sth->fetchrow;
2125             if ($reserve) {
2126                 $error = "book_reserved";
2127             } 
2128             else {
2129                 DelItem($dbh, $biblionumber, $itemnumber);
2130                 return 1;
2131             }
2132         }
2133     }
2134     return $error;
2135 }
2136
2137 =head2 _koha_modify_item
2138
2139   my ($itemnumber,$error) =_koha_modify_item( $item );
2140
2141 Perform the actual update of the C<items> row.  Note that this
2142 routine accepts a hashref specifying the columns to update.
2143
2144 =cut
2145
2146 sub _koha_modify_item {
2147     my ( $item ) = @_;
2148     my $dbh=C4::Context->dbh;  
2149     my $error;
2150
2151     my $query = "UPDATE items SET ";
2152     my @bind;
2153     for my $key ( keys %$item ) {
2154         $query.="$key=?,";
2155         push @bind, $item->{$key};
2156     }
2157     $query =~ s/,$//;
2158     $query .= " WHERE itemnumber=?";
2159     push @bind, $item->{'itemnumber'};
2160     my $sth = C4::Context->dbh->prepare($query);
2161     $sth->execute(@bind);
2162     if ( C4::Context->dbh->errstr ) {
2163         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
2164         warn $error;
2165     }
2166     return ($item->{'itemnumber'},$error);
2167 }
2168
2169 =head2 _koha_delete_item
2170
2171   _koha_delete_item( $dbh, $itemnum );
2172
2173 Internal function to delete an item record from the koha tables
2174
2175 =cut
2176
2177 sub _koha_delete_item {
2178     my ( $dbh, $itemnum ) = @_;
2179
2180     # save the deleted item to deleteditems table
2181     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2182     $sth->execute($itemnum);
2183     my $data = $sth->fetchrow_hashref();
2184     my $query = "INSERT INTO deleteditems SET ";
2185     my @bind  = ();
2186     foreach my $key ( keys %$data ) {
2187         $query .= "$key = ?,";
2188         push( @bind, $data->{$key} );
2189     }
2190     $query =~ s/\,$//;
2191     $sth = $dbh->prepare($query);
2192     $sth->execute(@bind);
2193
2194     # delete from items table
2195     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2196     $sth->execute($itemnum);
2197     return undef;
2198 }
2199
2200 =head2 _marc_from_item_hash
2201
2202   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2203
2204 Given an item hash representing a complete item record,
2205 create a C<MARC::Record> object containing an embedded
2206 tag representing that item.
2207
2208 The third, optional parameter C<$unlinked_item_subfields> is
2209 an arrayref of subfields (not mapped to C<items> fields per the
2210 framework) to be added to the MARC representation
2211 of the item.
2212
2213 =cut
2214
2215 sub _marc_from_item_hash {
2216     my $item = shift;
2217     my $frameworkcode = shift;
2218     my $unlinked_item_subfields;
2219     if (@_) {
2220         $unlinked_item_subfields = shift;
2221     }
2222    
2223     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2224     # Also, don't emit a subfield if the underlying field is blank.
2225     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2226                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2227                                 : ()  } keys %{ $item } }; 
2228
2229     my $item_marc = MARC::Record->new();
2230     foreach my $item_field ( keys %{$mungeditem} ) {
2231         my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2232         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
2233         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2234         foreach my $value (@values){
2235             if ( my $field = $item_marc->field($tag) ) {
2236                     $field->add_subfields( $subfield => $value );
2237             } else {
2238                 my $add_subfields = [];
2239                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2240                     $add_subfields = $unlinked_item_subfields;
2241             }
2242             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2243             }
2244         }
2245     }
2246
2247     return $item_marc;
2248 }
2249
2250 =head2 _add_item_field_to_biblio
2251
2252   _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
2253
2254 Adds the fields from a MARC record containing the
2255 representation of a Koha item record to the MARC
2256 biblio record.  The input C<$item_marc> record
2257 is expect to contain just one field, the embedded
2258 item information field.
2259
2260 =cut
2261
2262 sub _add_item_field_to_biblio {
2263     my ($item_marc, $biblionumber, $frameworkcode) = @_;
2264
2265     my $biblio_marc = GetMarcBiblio($biblionumber);
2266     foreach my $field ($item_marc->fields()) {
2267         $biblio_marc->append_fields($field);
2268     }
2269
2270     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
2271 }
2272
2273 =head2 _replace_item_field_in_biblio
2274
2275   &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
2276
2277 Given a MARC::Record C<$item_marc> containing one tag with the MARC 
2278 representation of the item, examine the biblio MARC
2279 for the corresponding tag for that item and 
2280 replace it with the tag from C<$item_marc>.
2281
2282 =cut
2283
2284 sub _replace_item_field_in_biblio {
2285     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
2286     my $dbh = C4::Context->dbh;
2287     
2288     # get complete MARC record & replace the item field by the new one
2289     my $completeRecord = GetMarcBiblio($biblionumber);
2290     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
2291     my $itemField = $ItemRecord->field($itemtag);
2292     my @items = $completeRecord->field($itemtag);
2293     my $found = 0;
2294     foreach (@items) {
2295         if ($_->subfield($itemsubfield) eq $itemnumber) {
2296             $_->replace_with($itemField);
2297             $found = 1;
2298         }
2299     }
2300   
2301     unless ($found) { 
2302         # If we haven't found the matching field,
2303         # just add it.  However, this means that
2304         # there is likely a bug.
2305         $completeRecord->append_fields($itemField);
2306     }
2307
2308     # save the record
2309     #ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
2310 }
2311
2312 =head2 _repack_item_errors
2313
2314 Add an error message hash generated by C<CheckItemPreSave>
2315 to a list of errors.
2316
2317 =cut
2318
2319 sub _repack_item_errors {
2320     my $item_sequence_num = shift;
2321     my $item_ref = shift;
2322     my $error_ref = shift;
2323
2324     my @repacked_errors = ();
2325
2326     foreach my $error_code (sort keys %{ $error_ref }) {
2327         my $repacked_error = {};
2328         $repacked_error->{'item_sequence'} = $item_sequence_num;
2329         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2330         $repacked_error->{'error_code'} = $error_code;
2331         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2332         push @repacked_errors, $repacked_error;
2333     } 
2334
2335     return @repacked_errors;
2336 }
2337
2338 =head2 _get_unlinked_item_subfields
2339
2340   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2341
2342 =cut
2343
2344 sub _get_unlinked_item_subfields {
2345     my $original_item_marc = shift;
2346     my $frameworkcode = shift;
2347
2348     my $marcstructure = GetMarcStructure(1, $frameworkcode);
2349
2350     # assume that this record has only one field, and that that
2351     # field contains only the item information
2352     my $subfields = [];
2353     my @fields = $original_item_marc->fields();
2354     if ($#fields > -1) {
2355         my $field = $fields[0];
2356             my $tag = $field->tag();
2357         foreach my $subfield ($field->subfields()) {
2358             if (defined $subfield->[1] and
2359                 $subfield->[1] ne '' and
2360                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2361                 push @$subfields, $subfield->[0] => $subfield->[1];
2362             }
2363         }
2364     }
2365     return $subfields;
2366 }
2367
2368 =head2 _get_unlinked_subfields_xml
2369
2370   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2371
2372 =cut
2373
2374 sub _get_unlinked_subfields_xml {
2375     my $unlinked_item_subfields = shift;
2376
2377     my $xml;
2378     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2379         my $marc = MARC::Record->new();
2380         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2381         # used in the framework
2382         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2383         $marc->encoding("UTF-8");    
2384         $xml = $marc->as_xml("USMARC");
2385     }
2386
2387     return $xml;
2388 }
2389
2390 =head2 _parse_unlinked_item_subfields_from_xml
2391
2392   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2393
2394 =cut
2395
2396 sub  _parse_unlinked_item_subfields_from_xml {
2397     my $xml = shift;
2398
2399     return unless defined $xml and $xml ne "";
2400     my $marc = MARC::Record->new_from_xml(StripNonXmlChars($xml),'UTF-8');
2401     my $unlinked_subfields = [];
2402     my @fields = $marc->fields();
2403     if ($#fields > -1) {
2404         foreach my $subfield ($fields[0]->subfields()) {
2405             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2406         }
2407     }
2408     return $unlinked_subfields;
2409 }
2410
2411 1;