[2/40] Work on C4::Labels::Template and associated tests
[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     if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
1763         $item->{'permanent_location'} = $item->{'location'};
1764     }
1765 }
1766
1767 =head2 _get_single_item_column
1768
1769 =over 4
1770
1771 _get_single_item_column($column, $itemnumber);
1772
1773 =back
1774
1775 Retrieves the value of a single column from an C<items>
1776 row specified by C<$itemnumber>.
1777
1778 =cut
1779
1780 sub _get_single_item_column {
1781     my $column = shift;
1782     my $itemnumber = shift;
1783     
1784     my $dbh = C4::Context->dbh;
1785     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1786     $sth->execute($itemnumber);
1787     my ($value) = $sth->fetchrow();
1788     return $value; 
1789 }
1790
1791 =head2 _calc_items_cn_sort
1792
1793 =over 4
1794
1795 _calc_items_cn_sort($item, $source_values);
1796
1797 =back
1798
1799 Helper routine to calculate C<items.cn_sort>.
1800
1801 =cut
1802
1803 sub _calc_items_cn_sort {
1804     my $item = shift;
1805     my $source_values = shift;
1806
1807     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1808 }
1809
1810 =head2 _set_defaults_for_add 
1811
1812 =over 4
1813
1814 _set_defaults_for_add($item_hash);
1815
1816 =back
1817
1818 Given an item hash representing an item to be added, set
1819 correct default values for columns whose default value
1820 is not handled by the DBMS.  This includes the following
1821 columns:
1822
1823 =over 2
1824
1825 =item * 
1826
1827 C<items.dateaccessioned>
1828
1829 =item *
1830
1831 C<items.notforloan>
1832
1833 =item *
1834
1835 C<items.damaged>
1836
1837 =item *
1838
1839 C<items.itemlost>
1840
1841 =item *
1842
1843 C<items.wthdrawn>
1844
1845 =back
1846
1847 =cut
1848
1849 sub _set_defaults_for_add {
1850     my $item = shift;
1851     $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
1852     $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
1853 }
1854
1855 =head2 _koha_new_item
1856
1857 =over 4
1858
1859 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1860
1861 =back
1862
1863 Perform the actual insert into the C<items> table.
1864
1865 =cut
1866
1867 sub _koha_new_item {
1868     my ( $item, $barcode ) = @_;
1869     my $dbh=C4::Context->dbh;  
1870     my $error;
1871     my $query =
1872            "INSERT INTO items SET
1873             biblionumber        = ?,
1874             biblioitemnumber    = ?,
1875             barcode             = ?,
1876             dateaccessioned     = ?,
1877             booksellerid        = ?,
1878             homebranch          = ?,
1879             price               = ?,
1880             replacementprice    = ?,
1881             replacementpricedate = NOW(),
1882             datelastborrowed    = ?,
1883             datelastseen        = NOW(),
1884             stack               = ?,
1885             notforloan          = ?,
1886             damaged             = ?,
1887             itemlost            = ?,
1888             wthdrawn            = ?,
1889             itemcallnumber      = ?,
1890             restricted          = ?,
1891             itemnotes           = ?,
1892             holdingbranch       = ?,
1893             paidfor             = ?,
1894             location            = ?,
1895             onloan              = ?,
1896             issues              = ?,
1897             renewals            = ?,
1898             reserves            = ?,
1899             cn_source           = ?,
1900             cn_sort             = ?,
1901             ccode               = ?,
1902             itype               = ?,
1903             materials           = ?,
1904             uri = ?,
1905             enumchron           = ?,
1906             more_subfields_xml  = ?,
1907             copynumber          = ?
1908           ";
1909     my $sth = $dbh->prepare($query);
1910    $sth->execute(
1911             $item->{'biblionumber'},
1912             $item->{'biblioitemnumber'},
1913             $barcode,
1914             $item->{'dateaccessioned'},
1915             $item->{'booksellerid'},
1916             $item->{'homebranch'},
1917             $item->{'price'},
1918             $item->{'replacementprice'},
1919             $item->{datelastborrowed},
1920             $item->{stack},
1921             $item->{'notforloan'},
1922             $item->{'damaged'},
1923             $item->{'itemlost'},
1924             $item->{'wthdrawn'},
1925             $item->{'itemcallnumber'},
1926             $item->{'restricted'},
1927             $item->{'itemnotes'},
1928             $item->{'holdingbranch'},
1929             $item->{'paidfor'},
1930             $item->{'location'},
1931             $item->{'onloan'},
1932             $item->{'issues'},
1933             $item->{'renewals'},
1934             $item->{'reserves'},
1935             $item->{'items.cn_source'},
1936             $item->{'items.cn_sort'},
1937             $item->{'ccode'},
1938             $item->{'itype'},
1939             $item->{'materials'},
1940             $item->{'uri'},
1941             $item->{'enumchron'},
1942             $item->{'more_subfields_xml'},
1943             $item->{'copynumber'},
1944     );
1945     my $itemnumber = $dbh->{'mysql_insertid'};
1946     if ( defined $sth->errstr ) {
1947         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1948     }
1949     $sth->finish();
1950     return ( $itemnumber, $error );
1951 }
1952
1953 =head2 _koha_modify_item
1954
1955 =over 4
1956
1957 my ($itemnumber,$error) =_koha_modify_item( $item );
1958
1959 =back
1960
1961 Perform the actual update of the C<items> row.  Note that this
1962 routine accepts a hashref specifying the columns to update.
1963
1964 =cut
1965
1966 sub _koha_modify_item {
1967     my ( $item ) = @_;
1968     my $dbh=C4::Context->dbh;  
1969     my $error;
1970
1971     my $query = "UPDATE items SET ";
1972     my @bind;
1973     for my $key ( keys %$item ) {
1974         $query.="$key=?,";
1975         push @bind, $item->{$key};
1976     }
1977     $query =~ s/,$//;
1978     $query .= " WHERE itemnumber=?";
1979     push @bind, $item->{'itemnumber'};
1980     my $sth = C4::Context->dbh->prepare($query);
1981     $sth->execute(@bind);
1982     if ( C4::Context->dbh->errstr ) {
1983         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
1984         warn $error;
1985     }
1986     $sth->finish();
1987     return ($item->{'itemnumber'},$error);
1988 }
1989
1990 =head2 _koha_delete_item
1991
1992 =over 4
1993
1994 _koha_delete_item( $dbh, $itemnum );
1995
1996 =back
1997
1998 Internal function to delete an item record from the koha tables
1999
2000 =cut
2001
2002 sub _koha_delete_item {
2003     my ( $dbh, $itemnum ) = @_;
2004
2005     # save the deleted item to deleteditems table
2006     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2007     $sth->execute($itemnum);
2008     my $data = $sth->fetchrow_hashref();
2009     $sth->finish();
2010     my $query = "INSERT INTO deleteditems SET ";
2011     my @bind  = ();
2012     foreach my $key ( keys %$data ) {
2013         $query .= "$key = ?,";
2014         push( @bind, $data->{$key} );
2015     }
2016     $query =~ s/\,$//;
2017     $sth = $dbh->prepare($query);
2018     $sth->execute(@bind);
2019     $sth->finish();
2020
2021     # delete from items table
2022     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2023     $sth->execute($itemnum);
2024     $sth->finish();
2025     return undef;
2026 }
2027
2028 =head2 _marc_from_item_hash
2029
2030 =over 4
2031
2032 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2033
2034 =back
2035
2036 Given an item hash representing a complete item record,
2037 create a C<MARC::Record> object containing an embedded
2038 tag representing that item.
2039
2040 The third, optional parameter C<$unlinked_item_subfields> is
2041 an arrayref of subfields (not mapped to C<items> fields per the
2042 framework) to be added to the MARC representation
2043 of the item.
2044
2045 =cut
2046
2047 sub _marc_from_item_hash {
2048     my $item = shift;
2049     my $frameworkcode = shift;
2050     my $unlinked_item_subfields;
2051     if (@_) {
2052         $unlinked_item_subfields = shift;
2053     }
2054    
2055     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2056     # Also, don't emit a subfield if the underlying field is blank.
2057     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
2058                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
2059                                 : ()  } keys %{ $item } }; 
2060
2061     my $item_marc = MARC::Record->new();
2062     foreach my $item_field (keys %{ $mungeditem }) {
2063         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
2064         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2065         if (my $field = $item_marc->field($tag)) {
2066             $field->add_subfields($subfield => $mungeditem->{$item_field});
2067         } else {
2068             my $add_subfields = [];
2069             if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2070                 $add_subfields = $unlinked_item_subfields;
2071             }
2072             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field}, @$add_subfields);
2073         }
2074     }
2075
2076     return $item_marc;
2077 }
2078
2079 =head2 _add_item_field_to_biblio
2080
2081 =over 4
2082
2083 _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
2084
2085 =back
2086
2087 Adds the fields from a MARC record containing the
2088 representation of a Koha item record to the MARC
2089 biblio record.  The input C<$item_marc> record
2090 is expect to contain just one field, the embedded
2091 item information field.
2092
2093 =cut
2094
2095 sub _add_item_field_to_biblio {
2096     my ($item_marc, $biblionumber, $frameworkcode) = @_;
2097
2098     my $biblio_marc = GetMarcBiblio($biblionumber);
2099     foreach my $field ($item_marc->fields()) {
2100         $biblio_marc->append_fields($field);
2101     }
2102
2103     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
2104 }
2105
2106 =head2 _replace_item_field_in_biblio
2107
2108 =over
2109
2110 &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
2111
2112 =back
2113
2114 Given a MARC::Record C<$item_marc> containing one tag with the MARC 
2115 representation of the item, examine the biblio MARC
2116 for the corresponding tag for that item and 
2117 replace it with the tag from C<$item_marc>.
2118
2119 =cut
2120
2121 sub _replace_item_field_in_biblio {
2122     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
2123     my $dbh = C4::Context->dbh;
2124     
2125     # get complete MARC record & replace the item field by the new one
2126     my $completeRecord = GetMarcBiblio($biblionumber);
2127     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
2128     my $itemField = $ItemRecord->field($itemtag);
2129     my @items = $completeRecord->field($itemtag);
2130     my $found = 0;
2131     foreach (@items) {
2132         if ($_->subfield($itemsubfield) eq $itemnumber) {
2133             $_->replace_with($itemField);
2134             $found = 1;
2135         }
2136     }
2137   
2138     unless ($found) { 
2139         # If we haven't found the matching field,
2140         # just add it.  However, this means that
2141         # there is likely a bug.
2142         $completeRecord->append_fields($itemField);
2143     }
2144
2145     # save the record
2146     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
2147 }
2148
2149 =head2 _repack_item_errors
2150
2151 Add an error message hash generated by C<CheckItemPreSave>
2152 to a list of errors.
2153
2154 =cut
2155
2156 sub _repack_item_errors {
2157     my $item_sequence_num = shift;
2158     my $item_ref = shift;
2159     my $error_ref = shift;
2160
2161     my @repacked_errors = ();
2162
2163     foreach my $error_code (sort keys %{ $error_ref }) {
2164         my $repacked_error = {};
2165         $repacked_error->{'item_sequence'} = $item_sequence_num;
2166         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2167         $repacked_error->{'error_code'} = $error_code;
2168         $repacked_error->{'error_information'} = $error_ref->{$error_code};
2169         push @repacked_errors, $repacked_error;
2170     } 
2171
2172     return @repacked_errors;
2173 }
2174
2175 =head2 _get_unlinked_item_subfields
2176
2177 =over 4
2178
2179 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2180
2181 =back
2182
2183 =cut
2184
2185 sub _get_unlinked_item_subfields {
2186     my $original_item_marc = shift;
2187     my $frameworkcode = shift;
2188
2189     my $marcstructure = GetMarcStructure(1, $frameworkcode);
2190
2191     # assume that this record has only one field, and that that
2192     # field contains only the item information
2193     my $subfields = [];
2194     my @fields = $original_item_marc->fields();
2195     if ($#fields > -1) {
2196         my $field = $fields[0];
2197             my $tag = $field->tag();
2198         foreach my $subfield ($field->subfields()) {
2199             if (defined $subfield->[1] and
2200                 $subfield->[1] ne '' and
2201                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2202                 push @$subfields, $subfield->[0] => $subfield->[1];
2203             }
2204         }
2205     }
2206     return $subfields;
2207 }
2208
2209 =head2 _get_unlinked_subfields_xml
2210
2211 =over 4
2212
2213 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2214
2215 =back
2216
2217 =cut
2218
2219 sub _get_unlinked_subfields_xml {
2220     my $unlinked_item_subfields = shift;
2221
2222     my $xml;
2223     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2224         my $marc = MARC::Record->new();
2225         # use of tag 999 is arbitrary, and doesn't need to match the item tag
2226         # used in the framework
2227         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2228         $marc->encoding("UTF-8");    
2229         $xml = $marc->as_xml("USMARC");
2230     }
2231
2232     return $xml;
2233 }
2234
2235 =head2 _parse_unlinked_item_subfields_from_xml
2236
2237 =over 4
2238
2239 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2240
2241 =back
2242
2243 =cut
2244
2245 sub  _parse_unlinked_item_subfields_from_xml {
2246     my $xml = shift;
2247
2248     return unless defined $xml and $xml ne "";
2249     my $marc = MARC::Record->new_from_xml(StripNonXmlChars($xml),'UTF-8');
2250     my $unlinked_subfields = [];
2251     my @fields = $marc->fields();
2252     if ($#fields > -1) {
2253         foreach my $subfield ($fields[0]->subfields()) {
2254             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2255         }
2256     }
2257     return $unlinked_subfields;
2258 }
2259
2260 1;