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