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