bug 5579: fix moving items from bib to another
[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 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     #_add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
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
513     unless (defined $unlinked_item_subfields) {
514         $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'});
515     }
516     my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode, $unlinked_item_subfields) 
517         or die "FAILED _marc_from_item_hash($whole_item, $frameworkcode)";
518     
519     #_replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
520         ($new_item_marc       eq '0') and die "$new_item_marc is '0', not hashref";  # logaction line would crash anyway
521     logaction("CATALOGUING", "MODIFY", $itemnumber, $new_item_marc->as_formatted) if C4::Context->preference("CataloguingLog");
522 }
523
524 =head2 ModItemTransfer
525
526   ModItemTransfer($itenumber, $frombranch, $tobranch);
527
528 Marks an item as being transferred from one branch
529 to another.
530
531 =cut
532
533 sub ModItemTransfer {
534     my ( $itemnumber, $frombranch, $tobranch ) = @_;
535
536     my $dbh = C4::Context->dbh;
537
538     #new entry in branchtransfers....
539     my $sth = $dbh->prepare(
540         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
541         VALUES (?, ?, NOW(), ?)");
542     $sth->execute($itemnumber, $frombranch, $tobranch);
543
544     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
545     ModDateLastSeen($itemnumber);
546     return;
547 }
548
549 =head2 ModDateLastSeen
550
551   ModDateLastSeen($itemnum);
552
553 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
554 C<$itemnum> is the item number
555
556 =cut
557
558 sub ModDateLastSeen {
559     my ($itemnumber) = @_;
560     
561     my $today = C4::Dates->new();    
562     ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
563 }
564
565 =head2 DelItem
566
567   DelItem($dbh, $biblionumber, $itemnumber);
568
569 Exported function (core API) for deleting an item record in Koha.
570
571 =cut
572
573 sub DelItem {
574     my ( $dbh, $biblionumber, $itemnumber ) = @_;
575     
576     # FIXME check the item has no current issues
577     
578     _koha_delete_item( $dbh, $itemnumber );
579
580     # get the MARC record
581     my $record = GetMarcBiblio($biblionumber);
582     ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
583
584     # backup the record
585     my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
586     $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
587
588     #search item field code
589     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
590 }
591
592 =head2 CheckItemPreSave
593
594     my $item_ref = TransformMarcToKoha($marc, 'items');
595     # do stuff
596     my %errors = CheckItemPreSave($item_ref);
597     if (exists $errors{'duplicate_barcode'}) {
598         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
599     } elsif (exists $errors{'invalid_homebranch'}) {
600         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
601     } elsif (exists $errors{'invalid_holdingbranch'}) {
602         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
603     } else {
604         print "item is OK";
605     }
606
607 Given a hashref containing item fields, determine if it can be
608 inserted or updated in the database.  Specifically, checks for
609 database integrity issues, and returns a hash containing any
610 of the following keys, if applicable.
611
612 =over 2
613
614 =item duplicate_barcode
615
616 Barcode, if it duplicates one already found in the database.
617
618 =item invalid_homebranch
619
620 Home branch, if not defined in branches table.
621
622 =item invalid_holdingbranch
623
624 Holding branch, if not defined in branches table.
625
626 =back
627
628 This function does NOT implement any policy-related checks,
629 e.g., whether current operator is allowed to save an
630 item that has a given branch code.
631
632 =cut
633
634 sub CheckItemPreSave {
635     my $item_ref = shift;
636
637     my %errors = ();
638
639     # check for duplicate barcode
640     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
641         my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
642         if ($existing_itemnumber) {
643             if (!exists $item_ref->{'itemnumber'}                       # new item
644                 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
645                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
646             }
647         }
648     }
649
650     # check for valid home branch
651     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
652         my $branch_name = GetBranchName($item_ref->{'homebranch'});
653         unless (defined $branch_name) {
654             # relies on fact that branches.branchname is a non-NULL column,
655             # so GetBranchName returns undef only if branch does not exist
656             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
657         }
658     }
659
660     # check for valid holding branch
661     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
662         my $branch_name = GetBranchName($item_ref->{'holdingbranch'});
663         unless (defined $branch_name) {
664             # relies on fact that branches.branchname is a non-NULL column,
665             # so GetBranchName returns undef only if branch does not exist
666             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
667         }
668     }
669
670     return %errors;
671
672 }
673
674 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
675
676 The following functions provide various ways of 
677 getting an item record, a set of item records, or
678 lists of authorized values for certain item fields.
679
680 Some of the functions in this group are candidates
681 for refactoring -- for example, some of the code
682 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
683 has copy-and-paste work.
684
685 =cut
686
687 =head2 GetItemStatus
688
689   $itemstatushash = GetItemStatus($fwkcode);
690
691 Returns a list of valid values for the
692 C<items.notforloan> field.
693
694 NOTE: does B<not> return an individual item's
695 status.
696
697 Can be MARC dependant.
698 fwkcode is optional.
699 But basically could be can be loan or not
700 Create a status selector with the following code
701
702 =head3 in PERL SCRIPT
703
704  my $itemstatushash = getitemstatus;
705  my @itemstatusloop;
706  foreach my $thisstatus (keys %$itemstatushash) {
707      my %row =(value => $thisstatus,
708                  statusname => $itemstatushash->{$thisstatus}->{'statusname'},
709              );
710      push @itemstatusloop, \%row;
711  }
712  $template->param(statusloop=>\@itemstatusloop);
713
714 =head3 in TEMPLATE
715
716  <select name="statusloop">
717      <option value="">Default</option>
718  <!-- TMPL_LOOP name="statusloop" -->
719      <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
720  <!-- /TMPL_LOOP -->
721  </select>
722
723 =cut
724
725 sub GetItemStatus {
726
727     # returns a reference to a hash of references to status...
728     my ($fwk) = @_;
729     my %itemstatus;
730     my $dbh = C4::Context->dbh;
731     my $sth;
732     $fwk = '' unless ($fwk);
733     my ( $tag, $subfield ) =
734       GetMarcFromKohaField( "items.notforloan", $fwk );
735     if ( $tag and $subfield ) {
736         my $sth =
737           $dbh->prepare(
738             "SELECT authorised_value
739             FROM marc_subfield_structure
740             WHERE tagfield=?
741                 AND tagsubfield=?
742                 AND frameworkcode=?
743             "
744           );
745         $sth->execute( $tag, $subfield, $fwk );
746         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
747             my $authvalsth =
748               $dbh->prepare(
749                 "SELECT authorised_value,lib
750                 FROM authorised_values 
751                 WHERE category=? 
752                 ORDER BY lib
753                 "
754               );
755             $authvalsth->execute($authorisedvaluecat);
756             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
757                 $itemstatus{$authorisedvalue} = $lib;
758             }
759             return \%itemstatus;
760             exit 1;
761         }
762         else {
763
764             #No authvalue list
765             # build default
766         }
767     }
768
769     #No authvalue list
770     #build default
771     $itemstatus{"1"} = "Not For Loan";
772     return \%itemstatus;
773 }
774
775 =head2 GetItemLocation
776
777   $itemlochash = GetItemLocation($fwk);
778
779 Returns a list of valid values for the
780 C<items.location> field.
781
782 NOTE: does B<not> return an individual item's
783 location.
784
785 where fwk stands for an optional framework code.
786 Create a location selector with the following code
787
788 =head3 in PERL SCRIPT
789
790   my $itemlochash = getitemlocation;
791   my @itemlocloop;
792   foreach my $thisloc (keys %$itemlochash) {
793       my $selected = 1 if $thisbranch eq $branch;
794       my %row =(locval => $thisloc,
795                   selected => $selected,
796                   locname => $itemlochash->{$thisloc},
797                );
798       push @itemlocloop, \%row;
799   }
800   $template->param(itemlocationloop => \@itemlocloop);
801
802 =head3 in TEMPLATE
803
804   <select name="location">
805       <option value="">Default</option>
806   <!-- TMPL_LOOP name="itemlocationloop" -->
807       <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
808   <!-- /TMPL_LOOP -->
809   </select>
810
811 =cut
812
813 sub GetItemLocation {
814
815     # returns a reference to a hash of references to location...
816     my ($fwk) = @_;
817     my %itemlocation;
818     my $dbh = C4::Context->dbh;
819     my $sth;
820     $fwk = '' unless ($fwk);
821     my ( $tag, $subfield ) =
822       GetMarcFromKohaField( "items.location", $fwk );
823     if ( $tag and $subfield ) {
824         my $sth =
825           $dbh->prepare(
826             "SELECT authorised_value
827             FROM marc_subfield_structure 
828             WHERE tagfield=? 
829                 AND tagsubfield=? 
830                 AND frameworkcode=?"
831           );
832         $sth->execute( $tag, $subfield, $fwk );
833         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
834             my $authvalsth =
835               $dbh->prepare(
836                 "SELECT authorised_value,lib
837                 FROM authorised_values
838                 WHERE category=?
839                 ORDER BY lib"
840               );
841             $authvalsth->execute($authorisedvaluecat);
842             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
843                 $itemlocation{$authorisedvalue} = $lib;
844             }
845             return \%itemlocation;
846             exit 1;
847         }
848         else {
849
850             #No authvalue list
851             # build default
852         }
853     }
854
855     #No authvalue list
856     #build default
857     $itemlocation{"1"} = "Not For Loan";
858     return \%itemlocation;
859 }
860
861 =head2 GetLostItems
862
863   $items = GetLostItems( $where, $orderby );
864
865 This function gets a list of lost items.
866
867 =over 2
868
869 =item input:
870
871 C<$where> is a hashref. it containts a field of the items table as key
872 and the value to match as value. For example:
873
874 { barcode    => 'abc123',
875   homebranch => 'CPL',    }
876
877 C<$orderby> is a field of the items table by which the resultset
878 should be orderd.
879
880 =item return:
881
882 C<$items> is a reference to an array full of hashrefs with columns
883 from the "items" table as keys.
884
885 =item usage in the perl script:
886
887   my $where = { barcode => '0001548' };
888   my $items = GetLostItems( $where, "homebranch" );
889   $template->param( itemsloop => $items );
890
891 =back
892
893 =cut
894
895 sub GetLostItems {
896     # Getting input args.
897     my $where   = shift;
898     my $orderby = shift;
899     my $dbh     = C4::Context->dbh;
900
901     my $query   = "
902         SELECT *
903         FROM   items
904             LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
905             LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
906             LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
907         WHERE
908                 authorised_values.category = 'LOST'
909                 AND itemlost IS NOT NULL
910                 AND itemlost <> 0
911     ";
912     my @query_parameters;
913     foreach my $key (keys %$where) {
914         $query .= " AND $key LIKE ?";
915         push @query_parameters, "%$where->{$key}%";
916     }
917     my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
918     
919     if ( defined $orderby && grep($orderby, @ordervalues)) {
920         $query .= ' ORDER BY '.$orderby;
921     }
922
923     my $sth = $dbh->prepare($query);
924     $sth->execute( @query_parameters );
925     my $items = [];
926     while ( my $row = $sth->fetchrow_hashref ){
927         push @$items, $row;
928     }
929     return $items;
930 }
931
932 =head2 GetItemsForInventory
933
934   $itemlist = GetItemsForInventory($minlocation, $maxlocation, 
935                  $location, $itemtype $datelastseen, $branch, 
936                  $offset, $size, $statushash);
937
938 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
939
940 The sub returns a reference to a list of hashes, each containing
941 itemnumber, author, title, barcode, item callnumber, and date last
942 seen. It is ordered by callnumber then title.
943
944 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
945 the datelastseen can be used to specify that you want to see items not seen since a past date only.
946 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
947 $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.
948
949 =cut
950
951 sub GetItemsForInventory {
952     my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branchcode, $branch, $offset, $size, $statushash ) = @_;
953     my $dbh = C4::Context->dbh;
954     my ( @bind_params, @where_strings );
955
956     my $query = <<'END_SQL';
957 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
958 FROM items
959   LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
960   LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
961 END_SQL
962     if ($statushash){
963         for my $authvfield (keys %$statushash){
964             if ( scalar @{$statushash->{$authvfield}} > 0 ){
965                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
966                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
967             }
968         }
969     }
970
971     if ($minlocation) {
972         push @where_strings, 'itemcallnumber >= ?';
973         push @bind_params, $minlocation;
974     }
975
976     if ($maxlocation) {
977         push @where_strings, 'itemcallnumber <= ?';
978         push @bind_params, $maxlocation;
979     }
980
981     if ($datelastseen) {
982         $datelastseen = format_date_in_iso($datelastseen);  
983         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
984         push @bind_params, $datelastseen;
985     }
986
987     if ( $location ) {
988         push @where_strings, 'items.location = ?';
989         push @bind_params, $location;
990     }
991
992     if ( $branchcode ) {
993         if($branch eq "homebranch"){
994         push @where_strings, 'items.homebranch = ?';
995         }else{
996             push @where_strings, 'items.holdingbranch = ?';
997         }
998         push @bind_params, $branchcode;
999     }
1000     
1001     if ( $itemtype ) {
1002         push @where_strings, 'biblioitems.itemtype = ?';
1003         push @bind_params, $itemtype;
1004     }
1005
1006     if ( $ignoreissued) {
1007         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1008         push @where_strings, 'issues.date_due IS NULL';
1009     }
1010
1011     if ( @where_strings ) {
1012         $query .= 'WHERE ';
1013         $query .= join ' AND ', @where_strings;
1014     }
1015     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1016     my $sth = $dbh->prepare($query);
1017     $sth->execute( @bind_params );
1018
1019     my @results;
1020     $size--;
1021     while ( my $row = $sth->fetchrow_hashref ) {
1022         $offset-- if ($offset);
1023         $row->{datelastseen}=format_date($row->{datelastseen});
1024         if ( ( !$offset ) && $size ) {
1025             push @results, $row;
1026             $size--;
1027         }
1028     }
1029     return \@results;
1030 }
1031
1032 =head2 GetItemsCount
1033
1034   $count = &GetItemsCount( $biblionumber);
1035
1036 This function return count of item with $biblionumber
1037
1038 =cut
1039
1040 sub GetItemsCount {
1041     my ( $biblionumber ) = @_;
1042     my $dbh = C4::Context->dbh;
1043     my $query = "SELECT count(*)
1044           FROM  items 
1045           WHERE biblionumber=?";
1046     my $sth = $dbh->prepare($query);
1047     $sth->execute($biblionumber);
1048     my $count = $sth->fetchrow;  
1049     return ($count);
1050 }
1051
1052 =head2 GetItemInfosOf
1053
1054   GetItemInfosOf(@itemnumbers);
1055
1056 =cut
1057
1058 sub GetItemInfosOf {
1059     my @itemnumbers = @_;
1060
1061     my $query = '
1062         SELECT *
1063         FROM items
1064         WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
1065     ';
1066     return get_infos_of( $query, 'itemnumber' );
1067 }
1068
1069 =head2 GetItemsByBiblioitemnumber
1070
1071   GetItemsByBiblioitemnumber($biblioitemnumber);
1072
1073 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1074 Called by C<C4::XISBN>
1075
1076 =cut
1077
1078 sub GetItemsByBiblioitemnumber {
1079     my ( $bibitem ) = @_;
1080     my $dbh = C4::Context->dbh;
1081     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1082     # Get all items attached to a biblioitem
1083     my $i = 0;
1084     my @results; 
1085     $sth->execute($bibitem) || die $sth->errstr;
1086     while ( my $data = $sth->fetchrow_hashref ) {  
1087         # Foreach item, get circulation information
1088         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1089                                    WHERE itemnumber = ?
1090                                    AND issues.borrowernumber = borrowers.borrowernumber"
1091         );
1092         $sth2->execute( $data->{'itemnumber'} );
1093         if ( my $data2 = $sth2->fetchrow_hashref ) {
1094             # if item is out, set the due date and who it is out too
1095             $data->{'date_due'}   = $data2->{'date_due'};
1096             $data->{'cardnumber'} = $data2->{'cardnumber'};
1097             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1098         }
1099         else {
1100             # set date_due to blank, so in the template we check itemlost, and wthdrawn 
1101             $data->{'date_due'} = '';                                                                                                         
1102         }    # else         
1103         # Find the last 3 people who borrowed this item.                  
1104         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1105                       AND old_issues.borrowernumber = borrowers.borrowernumber
1106                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1107         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1108         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1109         my $i2 = 0;
1110         while ( my $data2 = $sth2->fetchrow_hashref ) {
1111             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1112             $data->{"card$i2"}      = $data2->{'cardnumber'};
1113             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1114             $i2++;
1115         }
1116         push(@results,$data);
1117     } 
1118     return (\@results); 
1119 }
1120
1121 =head2 GetItemsInfo
1122
1123   @results = GetItemsInfo($biblionumber, $type);
1124
1125 Returns information about books with the given biblionumber.
1126
1127 C<$type> may be either C<intra> or anything else. If it is not set to
1128 C<intra>, then the search will exclude lost, very overdue, and
1129 withdrawn items.
1130
1131 C<GetItemsInfo> returns a list of references-to-hash. Each element
1132 contains a number of keys. Most of them are table items from the
1133 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1134 Koha database. Other keys include:
1135
1136 =over 2
1137
1138 =item C<$data-E<gt>{branchname}>
1139
1140 The name (not the code) of the branch to which the book belongs.
1141
1142 =item C<$data-E<gt>{datelastseen}>
1143
1144 This is simply C<items.datelastseen>, except that while the date is
1145 stored in YYYY-MM-DD format in the database, here it is converted to
1146 DD/MM/YYYY format. A NULL date is returned as C<//>.
1147
1148 =item C<$data-E<gt>{datedue}>
1149
1150 =item C<$data-E<gt>{class}>
1151
1152 This is the concatenation of C<biblioitems.classification>, the book's
1153 Dewey code, and C<biblioitems.subclass>.
1154
1155 =item C<$data-E<gt>{ocount}>
1156
1157 I think this is the number of copies of the book available.
1158
1159 =item C<$data-E<gt>{order}>
1160
1161 If this is set, it is set to C<One Order>.
1162
1163 =back
1164
1165 =cut
1166
1167 sub GetItemsInfo {
1168     my ( $biblionumber, $type ) = @_;
1169     my $dbh   = C4::Context->dbh;
1170     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1171     my $query = "
1172     SELECT items.*,
1173            biblio.*,
1174            biblioitems.volume,
1175            biblioitems.number,
1176            biblioitems.itemtype,
1177            biblioitems.isbn,
1178            biblioitems.issn,
1179            biblioitems.publicationyear,
1180            biblioitems.publishercode,
1181            biblioitems.volumedate,
1182            biblioitems.volumedesc,
1183            biblioitems.lccn,
1184            biblioitems.url,
1185            items.notforloan as itemnotforloan,
1186            itemtypes.description,
1187            itemtypes.notforloan as notforloan_per_itemtype,
1188            branchurl
1189      FROM items
1190      LEFT JOIN branches ON items.homebranch = branches.branchcode
1191      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1192      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1193      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1194      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1195     $query .= " WHERE items.biblionumber = ? ORDER BY branches.branchname,items.dateaccessioned desc" ;
1196     my $sth = $dbh->prepare($query);
1197     $sth->execute($biblionumber);
1198     my $i = 0;
1199     my @results;
1200     my $serial;
1201
1202     my $isth    = $dbh->prepare(
1203         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
1204         FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
1205         WHERE  itemnumber = ?"
1206        );
1207         my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? "); 
1208         while ( my $data = $sth->fetchrow_hashref ) {
1209         my $datedue = '';
1210         my $count_reserves;
1211         $isth->execute( $data->{'itemnumber'} );
1212         if ( my $idata = $isth->fetchrow_hashref ) {
1213             $data->{borrowernumber} = $idata->{borrowernumber};
1214             $data->{cardnumber}     = $idata->{cardnumber};
1215             $data->{surname}     = $idata->{surname};
1216             $data->{firstname}     = $idata->{firstname};
1217             $datedue                = $idata->{'date_due'};
1218         if (C4::Context->preference("IndependantBranches")){
1219         my $userenv = C4::Context->userenv;
1220         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) { 
1221             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1222         }
1223         }
1224         }
1225                 if ( $data->{'serial'}) {       
1226                         $ssth->execute($data->{'itemnumber'}) ;
1227                         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1228                         $serial = 1;
1229         }
1230                 if ( $datedue eq '' ) {
1231             my ( $restype, $reserves ) =
1232               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
1233 # Previous conditional check with if ($restype) is not needed because a true
1234 # result for one item will result in subsequent items defaulting to this true
1235 # value.
1236             $count_reserves = $restype;
1237         }
1238         #get branch information.....
1239         my $bsth = $dbh->prepare(
1240             "SELECT * FROM branches WHERE branchcode = ?
1241         "
1242         );
1243         $bsth->execute( $data->{'holdingbranch'} );
1244         if ( my $bdata = $bsth->fetchrow_hashref ) {
1245             $data->{'branchname'} = $bdata->{'branchname'};
1246         }
1247         $data->{'datedue'}        = $datedue;
1248         $data->{'count_reserves'} = $count_reserves;
1249
1250         # get notforloan complete status if applicable
1251         my $sthnflstatus = $dbh->prepare(
1252             'SELECT authorised_value
1253             FROM   marc_subfield_structure
1254             WHERE  kohafield="items.notforloan"
1255         '
1256         );
1257
1258         $sthnflstatus->execute;
1259         my ($authorised_valuecode) = $sthnflstatus->fetchrow;
1260         if ($authorised_valuecode) {
1261             $sthnflstatus = $dbh->prepare(
1262                 "SELECT lib FROM authorised_values
1263                  WHERE  category=?
1264                  AND authorised_value=?"
1265             );
1266             $sthnflstatus->execute( $authorised_valuecode,
1267                 $data->{itemnotforloan} );
1268             my ($lib) = $sthnflstatus->fetchrow;
1269             $data->{notforloanvalue} = $lib;
1270         }
1271
1272         # get restricted status and description if applicable
1273         my $restrictedstatus = $dbh->prepare(
1274             'SELECT authorised_value
1275             FROM   marc_subfield_structure
1276             WHERE  kohafield="items.restricted"
1277         '
1278         );
1279
1280         $restrictedstatus->execute;
1281         ($authorised_valuecode) = $restrictedstatus->fetchrow;
1282         if ($authorised_valuecode) {
1283             $restrictedstatus = $dbh->prepare(
1284                 "SELECT lib,lib_opac FROM authorised_values
1285                  WHERE  category=?
1286                  AND authorised_value=?"
1287             );
1288             $restrictedstatus->execute( $authorised_valuecode,
1289                 $data->{restricted} );
1290
1291             if ( my $rstdata = $restrictedstatus->fetchrow_hashref ) {
1292                 $data->{restricted} = $rstdata->{'lib'};
1293                 $data->{restrictedopac} = $rstdata->{'lib_opac'};
1294             }
1295         }
1296
1297         # my stack procedures
1298         my $stackstatus = $dbh->prepare(
1299             'SELECT authorised_value
1300              FROM   marc_subfield_structure
1301              WHERE  kohafield="items.stack"
1302         '
1303         );
1304         $stackstatus->execute;
1305
1306         ($authorised_valuecode) = $stackstatus->fetchrow;
1307         if ($authorised_valuecode) {
1308             $stackstatus = $dbh->prepare(
1309                 "SELECT lib
1310                  FROM   authorised_values
1311                  WHERE  category=?
1312                  AND    authorised_value=?
1313             "
1314             );
1315             $stackstatus->execute( $authorised_valuecode, $data->{stack} );
1316             my ($lib) = $stackstatus->fetchrow;
1317             $data->{stack} = $lib;
1318         }
1319         # Find the last 3 people who borrowed this item.
1320         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1321                                     WHERE itemnumber = ?
1322                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1323                                     ORDER BY returndate DESC
1324                                     LIMIT 3");
1325         $sth2->execute($data->{'itemnumber'});
1326         my $ii = 0;
1327         while (my $data2 = $sth2->fetchrow_hashref()) {
1328             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1329             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1330             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1331             $ii++;
1332         }
1333
1334         $results[$i] = $data;
1335         $i++;
1336     }
1337         if($serial) {
1338                 return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
1339         } else {
1340         return (@results);
1341         }
1342 }
1343
1344 =head2 GetItemsLocationInfo
1345
1346   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1347
1348 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1349
1350 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1351
1352 =over 2
1353
1354 =item C<$data-E<gt>{homebranch}>
1355
1356 Branch Name of the item's homebranch
1357
1358 =item C<$data-E<gt>{holdingbranch}>
1359
1360 Branch Name of the item's holdingbranch
1361
1362 =item C<$data-E<gt>{location}>
1363
1364 Item's shelving location code
1365
1366 =item C<$data-E<gt>{location_intranet}>
1367
1368 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1369
1370 =item C<$data-E<gt>{location_opac}>
1371
1372 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
1373 description is set.
1374
1375 =item C<$data-E<gt>{itemcallnumber}>
1376
1377 Item's itemcallnumber
1378
1379 =item C<$data-E<gt>{cn_sort}>
1380
1381 Item's call number normalized for sorting
1382
1383 =back
1384   
1385 =cut
1386
1387 sub GetItemsLocationInfo {
1388         my $biblionumber = shift;
1389         my @results;
1390
1391         my $dbh = C4::Context->dbh;
1392         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
1393                             location, itemcallnumber, cn_sort
1394                      FROM items, branches as a, branches as b
1395                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
1396                      AND biblionumber = ?
1397                      ORDER BY cn_sort ASC";
1398         my $sth = $dbh->prepare($query);
1399         $sth->execute($biblionumber);
1400
1401         while ( my $data = $sth->fetchrow_hashref ) {
1402              $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
1403              $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
1404              push @results, $data;
1405         }
1406         return @results;
1407 }
1408
1409
1410 =head2 GetLastAcquisitions
1411
1412   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1413                                     'itemtypes' => ('BK','BD')}, 10);
1414
1415 =cut
1416
1417 sub  GetLastAcquisitions {
1418         my ($data,$max) = @_;
1419
1420         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1421         
1422         my $number_of_branches = @{$data->{branches}};
1423         my $number_of_itemtypes   = @{$data->{itemtypes}};
1424         
1425         
1426         my @where = ('WHERE 1 '); 
1427         $number_of_branches and push @where
1428            , 'AND holdingbranch IN (' 
1429            , join(',', ('?') x $number_of_branches )
1430            , ')'
1431          ;
1432         
1433         $number_of_itemtypes and push @where
1434            , "AND $itemtype IN (" 
1435            , join(',', ('?') x $number_of_itemtypes )
1436            , ')'
1437          ;
1438
1439         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1440                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1441                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1442                                     @where
1443                                     GROUP BY biblio.biblionumber 
1444                                     ORDER BY dateaccessioned DESC LIMIT $max";
1445
1446         my $dbh = C4::Context->dbh;
1447         my $sth = $dbh->prepare($query);
1448     
1449     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1450         
1451         my @results;
1452         while( my $row = $sth->fetchrow_hashref){
1453                 push @results, {date => $row->{dateaccessioned} 
1454                                                 , biblionumber => $row->{biblionumber}
1455                                                 , title => $row->{title}};
1456         }
1457         
1458         return @results;
1459 }
1460
1461 =head2 get_itemnumbers_of
1462
1463   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1464
1465 Given a list of biblionumbers, return the list of corresponding itemnumbers
1466 for each biblionumber.
1467
1468 Return a reference on a hash where keys are biblionumbers and values are
1469 references on array of itemnumbers.
1470
1471 =cut
1472
1473 sub get_itemnumbers_of {
1474     my @biblionumbers = @_;
1475
1476     my $dbh = C4::Context->dbh;
1477
1478     my $query = '
1479         SELECT itemnumber,
1480             biblionumber
1481         FROM items
1482         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1483     ';
1484     my $sth = $dbh->prepare($query);
1485     $sth->execute(@biblionumbers);
1486
1487     my %itemnumbers_of;
1488
1489     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1490         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1491     }
1492
1493     return \%itemnumbers_of;
1494 }
1495
1496 =head2 GetItemnumberFromBarcode
1497
1498   $result = GetItemnumberFromBarcode($barcode);
1499
1500 =cut
1501
1502 sub GetItemnumberFromBarcode {
1503     my ($barcode) = @_;
1504     my $dbh = C4::Context->dbh;
1505
1506     my $rq =
1507       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1508     $rq->execute($barcode);
1509     my ($result) = $rq->fetchrow;
1510     return ($result);
1511 }
1512
1513 =head2 GetBarcodeFromItemnumber
1514
1515   $result = GetBarcodeFromItemnumber($itemnumber);
1516
1517 =cut
1518
1519 sub GetBarcodeFromItemnumber {
1520     my ($itemnumber) = @_;
1521     my $dbh = C4::Context->dbh;
1522
1523     my $rq =
1524       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1525     $rq->execute($itemnumber);
1526     my ($result) = $rq->fetchrow;
1527     return ($result);
1528 }
1529
1530 =head2 GetHiddenItemnumbers
1531
1532 =over 4
1533
1534 $result = GetHiddenItemnumbers(@items);
1535
1536 =back
1537
1538 =cut
1539
1540 sub GetHiddenItemnumbers {
1541     my (@items) = @_;
1542     my @resultitems;
1543
1544     my $yaml = C4::Context->preference('OpacHiddenItems');
1545     my $hidingrules;
1546     eval {
1547         $hidingrules = YAML::Load($yaml);
1548     };
1549     if ($@) {
1550         warn "Unable to parse OpacHiddenItems syspref : $@";
1551         return ();
1552     } else {
1553     my $dbh = C4::Context->dbh;
1554
1555         # For each item
1556         foreach my $item (@items) {
1557
1558             # We check each rule
1559             foreach my $field (keys %$hidingrules) {
1560                 my $query = "SELECT $field from items where itemnumber = ?";
1561                 my $sth = $dbh->prepare($query);        
1562                 $sth->execute($item->{'itemnumber'});
1563                 my ($result) = $sth->fetchrow;
1564
1565                 # If the results matches the values in the yaml file
1566                 if (any { $result eq $_ } @{$hidingrules->{$field}}) {
1567
1568                     # We add the itemnumber to the list
1569                     push @resultitems, $item->{'itemnumber'};       
1570
1571                     # If at least one rule matched for an item, no need to test the others
1572                     last;
1573                 }
1574             }
1575         }
1576         return @resultitems;
1577     }
1578
1579  }
1580
1581 =head3 get_item_authorised_values
1582
1583 find the types and values for all authorised values assigned to this item.
1584
1585 parameters: itemnumber
1586
1587 returns: a hashref malling the authorised value to the value set for this itemnumber
1588
1589     $authorised_values = {
1590              'CCODE'      => undef,
1591              'DAMAGED'    => '0',
1592              'LOC'        => '3',
1593              'LOST'       => '0'
1594              'NOT_LOAN'   => '0',
1595              'RESTRICTED' => undef,
1596              'STACK'      => undef,
1597              'WITHDRAWN'  => '0',
1598              'branches'   => 'CPL',
1599              'cn_source'  => undef,
1600              'itemtypes'  => 'SER',
1601            };
1602
1603 Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
1604
1605 =cut
1606
1607 sub get_item_authorised_values {
1608     my $itemnumber = shift;
1609
1610     # assume that these entries in the authorised_value table are item level.
1611     my $query = q(SELECT distinct authorised_value, kohafield
1612                     FROM marc_subfield_structure
1613                     WHERE kohafield like 'item%'
1614                       AND authorised_value != '' );
1615
1616     my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
1617     my $iteminfo = GetItem( $itemnumber );
1618     # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
1619     my $return;
1620     foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
1621         my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
1622         $field =~ s/^items\.//;
1623         if ( exists $iteminfo->{ $field } ) {
1624             $return->{ $this_authorised_value } = $iteminfo->{ $field };
1625         }
1626     }
1627     # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
1628     return $return;
1629 }
1630
1631 =head3 get_authorised_value_images
1632
1633 find a list of icons that are appropriate for display based on the
1634 authorised values for a biblio.
1635
1636 parameters: listref of authorised values, such as comes from
1637 get_item_authorised_values or
1638 from C4::Biblio::get_biblio_authorised_values
1639
1640 returns: listref of hashrefs for each image. Each hashref looks like this:
1641
1642       { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
1643         label    => '',
1644         category => '',
1645         value    => '', }
1646
1647 Notes: Currently, I put on the full path to the images on the staff
1648 side. This should either be configurable or not done at all. Since I
1649 have to deal with 'intranet' or 'opac' in
1650 get_biblio_authorised_values, perhaps I should be passing it in.
1651
1652 =cut
1653
1654 sub get_authorised_value_images {
1655     my $authorised_values = shift;
1656
1657     my @imagelist;
1658
1659     my $authorised_value_list = GetAuthorisedValues();
1660     # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
1661     foreach my $this_authorised_value ( @$authorised_value_list ) {
1662         if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
1663              && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
1664             # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
1665             if ( defined $this_authorised_value->{'imageurl'} ) {
1666                 push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
1667                                    label    => $this_authorised_value->{'lib'},
1668                                    category => $this_authorised_value->{'category'},
1669                                    value    => $this_authorised_value->{'authorised_value'}, };
1670             }
1671         }
1672     }
1673
1674     # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
1675     return \@imagelist;
1676
1677 }
1678
1679 =head1 LIMITED USE FUNCTIONS
1680
1681 The following functions, while part of the public API,
1682 are not exported.  This is generally because they are
1683 meant to be used by only one script for a specific
1684 purpose, and should not be used in any other context
1685 without careful thought.
1686
1687 =cut
1688
1689 =head2 GetMarcItem
1690
1691   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1692
1693 Returns MARC::Record of the item passed in parameter.
1694 This function is meant for use only in C<cataloguing/additem.pl>,
1695 where it is needed to support that script's MARC-like
1696 editor.
1697
1698 =cut
1699
1700 sub GetMarcItem {
1701     my ( $biblionumber, $itemnumber ) = @_;
1702
1703     # GetMarcItem has been revised so that it does the following:
1704     #  1. Gets the item information from the items table.
1705     #  2. Converts it to a MARC field for storage in the bib record.
1706     #
1707     # The previous behavior was:
1708     #  1. Get the bib record.
1709     #  2. Return the MARC tag corresponding to the item record.
1710     #
1711     # The difference is that one treats the items row as authoritative,
1712     # while the other treats the MARC representation as authoritative
1713     # under certain circumstances.
1714
1715     my $itemrecord = GetItem($itemnumber);
1716
1717     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1718     # Also, don't emit a subfield if the underlying field is blank.
1719
1720     
1721     return Item2Marc($itemrecord,$biblionumber);
1722
1723 }
1724 sub Item2Marc {
1725         my ($itemrecord,$biblionumber)=@_;
1726     my $mungeditem = { 
1727         map {  
1728             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1729         } keys %{ $itemrecord } 
1730     };
1731     my $itemmarc = TransformKohaToMarc($mungeditem);
1732     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1733
1734     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1735     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1736                 foreach my $field ($itemmarc->field($itemtag)){
1737             $field->add_subfields(@$unlinked_item_subfields);
1738         }
1739     }
1740         return $itemmarc;
1741 }
1742
1743 =head1 PRIVATE FUNCTIONS AND VARIABLES
1744
1745 The following functions are not meant to be called
1746 directly, but are documented in order to explain
1747 the inner workings of C<C4::Items>.
1748
1749 =cut
1750
1751 =head2 %derived_columns
1752
1753 This hash keeps track of item columns that
1754 are strictly derived from other columns in
1755 the item record and are not meant to be set
1756 independently.
1757
1758 Each key in the hash should be the name of a
1759 column (as named by TransformMarcToKoha).  Each
1760 value should be hashref whose keys are the
1761 columns on which the derived column depends.  The
1762 hashref should also contain a 'BUILDER' key
1763 that is a reference to a sub that calculates
1764 the derived value.
1765
1766 =cut
1767
1768 my %derived_columns = (
1769     'items.cn_sort' => {
1770         'itemcallnumber' => 1,
1771         'items.cn_source' => 1,
1772         'BUILDER' => \&_calc_items_cn_sort,
1773     }
1774 );
1775
1776 =head2 _set_derived_columns_for_add 
1777
1778   _set_derived_column_for_add($item);
1779
1780 Given an item hash representing a new item to be added,
1781 calculate any derived columns.  Currently the only
1782 such column is C<items.cn_sort>.
1783
1784 =cut
1785
1786 sub _set_derived_columns_for_add {
1787     my $item = shift;
1788
1789     foreach my $column (keys %derived_columns) {
1790         my $builder = $derived_columns{$column}->{'BUILDER'};
1791         my $source_values = {};
1792         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1793             next if $source_column eq 'BUILDER';
1794             $source_values->{$source_column} = $item->{$source_column};
1795         }
1796         $builder->($item, $source_values);
1797     }
1798 }
1799
1800 =head2 _set_derived_columns_for_mod 
1801
1802   _set_derived_column_for_mod($item);
1803
1804 Given an item hash representing a new item to be modified.
1805 calculate any derived columns.  Currently the only
1806 such column is C<items.cn_sort>.
1807
1808 This routine differs from C<_set_derived_columns_for_add>
1809 in that it needs to handle partial item records.  In other
1810 words, the caller of C<ModItem> may have supplied only one
1811 or two columns to be changed, so this function needs to
1812 determine whether any of the columns to be changed affect
1813 any of the derived columns.  Also, if a derived column
1814 depends on more than one column, but the caller is not
1815 changing all of then, this routine retrieves the unchanged
1816 values from the database in order to ensure a correct
1817 calculation.
1818
1819 =cut
1820
1821 sub _set_derived_columns_for_mod {
1822     my $item = shift;
1823
1824     foreach my $column (keys %derived_columns) {
1825         my $builder = $derived_columns{$column}->{'BUILDER'};
1826         my $source_values = {};
1827         my %missing_sources = ();
1828         my $must_recalc = 0;
1829         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1830             next if $source_column eq 'BUILDER';
1831             if (exists $item->{$source_column}) {
1832                 $must_recalc = 1;
1833                 $source_values->{$source_column} = $item->{$source_column};
1834             } else {
1835                 $missing_sources{$source_column} = 1;
1836             }
1837         }
1838         if ($must_recalc) {
1839             foreach my $source_column (keys %missing_sources) {
1840                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1841             }
1842             $builder->($item, $source_values);
1843         }
1844     }
1845 }
1846
1847 =head2 _do_column_fixes_for_mod
1848
1849   _do_column_fixes_for_mod($item);
1850
1851 Given an item hashref containing one or more
1852 columns to modify, fix up certain values.
1853 Specifically, set to 0 any passed value
1854 of C<notforloan>, C<damaged>, C<itemlost>, or
1855 C<wthdrawn> that is either undefined or
1856 contains the empty string.
1857
1858 =cut
1859
1860 sub _do_column_fixes_for_mod {
1861     my $item = shift;
1862
1863     if (exists $item->{'notforloan'} and
1864         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1865         $item->{'notforloan'} = 0;
1866     }
1867     if (exists $item->{'damaged'} and
1868         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1869         $item->{'damaged'} = 0;
1870     }
1871     if (exists $item->{'itemlost'} and
1872         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1873         $item->{'itemlost'} = 0;
1874     }
1875     if (exists $item->{'wthdrawn'} and
1876         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1877         $item->{'wthdrawn'} = 0;
1878     }
1879     if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
1880         $item->{'permanent_location'} = $item->{'location'};
1881     }
1882     if (exists $item->{'timestamp'}) {
1883         delete $item->{'timestamp'};
1884     }
1885 }
1886
1887 =head2 _get_single_item_column
1888
1889   _get_single_item_column($column, $itemnumber);
1890
1891 Retrieves the value of a single column from an C<items>
1892 row specified by C<$itemnumber>.
1893
1894 =cut
1895
1896 sub _get_single_item_column {
1897     my $column = shift;
1898     my $itemnumber = shift;
1899     
1900     my $dbh = C4::Context->dbh;
1901     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1902     $sth->execute($itemnumber);
1903     my ($value) = $sth->fetchrow();
1904     return $value; 
1905 }
1906
1907 =head2 _calc_items_cn_sort
1908
1909   _calc_items_cn_sort($item, $source_values);
1910
1911 Helper routine to calculate C<items.cn_sort>.
1912
1913 =cut
1914
1915 sub _calc_items_cn_sort {
1916     my $item = shift;
1917     my $source_values = shift;
1918
1919     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1920 }
1921
1922 =head2 _set_defaults_for_add 
1923
1924   _set_defaults_for_add($item_hash);
1925
1926 Given an item hash representing an item to be added, set
1927 correct default values for columns whose default value
1928 is not handled by the DBMS.  This includes the following
1929 columns:
1930
1931 =over 2
1932
1933 =item * 
1934
1935 C<items.dateaccessioned>
1936
1937 =item *
1938
1939 C<items.notforloan>
1940
1941 =item *
1942
1943 C<items.damaged>
1944
1945 =item *
1946
1947 C<items.itemlost>
1948
1949 =item *
1950
1951 C<items.wthdrawn>
1952
1953 =back
1954
1955 =cut
1956
1957 sub _set_defaults_for_add {
1958     my $item = shift;
1959     $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
1960     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
1961 }
1962
1963 =head2 _koha_new_item
1964
1965   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1966
1967 Perform the actual insert into the C<items> table.
1968
1969 =cut
1970
1971 sub _koha_new_item {
1972     my ( $item, $barcode ) = @_;
1973     my $dbh=C4::Context->dbh;  
1974     my $error;
1975     my $query =
1976            "INSERT INTO items SET
1977             biblionumber        = ?,
1978             biblioitemnumber    = ?,
1979             barcode             = ?,
1980             dateaccessioned     = ?,
1981             booksellerid        = ?,
1982             homebranch          = ?,
1983             price               = ?,
1984             replacementprice    = ?,
1985             replacementpricedate = NOW(),
1986             datelastborrowed    = ?,
1987             datelastseen        = NOW(),
1988             stack               = ?,
1989             notforloan          = ?,
1990             damaged             = ?,
1991             itemlost            = ?,
1992             wthdrawn            = ?,
1993             itemcallnumber      = ?,
1994             restricted          = ?,
1995             itemnotes           = ?,
1996             holdingbranch       = ?,
1997             paidfor             = ?,
1998             location            = ?,
1999             onloan              = ?,
2000             issues              = ?,
2001             renewals            = ?,
2002             reserves            = ?,
2003             cn_source           = ?,
2004             cn_sort             = ?,
2005             ccode               = ?,
2006             itype               = ?,
2007             materials           = ?,
2008             uri = ?,
2009             enumchron           = ?,
2010             more_subfields_xml  = ?,
2011             copynumber          = ?,
2012             stocknumber         = ?
2013           ";
2014     my $sth = $dbh->prepare($query);
2015    $sth->execute(
2016             $item->{'biblionumber'},
2017             $item->{'biblioitemnumber'},
2018             $barcode,
2019             $item->{'dateaccessioned'},
2020             $item->{'booksellerid'},
2021             $item->{'homebranch'},
2022             $item->{'price'},
2023             $item->{'replacementprice'},
2024             $item->{datelastborrowed},
2025             $item->{stack},
2026             $item->{'notforloan'},
2027             $item->{'damaged'},
2028             $item->{'itemlost'},
2029             $item->{'wthdrawn'},
2030             $item->{'itemcallnumber'},
2031             $item->{'restricted'},
2032             $item->{'itemnotes'},
2033             $item->{'holdingbranch'},
2034             $item->{'paidfor'},
2035             $item->{'location'},
2036             $item->{'onloan'},
2037             $item->{'issues'},
2038             $item->{'renewals'},
2039             $item->{'reserves'},
2040             $item->{'items.cn_source'},
2041             $item->{'items.cn_sort'},
2042             $item->{'ccode'},
2043             $item->{'itype'},
2044             $item->{'materials'},
2045             $item->{'uri'},
2046             $item->{'enumchron'},
2047             $item->{'more_subfields_xml'},
2048             $item->{'copynumber'},
2049             $item->{'stocknumber'},
2050     );
2051     my $itemnumber = $dbh->{'mysql_insertid'};
2052     if ( defined $sth->errstr ) {
2053         $error.="ERROR in _koha_new_item $query".$sth->errstr;
2054     }
2055     return ( $itemnumber, $error );
2056 }
2057
2058 =head2 MoveItemFromBiblio
2059
2060   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2061
2062 Moves an item from a biblio to another
2063
2064 Returns undef if the move failed or the biblionumber of the destination record otherwise
2065
2066 =cut
2067
2068 sub MoveItemFromBiblio {
2069     my ($itemnumber, $frombiblio, $tobiblio) = @_;
2070     my $dbh = C4::Context->dbh;
2071     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
2072     $sth->execute( $tobiblio );
2073     my ( $tobiblioitem ) = $sth->fetchrow();
2074     $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
2075     my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
2076     if ($return == 1) {
2077         ModZebra( $tobiblio, "specialUpdate", "biblioserver", undef, undef );
2078         ModZebra( $frombiblio, "specialUpdate", "biblioserver", undef, undef );
2079             # Checking if the item we want to move is in an order 
2080         my $order = GetOrderFromItemnumber($itemnumber);
2081             if ($order) {
2082                     # Replacing the biblionumber within the order if necessary
2083                     $order->{'biblionumber'} = $tobiblio;
2084                 ModOrder($order);
2085             }
2086         return $tobiblio;
2087         }
2088     return;
2089 }
2090
2091 =head2 DelItemCheck
2092
2093    DelItemCheck($dbh, $biblionumber, $itemnumber);
2094
2095 Exported function (core API) for deleting an item record in Koha if there no current issue.
2096
2097 =cut
2098
2099 sub DelItemCheck {
2100     my ( $dbh, $biblionumber, $itemnumber ) = @_;
2101     my $error;
2102
2103     # check that there is no issue on this item before deletion.
2104     my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
2105     $sth->execute($itemnumber);
2106
2107     my $item = GetItem($itemnumber);
2108     my $onloan = $sth->fetchrow;
2109     if ($onloan) {
2110         $error = "book_on_loan";
2111     }
2112     elsif (C4::Context->preference("IndependantBranches") and (C4::Context->userenv->{branch} ne $item->{C4::Context->preference("HomeOrHoldingBranch")||'homebranch'})){
2113         $error = "not_same_branch";
2114     } 
2115     else {
2116         if ($onloan){ 
2117             $error = "book_on_loan" 
2118         }
2119         else {
2120             # check it doesnt have a waiting reserve
2121             $sth=$dbh->prepare("SELECT * FROM reserves WHERE (found = 'W' or found = 'T') AND itemnumber = ?");
2122             $sth->execute($itemnumber);
2123             my $reserve=$sth->fetchrow;
2124             if ($reserve) {
2125                 $error = "book_reserved";
2126             } 
2127             else {
2128                 DelItem($dbh, $biblionumber, $itemnumber);
2129                 return 1;
2130             }
2131         }
2132     }
2133     return $error;
2134 }
2135
2136 =head2 _koha_modify_item
2137
2138   my ($itemnumber,$error) =_koha_modify_item( $item );
2139
2140 Perform the actual update of the C<items> row.  Note that this
2141 routine accepts a hashref specifying the columns to update.
2142
2143 =cut
2144
2145 sub _koha_modify_item {
2146     my ( $item ) = @_;
2147     my $dbh=C4::Context->dbh;  
2148     my $error;
2149
2150     my $query = "UPDATE items SET ";
2151     my @bind;
2152     for my $key ( keys %$item ) {
2153         $query.="$key=?,";
2154         push @bind, $item->{$key};
2155     }
2156     $query =~ s/,$//;
2157     $query .= " WHERE itemnumber=?";
2158     push @bind, $item->{'itemnumber'};
2159     my $sth = C4::Context->dbh->prepare($query);
2160     $sth->execute(@bind);
2161     if ( C4::Context->dbh->errstr ) {
2162         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
2163         warn $error;
2164     }
2165     ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver", undef, undef );
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;