Merge commit 'kc/master'
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 #
5 # This file is part of Koha.
6 #
7 # Koha is free software; you can redistribute it and/or modify it under the
8 # terms of the GNU General Public License as published by the Free Software
9 # Foundation; either version 2 of the License, or (at your option) any later
10 # version.
11 #
12 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
13 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License along
17 # with Koha; if not, write to the Free Software Foundation, Inc.,
18 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19
20 use strict;
21 #use warnings; FIXME - Bug 2505
22
23 use Carp;
24 use C4::Context;
25 use C4::Koha;
26 use C4::Biblio;
27 use C4::Dates qw/format_date format_date_in_iso/;
28 use MARC::Record;
29 use C4::ClassSource;
30 use C4::Log;
31 use C4::Branch;
32 require C4::Reserves;
33 use C4::Charset;
34 use C4::Acquisition;
35
36 use vars qw($VERSION @ISA @EXPORT);
37
38 BEGIN {
39     $VERSION = 3.01;
40
41         require Exporter;
42     @ISA = qw( Exporter );
43
44     # function exports
45     @EXPORT = qw(
46         GetItem
47         AddItemFromMarc
48         AddItem
49         AddItemBatchFromMarc
50         ModItemFromMarc
51                 Item2Marc
52         ModItem
53         ModDateLastSeen
54         ModItemTransfer
55         DelItem
56     
57         CheckItemPreSave
58     
59         GetItemStatus
60         GetItemLocation
61         GetLostItems
62         GetItemsForInventory
63         GetItemsCount
64         GetItemInfosOf
65         GetItemsByBiblioitemnumber
66         GetItemsInfo
67         get_itemnumbers_of
68         GetItemnumberFromBarcode
69         GetBarcodeFromItemnumber
70
71                 DelItemCheck
72                 MoveItemFromBiblio 
73                 GetLatestAcquisitions
74         CartToShelf
75     );
76 }
77
78 =head1 NAME
79
80 C4::Items - item management functions
81
82 =head1 DESCRIPTION
83
84 This module contains an API for manipulating item 
85 records in Koha, and is used by cataloguing, circulation,
86 acquisitions, and serials management.
87
88 A Koha item record is stored in two places: the
89 items table and embedded in a MARC tag in the XML
90 version of the associated bib record in C<biblioitems.marcxml>.
91 This is done to allow the item information to be readily
92 indexed (e.g., by Zebra), but means that each item
93 modification transaction must keep the items table
94 and the MARC XML in sync at all times.
95
96 Consequently, all code that creates, modifies, or deletes
97 item records B<must> use an appropriate function from 
98 C<C4::Items>.  If no existing function is suitable, it is
99 better to add one to C<C4::Items> than to use add
100 one-off SQL statements to add or modify items.
101
102 The items table will be considered authoritative.  In other
103 words, if there is ever a discrepancy between the items
104 table and the MARC XML, the items table should be considered
105 accurate.
106
107 =head1 HISTORICAL NOTE
108
109 Most of the functions in C<C4::Items> were originally in
110 the C<C4::Biblio> module.
111
112 =head1 CORE EXPORTED FUNCTIONS
113
114 The following functions are meant for use by users
115 of C<C4::Items>
116
117 =cut
118
119 =head2 GetItem
120
121   $item = GetItem($itemnumber,$barcode,$serial);
122
123 Return item information, for a given itemnumber or barcode.
124 The return value is a hashref mapping item column
125 names to values.  If C<$serial> is true, include serial publication data.
126
127 =cut
128
129 sub GetItem {
130     my ($itemnumber,$barcode, $serial) = @_;
131     my $dbh = C4::Context->dbh;
132         my $data;
133     if ($itemnumber) {
134         my $sth = $dbh->prepare("
135             SELECT * FROM items 
136             WHERE itemnumber = ?");
137         $sth->execute($itemnumber);
138         $data = $sth->fetchrow_hashref;
139     } else {
140         my $sth = $dbh->prepare("
141             SELECT * FROM items 
142             WHERE barcode = ?"
143             );
144         $sth->execute($barcode);                
145         $data = $sth->fetchrow_hashref;
146     }
147     if ( $serial) {      
148     my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
149         $ssth->execute($data->{'itemnumber'}) ;
150         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
151                 warn $data->{'serialseq'} , $data->{'publisheddate'};
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, $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 ( $branch ) {
999         push @where_strings, 'items.homebranch = ?';
1000         push @bind_params, $branch;
1001     }
1002     
1003     if ( $itemtype ) {
1004         push @where_strings, 'biblioitems.itemtype = ?';
1005         push @bind_params, $itemtype;
1006     }
1007
1008     if ( $ignoreissued) {
1009         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1010         push @where_strings, 'issues.date_due IS NULL';
1011     }
1012
1013     if ( @where_strings ) {
1014         $query .= 'WHERE ';
1015         $query .= join ' AND ', @where_strings;
1016     }
1017     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1018     my $sth = $dbh->prepare($query);
1019     $sth->execute( @bind_params );
1020
1021     my @results;
1022     $size--;
1023     while ( my $row = $sth->fetchrow_hashref ) {
1024         $offset-- if ($offset);
1025         $row->{datelastseen}=format_date($row->{datelastseen});
1026         if ( ( !$offset ) && $size ) {
1027             push @results, $row;
1028             $size--;
1029         }
1030     }
1031     return \@results;
1032 }
1033
1034 =head2 GetItemsCount
1035
1036   $count = &GetItemsCount( $biblionumber);
1037
1038 This function return count of item with $biblionumber
1039
1040 =cut
1041
1042 sub GetItemsCount {
1043     my ( $biblionumber ) = @_;
1044     my $dbh = C4::Context->dbh;
1045     my $query = "SELECT count(*)
1046           FROM  items 
1047           WHERE biblionumber=?";
1048     my $sth = $dbh->prepare($query);
1049     $sth->execute($biblionumber);
1050     my $count = $sth->fetchrow;  
1051     return ($count);
1052 }
1053
1054 =head2 GetItemInfosOf
1055
1056   GetItemInfosOf(@itemnumbers);
1057
1058 =cut
1059
1060 sub GetItemInfosOf {
1061     my @itemnumbers = @_;
1062
1063     my $query = '
1064         SELECT *
1065         FROM items
1066         WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
1067     ';
1068     return get_infos_of( $query, 'itemnumber' );
1069 }
1070
1071 =head2 GetItemsByBiblioitemnumber
1072
1073   GetItemsByBiblioitemnumber($biblioitemnumber);
1074
1075 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1076 Called by C<C4::XISBN>
1077
1078 =cut
1079
1080 sub GetItemsByBiblioitemnumber {
1081     my ( $bibitem ) = @_;
1082     my $dbh = C4::Context->dbh;
1083     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1084     # Get all items attached to a biblioitem
1085     my $i = 0;
1086     my @results; 
1087     $sth->execute($bibitem) || die $sth->errstr;
1088     while ( my $data = $sth->fetchrow_hashref ) {  
1089         # Foreach item, get circulation information
1090         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1091                                    WHERE itemnumber = ?
1092                                    AND issues.borrowernumber = borrowers.borrowernumber"
1093         );
1094         $sth2->execute( $data->{'itemnumber'} );
1095         if ( my $data2 = $sth2->fetchrow_hashref ) {
1096             # if item is out, set the due date and who it is out too
1097             $data->{'date_due'}   = $data2->{'date_due'};
1098             $data->{'cardnumber'} = $data2->{'cardnumber'};
1099             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
1100         }
1101         else {
1102             # set date_due to blank, so in the template we check itemlost, and wthdrawn 
1103             $data->{'date_due'} = '';                                                                                                         
1104         }    # else         
1105         # Find the last 3 people who borrowed this item.                  
1106         my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1107                       AND old_issues.borrowernumber = borrowers.borrowernumber
1108                       ORDER BY returndate desc,timestamp desc LIMIT 3";
1109         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1110         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1111         my $i2 = 0;
1112         while ( my $data2 = $sth2->fetchrow_hashref ) {
1113             $data->{"timestamp$i2"} = $data2->{'timestamp'};
1114             $data->{"card$i2"}      = $data2->{'cardnumber'};
1115             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
1116             $i2++;
1117         }
1118         push(@results,$data);
1119     } 
1120     return (\@results); 
1121 }
1122
1123 =head2 GetItemsInfo
1124
1125   @results = GetItemsInfo($biblionumber, $type);
1126
1127 Returns information about books with the given biblionumber.
1128
1129 C<$type> may be either C<intra> or anything else. If it is not set to
1130 C<intra>, then the search will exclude lost, very overdue, and
1131 withdrawn items.
1132
1133 C<GetItemsInfo> returns a list of references-to-hash. Each element
1134 contains a number of keys. Most of them are table items from the
1135 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1136 Koha database. Other keys include:
1137
1138 =over 2
1139
1140 =item C<$data-E<gt>{branchname}>
1141
1142 The name (not the code) of the branch to which the book belongs.
1143
1144 =item C<$data-E<gt>{datelastseen}>
1145
1146 This is simply C<items.datelastseen>, except that while the date is
1147 stored in YYYY-MM-DD format in the database, here it is converted to
1148 DD/MM/YYYY format. A NULL date is returned as C<//>.
1149
1150 =item C<$data-E<gt>{datedue}>
1151
1152 =item C<$data-E<gt>{class}>
1153
1154 This is the concatenation of C<biblioitems.classification>, the book's
1155 Dewey code, and C<biblioitems.subclass>.
1156
1157 =item C<$data-E<gt>{ocount}>
1158
1159 I think this is the number of copies of the book available.
1160
1161 =item C<$data-E<gt>{order}>
1162
1163 If this is set, it is set to C<One Order>.
1164
1165 =back
1166
1167 =cut
1168
1169 sub GetItemsInfo {
1170     my ( $biblionumber, $type ) = @_;
1171     my $dbh   = C4::Context->dbh;
1172     # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1173     my $query = "
1174     SELECT items.*,
1175            biblio.*,
1176            biblioitems.volume,
1177            biblioitems.number,
1178            biblioitems.itemtype,
1179            biblioitems.isbn,
1180            biblioitems.issn,
1181            biblioitems.publicationyear,
1182            biblioitems.publishercode,
1183            biblioitems.volumedate,
1184            biblioitems.volumedesc,
1185            biblioitems.lccn,
1186            biblioitems.url,
1187            items.notforloan as itemnotforloan,
1188            itemtypes.description,
1189            itemtypes.notforloan as notforloan_per_itemtype,
1190            branchurl
1191      FROM items
1192      LEFT JOIN branches ON items.homebranch = branches.branchcode
1193      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
1194      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1195      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
1196      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1197     $query .= " WHERE items.biblionumber = ? ORDER BY branches.branchname,items.dateaccessioned desc" ;
1198     my $sth = $dbh->prepare($query);
1199     $sth->execute($biblionumber);
1200     my $i = 0;
1201     my @results;
1202     my $serial;
1203
1204     my $isth    = $dbh->prepare(
1205         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
1206         FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
1207         WHERE  itemnumber = ?"
1208        );
1209         my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? "); 
1210         while ( my $data = $sth->fetchrow_hashref ) {
1211         my $datedue = '';
1212         my $count_reserves;
1213         $isth->execute( $data->{'itemnumber'} );
1214         if ( my $idata = $isth->fetchrow_hashref ) {
1215             $data->{borrowernumber} = $idata->{borrowernumber};
1216             $data->{cardnumber}     = $idata->{cardnumber};
1217             $data->{surname}     = $idata->{surname};
1218             $data->{firstname}     = $idata->{firstname};
1219             $datedue                = $idata->{'date_due'};
1220         if (C4::Context->preference("IndependantBranches")){
1221         my $userenv = C4::Context->userenv;
1222         if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) { 
1223             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1224         }
1225         }
1226         }
1227                 if ( $data->{'serial'}) {       
1228                         $ssth->execute($data->{'itemnumber'}) ;
1229                         ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1230                         $serial = 1;
1231         }
1232                 if ( $datedue eq '' ) {
1233             my ( $restype, $reserves ) =
1234               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
1235 # Previous conditional check with if ($restype) is not needed because a true
1236 # result for one item will result in subsequent items defaulting to this true
1237 # value.
1238             $count_reserves = $restype;
1239         }
1240         #get branch information.....
1241         my $bsth = $dbh->prepare(
1242             "SELECT * FROM branches WHERE branchcode = ?
1243         "
1244         );
1245         $bsth->execute( $data->{'holdingbranch'} );
1246         if ( my $bdata = $bsth->fetchrow_hashref ) {
1247             $data->{'branchname'} = $bdata->{'branchname'};
1248         }
1249         $data->{'datedue'}        = $datedue;
1250         $data->{'count_reserves'} = $count_reserves;
1251
1252         # get notforloan complete status if applicable
1253         my $sthnflstatus = $dbh->prepare(
1254             'SELECT authorised_value
1255             FROM   marc_subfield_structure
1256             WHERE  kohafield="items.notforloan"
1257         '
1258         );
1259
1260         $sthnflstatus->execute;
1261         my ($authorised_valuecode) = $sthnflstatus->fetchrow;
1262         if ($authorised_valuecode) {
1263             $sthnflstatus = $dbh->prepare(
1264                 "SELECT lib FROM authorised_values
1265                  WHERE  category=?
1266                  AND authorised_value=?"
1267             );
1268             $sthnflstatus->execute( $authorised_valuecode,
1269                 $data->{itemnotforloan} );
1270             my ($lib) = $sthnflstatus->fetchrow;
1271             $data->{notforloanvalue} = $lib;
1272         }
1273
1274         # my stack procedures
1275         my $stackstatus = $dbh->prepare(
1276             'SELECT authorised_value
1277              FROM   marc_subfield_structure
1278              WHERE  kohafield="items.stack"
1279         '
1280         );
1281         $stackstatus->execute;
1282
1283         ($authorised_valuecode) = $stackstatus->fetchrow;
1284         if ($authorised_valuecode) {
1285             $stackstatus = $dbh->prepare(
1286                 "SELECT lib
1287                  FROM   authorised_values
1288                  WHERE  category=?
1289                  AND    authorised_value=?
1290             "
1291             );
1292             $stackstatus->execute( $authorised_valuecode, $data->{stack} );
1293             my ($lib) = $stackstatus->fetchrow;
1294             $data->{stack} = $lib;
1295         }
1296         # Find the last 3 people who borrowed this item.
1297         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1298                                     WHERE itemnumber = ?
1299                                     AND old_issues.borrowernumber = borrowers.borrowernumber
1300                                     ORDER BY returndate DESC
1301                                     LIMIT 3");
1302         $sth2->execute($data->{'itemnumber'});
1303         my $ii = 0;
1304         while (my $data2 = $sth2->fetchrow_hashref()) {
1305             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1306             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1307             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1308             $ii++;
1309         }
1310
1311         $results[$i] = $data;
1312         $i++;
1313     }
1314         if($serial) {
1315                 return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
1316         } else {
1317         return (@results);
1318         }
1319 }
1320
1321 =head2 GetLastAcquisitions
1322
1323   my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 
1324                                     'itemtypes' => ('BK','BD')}, 10);
1325
1326 =cut
1327
1328 sub  GetLastAcquisitions {
1329         my ($data,$max) = @_;
1330
1331         my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1332         
1333         my $number_of_branches = @{$data->{branches}};
1334         my $number_of_itemtypes   = @{$data->{itemtypes}};
1335         
1336         
1337         my @where = ('WHERE 1 '); 
1338         $number_of_branches and push @where
1339            , 'AND holdingbranch IN (' 
1340            , join(',', ('?') x $number_of_branches )
1341            , ')'
1342          ;
1343         
1344         $number_of_itemtypes and push @where
1345            , "AND $itemtype IN (" 
1346            , join(',', ('?') x $number_of_itemtypes )
1347            , ')'
1348          ;
1349
1350         my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1351                                  FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber) 
1352                                     RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1353                                     @where
1354                                     GROUP BY biblio.biblionumber 
1355                                     ORDER BY dateaccessioned DESC LIMIT $max";
1356
1357         my $dbh = C4::Context->dbh;
1358         my $sth = $dbh->prepare($query);
1359     
1360     $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1361         
1362         my @results;
1363         while( my $row = $sth->fetchrow_hashref){
1364                 push @results, {date => $row->{dateaccessioned} 
1365                                                 , biblionumber => $row->{biblionumber}
1366                                                 , title => $row->{title}};
1367         }
1368         
1369         return @results;
1370 }
1371
1372 =head2 get_itemnumbers_of
1373
1374   my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1375
1376 Given a list of biblionumbers, return the list of corresponding itemnumbers
1377 for each biblionumber.
1378
1379 Return a reference on a hash where keys are biblionumbers and values are
1380 references on array of itemnumbers.
1381
1382 =cut
1383
1384 sub get_itemnumbers_of {
1385     my @biblionumbers = @_;
1386
1387     my $dbh = C4::Context->dbh;
1388
1389     my $query = '
1390         SELECT itemnumber,
1391             biblionumber
1392         FROM items
1393         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1394     ';
1395     my $sth = $dbh->prepare($query);
1396     $sth->execute(@biblionumbers);
1397
1398     my %itemnumbers_of;
1399
1400     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1401         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1402     }
1403
1404     return \%itemnumbers_of;
1405 }
1406
1407 =head2 GetItemnumberFromBarcode
1408
1409   $result = GetItemnumberFromBarcode($barcode);
1410
1411 =cut
1412
1413 sub GetItemnumberFromBarcode {
1414     my ($barcode) = @_;
1415     my $dbh = C4::Context->dbh;
1416
1417     my $rq =
1418       $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1419     $rq->execute($barcode);
1420     my ($result) = $rq->fetchrow;
1421     return ($result);
1422 }
1423
1424 =head2 GetBarcodeFromItemnumber
1425
1426   $result = GetBarcodeFromItemnumber($itemnumber);
1427
1428 =cut
1429
1430 sub GetBarcodeFromItemnumber {
1431     my ($itemnumber) = @_;
1432     my $dbh = C4::Context->dbh;
1433
1434     my $rq =
1435       $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1436     $rq->execute($itemnumber);
1437     my ($result) = $rq->fetchrow;
1438     return ($result);
1439 }
1440
1441 =head3 get_item_authorised_values
1442
1443 find the types and values for all authorised values assigned to this item.
1444
1445 parameters: itemnumber
1446
1447 returns: a hashref malling the authorised value to the value set for this itemnumber
1448
1449     $authorised_values = {
1450              'CCODE'      => undef,
1451              'DAMAGED'    => '0',
1452              'LOC'        => '3',
1453              'LOST'       => '0'
1454              'NOT_LOAN'   => '0',
1455              'RESTRICTED' => undef,
1456              'STACK'      => undef,
1457              'WITHDRAWN'  => '0',
1458              'branches'   => 'CPL',
1459              'cn_source'  => undef,
1460              'itemtypes'  => 'SER',
1461            };
1462
1463 Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
1464
1465 =cut
1466
1467 sub get_item_authorised_values {
1468     my $itemnumber = shift;
1469
1470     # assume that these entries in the authorised_value table are item level.
1471     my $query = q(SELECT distinct authorised_value, kohafield
1472                     FROM marc_subfield_structure
1473                     WHERE kohafield like 'item%'
1474                       AND authorised_value != '' );
1475
1476     my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
1477     my $iteminfo = GetItem( $itemnumber );
1478     # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
1479     my $return;
1480     foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
1481         my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
1482         $field =~ s/^items\.//;
1483         if ( exists $iteminfo->{ $field } ) {
1484             $return->{ $this_authorised_value } = $iteminfo->{ $field };
1485         }
1486     }
1487     # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
1488     return $return;
1489 }
1490
1491 =head3 get_authorised_value_images
1492
1493 find a list of icons that are appropriate for display based on the
1494 authorised values for a biblio.
1495
1496 parameters: listref of authorised values, such as comes from
1497 get_item_authorised_values or
1498 from C4::Biblio::get_biblio_authorised_values
1499
1500 returns: listref of hashrefs for each image. Each hashref looks like this:
1501
1502       { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
1503         label    => '',
1504         category => '',
1505         value    => '', }
1506
1507 Notes: Currently, I put on the full path to the images on the staff
1508 side. This should either be configurable or not done at all. Since I
1509 have to deal with 'intranet' or 'opac' in
1510 get_biblio_authorised_values, perhaps I should be passing it in.
1511
1512 =cut
1513
1514 sub get_authorised_value_images {
1515     my $authorised_values = shift;
1516
1517     my @imagelist;
1518
1519     my $authorised_value_list = GetAuthorisedValues();
1520     # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
1521     foreach my $this_authorised_value ( @$authorised_value_list ) {
1522         if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
1523              && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
1524             # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
1525             if ( defined $this_authorised_value->{'imageurl'} ) {
1526                 push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
1527                                    label    => $this_authorised_value->{'lib'},
1528                                    category => $this_authorised_value->{'category'},
1529                                    value    => $this_authorised_value->{'authorised_value'}, };
1530             }
1531         }
1532     }
1533
1534     # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
1535     return \@imagelist;
1536
1537 }
1538
1539 =head1 LIMITED USE FUNCTIONS
1540
1541 The following functions, while part of the public API,
1542 are not exported.  This is generally because they are
1543 meant to be used by only one script for a specific
1544 purpose, and should not be used in any other context
1545 without careful thought.
1546
1547 =cut
1548
1549 =head2 GetMarcItem
1550
1551   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1552
1553 Returns MARC::Record of the item passed in parameter.
1554 This function is meant for use only in C<cataloguing/additem.pl>,
1555 where it is needed to support that script's MARC-like
1556 editor.
1557
1558 =cut
1559
1560 sub GetMarcItem {
1561     my ( $biblionumber, $itemnumber ) = @_;
1562
1563     # GetMarcItem has been revised so that it does the following:
1564     #  1. Gets the item information from the items table.
1565     #  2. Converts it to a MARC field for storage in the bib record.
1566     #
1567     # The previous behavior was:
1568     #  1. Get the bib record.
1569     #  2. Return the MARC tag corresponding to the item record.
1570     #
1571     # The difference is that one treats the items row as authoritative,
1572     # while the other treats the MARC representation as authoritative
1573     # under certain circumstances.
1574
1575     my $itemrecord = GetItem($itemnumber);
1576
1577     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1578     # Also, don't emit a subfield if the underlying field is blank.
1579
1580     
1581     return Item2Marc($itemrecord,$biblionumber);
1582
1583 }
1584 sub Item2Marc {
1585         my ($itemrecord,$biblionumber)=@_;
1586     my $mungeditem = { 
1587         map {  
1588             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1589         } keys %{ $itemrecord } 
1590     };
1591     my $itemmarc = TransformKohaToMarc($mungeditem);
1592     my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1593
1594     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1595     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1596                 foreach my $field ($itemmarc->field($itemtag)){
1597             $field->add_subfields(@$unlinked_item_subfields);
1598         }
1599     }
1600         return $itemmarc;
1601 }
1602
1603 =head1 PRIVATE FUNCTIONS AND VARIABLES
1604
1605 The following functions are not meant to be called
1606 directly, but are documented in order to explain
1607 the inner workings of C<C4::Items>.
1608
1609 =cut
1610
1611 =head2 %derived_columns
1612
1613 This hash keeps track of item columns that
1614 are strictly derived from other columns in
1615 the item record and are not meant to be set
1616 independently.
1617
1618 Each key in the hash should be the name of a
1619 column (as named by TransformMarcToKoha).  Each
1620 value should be hashref whose keys are the
1621 columns on which the derived column depends.  The
1622 hashref should also contain a 'BUILDER' key
1623 that is a reference to a sub that calculates
1624 the derived value.
1625
1626 =cut
1627
1628 my %derived_columns = (
1629     'items.cn_sort' => {
1630         'itemcallnumber' => 1,
1631         'items.cn_source' => 1,
1632         'BUILDER' => \&_calc_items_cn_sort,
1633     }
1634 );
1635
1636 =head2 _set_derived_columns_for_add 
1637
1638   _set_derived_column_for_add($item);
1639
1640 Given an item hash representing a new item to be added,
1641 calculate any derived columns.  Currently the only
1642 such column is C<items.cn_sort>.
1643
1644 =cut
1645
1646 sub _set_derived_columns_for_add {
1647     my $item = shift;
1648
1649     foreach my $column (keys %derived_columns) {
1650         my $builder = $derived_columns{$column}->{'BUILDER'};
1651         my $source_values = {};
1652         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1653             next if $source_column eq 'BUILDER';
1654             $source_values->{$source_column} = $item->{$source_column};
1655         }
1656         $builder->($item, $source_values);
1657     }
1658 }
1659
1660 =head2 _set_derived_columns_for_mod 
1661
1662   _set_derived_column_for_mod($item);
1663
1664 Given an item hash representing a new item to be modified.
1665 calculate any derived columns.  Currently the only
1666 such column is C<items.cn_sort>.
1667
1668 This routine differs from C<_set_derived_columns_for_add>
1669 in that it needs to handle partial item records.  In other
1670 words, the caller of C<ModItem> may have supplied only one
1671 or two columns to be changed, so this function needs to
1672 determine whether any of the columns to be changed affect
1673 any of the derived columns.  Also, if a derived column
1674 depends on more than one column, but the caller is not
1675 changing all of then, this routine retrieves the unchanged
1676 values from the database in order to ensure a correct
1677 calculation.
1678
1679 =cut
1680
1681 sub _set_derived_columns_for_mod {
1682     my $item = shift;
1683
1684     foreach my $column (keys %derived_columns) {
1685         my $builder = $derived_columns{$column}->{'BUILDER'};
1686         my $source_values = {};
1687         my %missing_sources = ();
1688         my $must_recalc = 0;
1689         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1690             next if $source_column eq 'BUILDER';
1691             if (exists $item->{$source_column}) {
1692                 $must_recalc = 1;
1693                 $source_values->{$source_column} = $item->{$source_column};
1694             } else {
1695                 $missing_sources{$source_column} = 1;
1696             }
1697         }
1698         if ($must_recalc) {
1699             foreach my $source_column (keys %missing_sources) {
1700                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1701             }
1702             $builder->($item, $source_values);
1703         }
1704     }
1705 }
1706
1707 =head2 _do_column_fixes_for_mod
1708
1709   _do_column_fixes_for_mod($item);
1710
1711 Given an item hashref containing one or more
1712 columns to modify, fix up certain values.
1713 Specifically, set to 0 any passed value
1714 of C<notforloan>, C<damaged>, C<itemlost>, or
1715 C<wthdrawn> that is either undefined or
1716 contains the empty string.
1717
1718 =cut
1719
1720 sub _do_column_fixes_for_mod {
1721     my $item = shift;
1722
1723     if (exists $item->{'notforloan'} and
1724         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1725         $item->{'notforloan'} = 0;
1726     }
1727     if (exists $item->{'damaged'} and
1728         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1729         $item->{'damaged'} = 0;
1730     }
1731     if (exists $item->{'itemlost'} and
1732         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1733         $item->{'itemlost'} = 0;
1734     }
1735     if (exists $item->{'wthdrawn'} and
1736         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1737         $item->{'wthdrawn'} = 0;
1738     }
1739     if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
1740         $item->{'permanent_location'} = $item->{'location'};
1741     }
1742 }
1743
1744 =head2 _get_single_item_column
1745
1746   _get_single_item_column($column, $itemnumber);
1747
1748 Retrieves the value of a single column from an C<items>
1749 row specified by C<$itemnumber>.
1750
1751 =cut
1752
1753 sub _get_single_item_column {
1754     my $column = shift;
1755     my $itemnumber = shift;
1756     
1757     my $dbh = C4::Context->dbh;
1758     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1759     $sth->execute($itemnumber);
1760     my ($value) = $sth->fetchrow();
1761     return $value; 
1762 }
1763
1764 =head2 _calc_items_cn_sort
1765
1766   _calc_items_cn_sort($item, $source_values);
1767
1768 Helper routine to calculate C<items.cn_sort>.
1769
1770 =cut
1771
1772 sub _calc_items_cn_sort {
1773     my $item = shift;
1774     my $source_values = shift;
1775
1776     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1777 }
1778
1779 =head2 _set_defaults_for_add 
1780
1781   _set_defaults_for_add($item_hash);
1782
1783 Given an item hash representing an item to be added, set
1784 correct default values for columns whose default value
1785 is not handled by the DBMS.  This includes the following
1786 columns:
1787
1788 =over 2
1789
1790 =item * 
1791
1792 C<items.dateaccessioned>
1793
1794 =item *
1795
1796 C<items.notforloan>
1797
1798 =item *
1799
1800 C<items.damaged>
1801
1802 =item *
1803
1804 C<items.itemlost>
1805
1806 =item *
1807
1808 C<items.wthdrawn>
1809
1810 =back
1811
1812 =cut
1813
1814 sub _set_defaults_for_add {
1815     my $item = shift;
1816     $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
1817     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
1818 }
1819
1820 =head2 _koha_new_item
1821
1822   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1823
1824 Perform the actual insert into the C<items> table.
1825
1826 =cut
1827
1828 sub _koha_new_item {
1829     my ( $item, $barcode ) = @_;
1830     my $dbh=C4::Context->dbh;  
1831     my $error;
1832     my $query =
1833            "INSERT INTO items SET
1834             biblionumber        = ?,
1835             biblioitemnumber    = ?,
1836             barcode             = ?,
1837             dateaccessioned     = ?,
1838             booksellerid        = ?,
1839             homebranch          = ?,
1840             price               = ?,
1841             replacementprice    = ?,
1842             replacementpricedate = NOW(),
1843             datelastborrowed    = ?,
1844             datelastseen        = NOW(),
1845             stack               = ?,
1846             notforloan          = ?,
1847             damaged             = ?,
1848             itemlost            = ?,
1849             wthdrawn            = ?,
1850             itemcallnumber      = ?,
1851             restricted          = ?,
1852             itemnotes           = ?,
1853             holdingbranch       = ?,
1854             paidfor             = ?,
1855             location            = ?,
1856             onloan              = ?,
1857             issues              = ?,
1858             renewals            = ?,
1859             reserves            = ?,
1860             cn_source           = ?,
1861             cn_sort             = ?,
1862             ccode               = ?,
1863             itype               = ?,
1864             materials           = ?,
1865             uri = ?,
1866             enumchron           = ?,
1867             more_subfields_xml  = ?,
1868             copynumber          = ?
1869           ";
1870     my $sth = $dbh->prepare($query);
1871    $sth->execute(
1872             $item->{'biblionumber'},
1873             $item->{'biblioitemnumber'},
1874             $barcode,
1875             $item->{'dateaccessioned'},
1876             $item->{'booksellerid'},
1877             $item->{'homebranch'},
1878             $item->{'price'},
1879             $item->{'replacementprice'},
1880             $item->{datelastborrowed},
1881             $item->{stack},
1882             $item->{'notforloan'},
1883             $item->{'damaged'},
1884             $item->{'itemlost'},
1885             $item->{'wthdrawn'},
1886             $item->{'itemcallnumber'},
1887             $item->{'restricted'},
1888             $item->{'itemnotes'},
1889             $item->{'holdingbranch'},
1890             $item->{'paidfor'},
1891             $item->{'location'},
1892             $item->{'onloan'},
1893             $item->{'issues'},
1894             $item->{'renewals'},
1895             $item->{'reserves'},
1896             $item->{'items.cn_source'},
1897             $item->{'items.cn_sort'},
1898             $item->{'ccode'},
1899             $item->{'itype'},
1900             $item->{'materials'},
1901             $item->{'uri'},
1902             $item->{'enumchron'},
1903             $item->{'more_subfields_xml'},
1904             $item->{'copynumber'},
1905     );
1906     my $itemnumber = $dbh->{'mysql_insertid'};
1907     if ( defined $sth->errstr ) {
1908         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1909     }
1910     return ( $itemnumber, $error );
1911 }
1912
1913 =head2 MoveItemFromBiblio
1914
1915   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1916
1917 Moves an item from a biblio to another
1918
1919 Returns undef if the move failed or the biblionumber of the destination record otherwise
1920
1921 =cut
1922
1923 sub MoveItemFromBiblio {
1924     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1925     my $dbh = C4::Context->dbh;
1926     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
1927     $sth->execute( $tobiblio );
1928     my ( $tobiblioitem ) = $sth->fetchrow();
1929     $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
1930     my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
1931     if ($return == 1) {
1932
1933         # Getting framework
1934         my $frameworkcode = GetFrameworkCode($frombiblio);
1935
1936         # Getting marc field for itemnumber
1937         my ($itemtag, $itemsubfield) = GetMarcFromKohaField('items.itemnumber', $frameworkcode);
1938
1939         # Getting the record we want to move the item from
1940         my $record = GetMarcBiblio($frombiblio);
1941
1942         # The item we want to move
1943         my $item;
1944
1945         # For each item
1946         foreach my $fielditem ($record->field($itemtag)){
1947                 # If it is the item we want to move
1948                 if ($fielditem->subfield($itemsubfield) == $itemnumber) {
1949                     # We save it
1950                     $item = $fielditem;
1951                     # Then delete it from the record
1952                     $record->delete_field($fielditem) 
1953                 }
1954         }
1955
1956         # If we found an item (should always true, except in case of database-marcxml inconsistency)
1957         if ($item) {
1958
1959             # Checking if the item we want to move is in an order 
1960             my $order = GetOrderFromItemnumber($itemnumber);
1961             if ($order) {
1962                 # Replacing the biblionumber within the order if necessary
1963                 $order->{'biblionumber'} = $tobiblio;
1964                 ModOrder($order);
1965             }
1966
1967             # Saving the modification
1968             ModBiblioMarc($record, $frombiblio, $frameworkcode);
1969
1970             # Getting the record we want to move the item to
1971             $record = GetMarcBiblio($tobiblio);
1972
1973             # Inserting the previously saved item
1974             $record->insert_fields_ordered($item);      
1975
1976             # Saving the modification
1977             ModBiblioMarc($record, $tobiblio, $frameworkcode);
1978
1979         } else {
1980             return undef;
1981         }
1982     } else {
1983         return undef;
1984     }
1985 }
1986
1987 =head2 DelItemCheck
1988
1989    DelItemCheck($dbh, $biblionumber, $itemnumber);
1990
1991 Exported function (core API) for deleting an item record in Koha if there no current issue.
1992
1993 =cut
1994
1995 sub DelItemCheck {
1996     my ( $dbh, $biblionumber, $itemnumber ) = @_;
1997     my $error;
1998
1999     # check that there is no issue on this item before deletion.
2000     my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
2001     $sth->execute($itemnumber);
2002
2003     my $onloan=$sth->fetchrow;
2004
2005     if ($onloan){
2006         $error = "book_on_loan" 
2007     }else{
2008         # check it doesnt have a waiting reserve
2009         $sth=$dbh->prepare("SELECT * FROM reserves WHERE found = 'W' AND itemnumber = ?");
2010         $sth->execute($itemnumber);
2011         my $reserve=$sth->fetchrow;
2012         if ($reserve){
2013             $error = "book_reserved";
2014         }else{
2015             DelItem($dbh, $biblionumber, $itemnumber);
2016             return 1;
2017         }
2018     }
2019     return $error;
2020 }
2021
2022 =head2 _koha_modify_item
2023
2024   my ($itemnumber,$error) =_koha_modify_item( $item );
2025
2026 Perform the actual update of the C<items> row.  Note that this
2027 routine accepts a hashref specifying the columns to update.
2028
2029 =cut
2030
2031 sub _koha_modify_item {
2032     my ( $item ) = @_;
2033     my $dbh=C4::Context->dbh;  
2034     my $error;
2035
2036     my $query = "UPDATE items SET ";
2037     my @bind;
2038     for my $key ( keys %$item ) {
2039         $query.="$key=?,";
2040         push @bind, $item->{$key};
2041     }
2042     $query =~ s/,$//;
2043     $query .= " WHERE itemnumber=?";
2044     push @bind, $item->{'itemnumber'};
2045     my $sth = C4::Context->dbh->prepare($query);
2046     $sth->execute(@bind);
2047     if ( C4::Context->dbh->errstr ) {
2048         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
2049         warn $error;
2050     }
2051     return ($item->{'itemnumber'},$error);
2052 }
2053
2054 =head2 _koha_delete_item
2055
2056   _koha_delete_item( $dbh, $itemnum );
2057
2058 Internal function to delete an item record from the koha tables
2059
2060 =cut
2061
2062 sub _koha_delete_item {
2063     my ( $dbh, $itemnum ) = @_;
2064
2065     # save the deleted item to deleteditems table
2066     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2067     $sth->execute($itemnum);
2068     my $data = $sth->fetchrow_hashref();
2069     my $query = "INSERT INTO deleteditems SET ";
2070     my @bind  = ();
2071     foreach my $key ( keys %$data ) {
2072         $query .= "$key = ?,";
2073         push( @bind, $data->{$key} );
2074     }
2075     $query =~ s/\,$//;
2076     $sth = $dbh->prepare($query);
2077     $sth->execute(@bind);
2078
2079     # delete from items table
2080     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2081     $sth->execute($itemnum);
2082     return undef;
2083 }
2084
2085 =head2 _marc_from_item_hash
2086
2087   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2088
2089 Given an item hash representing a complete item record,
2090 create a C<MARC::Record> object containing an embedded
2091 tag representing that item.
2092
2093 The third, optional parameter C<$unlinked_item_subfields> is
2094 an arrayref of subfields (not mapped to C<items> fields per the
2095 framework) to be added to the MARC representation
2096 of the item.
2097
2098 =cut
2099
2100 sub _marc_from_item_hash {
2101     my $item = shift;
2102     my $frameworkcode = shift;
2103     my $unlinked_item_subfields;
2104     if (@_) {
2105         $unlinked_item_subfields = shift;
2106     }
2107    
2108     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2109     # Also, don't emit a subfield if the underlying field is blank.
2110     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2111                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2112                                 : ()  } keys %{ $item } }; 
2113
2114     my $item_marc = MARC::Record->new();
2115     foreach my $item_field (keys %{ $mungeditem }) {
2116         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
2117         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2118         if (my $field = $item_marc->field($tag)) {
2119             $field->add_subfields($subfield => $mungeditem->{$item_field});
2120         } else {
2121             my $add_subfields = [];
2122             if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2123                 $add_subfields = $unlinked_item_subfields;
2124             }
2125             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field}, @$add_subfields);
2126         }
2127     }
2128
2129     return $item_marc;
2130 }
2131
2132 =head2 _add_item_field_to_biblio
2133
2134   _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
2135
2136 Adds the fields from a MARC record containing the
2137 representation of a Koha item record to the MARC
2138 biblio record.  The input C<$item_marc> record
2139 is expect to contain just one field, the embedded
2140 item information field.
2141
2142 =cut
2143
2144 sub _add_item_field_to_biblio {
2145     my ($item_marc, $biblionumber, $frameworkcode) = @_;
2146
2147     my $biblio_marc = GetMarcBiblio($biblionumber);
2148     foreach my $field ($item_marc->fields()) {
2149         $biblio_marc->append_fields($field);
2150     }
2151
2152     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
2153 }
2154
2155 =head2 _replace_item_field_in_biblio
2156
2157   &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
2158
2159 Given a MARC::Record C<$item_marc> containing one tag with the MARC 
2160 representation of the item, examine the biblio MARC
2161 for the corresponding tag for that item and 
2162 replace it with the tag from C<$item_marc>.
2163
2164 =cut
2165
2166 sub _replace_item_field_in_biblio {
2167     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
2168     my $dbh = C4::Context->dbh;
2169     
2170     # get complete MARC record & replace the item field by the new one
2171     my $completeRecord = GetMarcBiblio($biblionumber);
2172     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
2173     my $itemField = $ItemRecord->field($itemtag);
2174     my @items = $completeRecord->field($itemtag);
2175     my $found = 0;
2176     foreach (@items) {
2177         if ($_->subfield($itemsubfield) eq $itemnumber) {
2178             $_->replace_with($itemField);
2179             $found = 1;
2180         }
2181     }
2182   
2183     unless ($found) { 
2184         # If we haven't found the matching field,
2185         # just add it.  However, this means that
2186         # there is likely a bug.
2187         $completeRecord->append_fields($itemField);
2188     }
2189
2190     # save the record
2191     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
2192 }
2193
2194 =head2 _repack_item_errors
2195
2196 Add an error message hash generated by C<CheckItemPreSave>
2197 to a list of errors.
2198
2199 =cut
2200
2201 sub _repack_item_errors {
2202     my $item_sequence_num = shift;
2203     my $item_ref = shift;
2204     my $error_ref = shift;
2205
2206     my @repacked_errors = ();
2207
2208     foreach my $error_code (sort keys %{ $error_ref }) {
2209         my $repacked_error = {};
2210         $repacked_error->{'item_sequence'} = $item_sequence_num;
2211         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2212         $repacked_error->{'error_code'} = $error_code;
2213         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2214         push @repacked_errors, $repacked_error;
2215     } 
2216
2217     return @repacked_errors;
2218 }
2219
2220 =head2 _get_unlinked_item_subfields
2221
2222   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2223
2224 =cut
2225
2226 sub _get_unlinked_item_subfields {
2227     my $original_item_marc = shift;
2228     my $frameworkcode = shift;
2229
2230     my $marcstructure = GetMarcStructure(1, $frameworkcode);
2231
2232     # assume that this record has only one field, and that that
2233     # field contains only the item information
2234     my $subfields = [];
2235     my @fields = $original_item_marc->fields();
2236     if ($#fields > -1) {
2237         my $field = $fields[0];
2238             my $tag = $field->tag();
2239         foreach my $subfield ($field->subfields()) {
2240             if (defined $subfield->[1] and
2241                 $subfield->[1] ne '' and
2242                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2243                 push @$subfields, $subfield->[0] => $subfield->[1];
2244             }
2245         }
2246     }
2247     return $subfields;
2248 }
2249
2250 =head2 _get_unlinked_subfields_xml
2251
2252   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2253
2254 =cut
2255
2256 sub _get_unlinked_subfields_xml {
2257     my $unlinked_item_subfields = shift;
2258
2259     my $xml;
2260     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2261         my $marc = MARC::Record->new();
2262         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2263         # used in the framework
2264         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2265         $marc->encoding("UTF-8");    
2266         $xml = $marc->as_xml("USMARC");
2267     }
2268
2269     return $xml;
2270 }
2271
2272 =head2 _parse_unlinked_item_subfields_from_xml
2273
2274   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2275
2276 =cut
2277
2278 sub  _parse_unlinked_item_subfields_from_xml {
2279     my $xml = shift;
2280
2281     return unless defined $xml and $xml ne "";
2282     my $marc = MARC::Record->new_from_xml(StripNonXmlChars($xml),'UTF-8');
2283     my $unlinked_subfields = [];
2284     my @fields = $marc->fields();
2285     if ($#fields > -1) {
2286         foreach my $subfield ($fields[0]->subfields()) {
2287             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2288         }
2289     }
2290     return $unlinked_subfields;
2291 }
2292
2293 1;