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