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