item rework: moved various accessor functions
[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 require Exporter;
23
24 use C4::Context;
25 use C4::Koha;
26 use C4::Biblio;
27 use C4::Dates qw/format_date format_date_in_iso/;
28 use MARC::Record;
29 use C4::ClassSource;
30 use C4::Log;
31 use C4::Reserves;
32
33 use vars qw($VERSION @ISA @EXPORT);
34
35 my $VERSION = 3.00;
36
37 @ISA = qw( Exporter );
38
39 # function exports
40 @EXPORT = qw(
41     GetItem
42     AddItemFromMarc
43     AddItem
44     ModItemFromMarc
45     ModItem
46     ModDateLastSeen
47     ModItemTransfer
48
49     GetItemStatus
50     GetItemLocation
51     GetLostItems
52     GetItemsForInventory
53     GetItemsCount
54     GetItemInfosOf
55     GetItemsByBiblioitemnumber
56     GetItemsInfo
57     get_itemnumbers_of
58 );
59
60 =head1 NAME
61
62 C4::Items - item management functions
63
64 =head1 DESCRIPTION
65
66 This module contains an API for manipulating item 
67 records in Koha, and is used by cataloguing, circulation,
68 acquisitions, and serials management.
69
70 A Koha item record is stored in two places: the
71 items table and embedded in a MARC tag in the XML
72 version of the associated bib record in C<biblioitems.marcxml>.
73 This is done to allow the item information to be readily
74 indexed (e.g., by Zebra), but means that each item
75 modification transaction must keep the items table
76 and the MARC XML in sync at all times.
77
78 Consequently, all code that creates, modifies, or deletes
79 item records B<must> use an appropriate function from 
80 C<C4::Items>.  If no existing function is suitable, it is
81 better to add one to C<C4::Items> than to use add
82 one-off SQL statements to add or modify items.
83
84 The items table will be considered authoritative.  In other
85 words, if there is ever a discrepancy between the items
86 table and the MARC XML, the items table should be considered
87 accurate.
88
89 =head1 HISTORICAL NOTE
90
91 Most of the functions in C<C4::Items> were originally in
92 the C<C4::Biblio> module.
93
94 =head1 CORE EXPORTED FUNCTIONS
95
96 The following functions are meant for use by users
97 of C<C4::Items>
98
99 =cut
100
101 =head2 GetItem
102
103 =over 4
104
105 $item = GetItem($itemnumber,$barcode);
106
107 =back
108
109 Return item information, for a given itemnumber or barcode.
110 The return value is a hashref mapping item column
111 names to values.
112
113 =cut
114
115 sub GetItem {
116     my ($itemnumber,$barcode) = @_;
117     my $dbh = C4::Context->dbh;
118     if ($itemnumber) {
119         my $sth = $dbh->prepare("
120             SELECT * FROM items 
121             WHERE itemnumber = ?");
122         $sth->execute($itemnumber);
123         my $data = $sth->fetchrow_hashref;
124         return $data;
125     } else {
126         my $sth = $dbh->prepare("
127             SELECT * FROM items 
128             WHERE barcode = ?"
129             );
130         $sth->execute($barcode);
131         my $data = $sth->fetchrow_hashref;
132         return $data;
133     }
134 }    # sub GetItem
135
136 =head2 AddItemFromMarc
137
138 =over 4
139
140 my ($biblionumber, $biblioitemnumber, $itemnumber) 
141     = AddItemFromMarc($source_item_marc, $biblionumber);
142
143 =back
144
145 Given a MARC::Record object containing an embedded item
146 record and a biblionumber, create a new item record.
147
148 =cut
149
150 sub AddItemFromMarc {
151     my ( $source_item_marc, $biblionumber ) = @_;
152     my $dbh = C4::Context->dbh;
153
154     # parse item hash from MARC
155     my $frameworkcode = GetFrameworkCode( $biblionumber );
156     my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
157
158     return AddItem($item, $biblionumber, $dbh, $frameworkcode);
159 }
160
161 =head2 AddItem
162
163 =over 4
164
165 my ($biblionumber, $biblioitemnumber, $itemnumber) 
166     = AddItem($item, $biblionumber[, $dbh, $frameworkcode]);
167
168 =back
169
170 Given a hash containing item column names as keys,
171 create a new Koha item record.
172
173 The two optional parameters (C<$dbh> and C<$frameworkcode>)
174 do not need to be supplied for general use; they exist
175 simply to allow them to be picked up from AddItemFromMarc.
176
177 =cut
178
179 sub AddItem {
180     my $item = shift;
181     my $biblionumber = shift;
182
183     my $dbh           = @_ ? shift : C4::Context->dbh;
184     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
185
186     # needs old biblionumber and biblioitemnumber
187     $item->{'biblionumber'} = $biblionumber;
188     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
189     $sth->execute( $item->{'biblionumber'} );
190     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
191
192     _set_defaults_for_add($item);
193     _set_derived_columns_for_add($item);
194     # FIXME - checks here
195     my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
196     $item->{'itemnumber'} = $itemnumber;
197
198     # create MARC tag representing item and add to bib
199     my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
200     _add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
201    
202     logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item") 
203         if C4::Context->preference("CataloguingLog");
204     
205     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
206 }
207
208 =head2 ModItemFromMarc
209
210 =over 4
211
212 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
213
214 =back
215
216 This function updates an item record based on a supplied
217 C<MARC::Record> object containing an embedded item field.
218 This API is meant for the use of C<additem.pl>; for 
219 other purposes, C<ModItem> should be used.
220
221 =cut
222
223 sub ModItemFromMarc {
224     my $item_marc = shift;
225     my $biblionumber = shift;
226     my $itemnumber = shift;
227
228     my $dbh = C4::Context->dbh;
229     my $frameworkcode = GetFrameworkCode( $biblionumber );
230     my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
231    
232     return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode); 
233 }
234
235 =head2 ModItem
236
237 =over 4
238
239 ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
240
241 =back
242
243 Change one or more columns in an item record and update
244 the MARC representation of the item.
245
246 The first argument is a hashref mapping from item column
247 names to the new values.  The second and third arguments
248 are the biblionumber and itemnumber, respectively.
249
250 If one of the changed columns is used to calculate
251 the derived value of a column such as C<items.cn_sort>, 
252 this routine will perform the necessary calculation
253 and set the value.
254
255 =cut
256
257 sub ModItem {
258     my $item = shift;
259     my $biblionumber = shift;
260     my $itemnumber = shift;
261
262     # if $biblionumber is undefined, get it from the current item
263     unless (defined $biblionumber) {
264         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
265     }
266
267     my $dbh           = @_ ? shift : C4::Context->dbh;
268     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
269
270     $item->{'itemnumber'} = $itemnumber;
271     _set_derived_columns_for_mod($item);
272     _do_column_fixes_for_mod($item);
273     # FIXME add checks
274     # duplicate barcode
275     # attempt to change itemnumber
276     # attempt to change biblionumber (if we want
277     # an API to relink an item to a different bib,
278     # it should be a separate function)
279
280     # update items table
281     _koha_modify_item($dbh, $item);
282
283     # update biblio MARC XML
284     my $whole_item = GetItem($itemnumber);
285     my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode);
286     _replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
287     
288     logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
289         if C4::Context->preference("CataloguingLog");
290 }
291
292 =head2 ModItemTransfer
293
294 =over 4
295
296 ModItemTransfer($itenumber, $frombranch, $tobranch);
297
298 =back
299
300 Marks an item as being transferred from one branch
301 to another.
302
303 =cut
304
305 sub ModItemTransfer {
306     my ( $itemnumber, $frombranch, $tobranch ) = @_;
307
308     my $dbh = C4::Context->dbh;
309
310     #new entry in branchtransfers....
311     my $sth = $dbh->prepare(
312         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
313         VALUES (?, ?, NOW(), ?)");
314     $sth->execute($itemnumber, $frombranch, $tobranch);
315
316     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
317     ModDateLastSeen($itemnumber);
318     return;
319 }
320
321 =head2 ModDateLastSeen
322
323 =over 4
324
325 ModDateLastSeen($itemnum);
326
327 =back
328
329 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
330 C<$itemnum> is the item number
331
332 =cut
333
334 sub ModDateLastSeen {
335     my ($itemnumber) = @_;
336     
337     my $today = C4::Dates->new();    
338     ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
339 }
340
341 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
342
343 The following functions provide various ways of 
344 getting an item record, a set of item records, or
345 lists of authorized values for certain item fields.
346
347 Some of the functions in this group are candidates
348 for refactoring -- for example, some of the code
349 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
350 has copy-and-paste work.
351
352 =cut
353
354 =head2 GetItemStatus
355
356 =over 4
357
358 $itemstatushash = GetItemStatus($fwkcode);
359
360 =back
361
362 Returns a list of valid values for the
363 C<items.notforloan> field.
364
365 NOTE: does B<not> return an individual item's
366 status.
367
368 Can be MARC dependant.
369 fwkcode is optional.
370 But basically could be can be loan or not
371 Create a status selector with the following code
372
373 =head3 in PERL SCRIPT
374
375 =over 4
376
377 my $itemstatushash = getitemstatus;
378 my @itemstatusloop;
379 foreach my $thisstatus (keys %$itemstatushash) {
380     my %row =(value => $thisstatus,
381                 statusname => $itemstatushash->{$thisstatus}->{'statusname'},
382             );
383     push @itemstatusloop, \%row;
384 }
385 $template->param(statusloop=>\@itemstatusloop);
386
387 =back
388
389 =head3 in TEMPLATE
390
391 =over 4
392
393 <select name="statusloop">
394     <option value="">Default</option>
395 <!-- TMPL_LOOP name="statusloop" -->
396     <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
397 <!-- /TMPL_LOOP -->
398 </select>
399
400 =back
401
402 =cut
403
404 sub GetItemStatus {
405
406     # returns a reference to a hash of references to status...
407     my ($fwk) = @_;
408     my %itemstatus;
409     my $dbh = C4::Context->dbh;
410     my $sth;
411     $fwk = '' unless ($fwk);
412     my ( $tag, $subfield ) =
413       GetMarcFromKohaField( "items.notforloan", $fwk );
414     if ( $tag and $subfield ) {
415         my $sth =
416           $dbh->prepare(
417             "SELECT authorised_value
418             FROM marc_subfield_structure
419             WHERE tagfield=?
420                 AND tagsubfield=?
421                 AND frameworkcode=?
422             "
423           );
424         $sth->execute( $tag, $subfield, $fwk );
425         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
426             my $authvalsth =
427               $dbh->prepare(
428                 "SELECT authorised_value,lib
429                 FROM authorised_values 
430                 WHERE category=? 
431                 ORDER BY lib
432                 "
433               );
434             $authvalsth->execute($authorisedvaluecat);
435             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
436                 $itemstatus{$authorisedvalue} = $lib;
437             }
438             $authvalsth->finish;
439             return \%itemstatus;
440             exit 1;
441         }
442         else {
443
444             #No authvalue list
445             # build default
446         }
447         $sth->finish;
448     }
449
450     #No authvalue list
451     #build default
452     $itemstatus{"1"} = "Not For Loan";
453     return \%itemstatus;
454 }
455
456 =head2 GetItemLocation
457
458 =over 4
459
460 $itemlochash = GetItemLocation($fwk);
461
462 =back
463
464 Returns a list of valid values for the
465 C<items.location> field.
466
467 NOTE: does B<not> return an individual item's
468 location.
469
470 where fwk stands for an optional framework code.
471 Create a location selector with the following code
472
473 =head3 in PERL SCRIPT
474
475 =over 4
476
477 my $itemlochash = getitemlocation;
478 my @itemlocloop;
479 foreach my $thisloc (keys %$itemlochash) {
480     my $selected = 1 if $thisbranch eq $branch;
481     my %row =(locval => $thisloc,
482                 selected => $selected,
483                 locname => $itemlochash->{$thisloc},
484             );
485     push @itemlocloop, \%row;
486 }
487 $template->param(itemlocationloop => \@itemlocloop);
488
489 =back
490
491 =head3 in TEMPLATE
492
493 =over 4
494
495 <select name="location">
496     <option value="">Default</option>
497 <!-- TMPL_LOOP name="itemlocationloop" -->
498     <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
499 <!-- /TMPL_LOOP -->
500 </select>
501
502 =back
503
504 =cut
505
506 sub GetItemLocation {
507
508     # returns a reference to a hash of references to location...
509     my ($fwk) = @_;
510     my %itemlocation;
511     my $dbh = C4::Context->dbh;
512     my $sth;
513     $fwk = '' unless ($fwk);
514     my ( $tag, $subfield ) =
515       GetMarcFromKohaField( "items.location", $fwk );
516     if ( $tag and $subfield ) {
517         my $sth =
518           $dbh->prepare(
519             "SELECT authorised_value
520             FROM marc_subfield_structure 
521             WHERE tagfield=? 
522                 AND tagsubfield=? 
523                 AND frameworkcode=?"
524           );
525         $sth->execute( $tag, $subfield, $fwk );
526         if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
527             my $authvalsth =
528               $dbh->prepare(
529                 "SELECT authorised_value,lib
530                 FROM authorised_values
531                 WHERE category=?
532                 ORDER BY lib"
533               );
534             $authvalsth->execute($authorisedvaluecat);
535             while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
536                 $itemlocation{$authorisedvalue} = $lib;
537             }
538             $authvalsth->finish;
539             return \%itemlocation;
540             exit 1;
541         }
542         else {
543
544             #No authvalue list
545             # build default
546         }
547         $sth->finish;
548     }
549
550     #No authvalue list
551     #build default
552     $itemlocation{"1"} = "Not For Loan";
553     return \%itemlocation;
554 }
555
556 =head2 GetLostItems
557
558 =over 4
559
560 $items = GetLostItems($where,$orderby);
561
562 =back
563
564 This function get the items lost into C<$items>.
565
566 =over 2
567
568 =item input:
569 C<$where> is a hashref. it containts a field of the items table as key
570 and the value to match as value.
571 C<$orderby> is a field of the items table.
572
573 =item return:
574 C<$items> is a reference to an array full of hasref which keys are items' table column.
575
576 =item usage in the perl script:
577
578 my %where;
579 $where{barcode} = 0001548;
580 my $items = GetLostItems( \%where, "homebranch" );
581 $template->param(itemsloop => $items);
582
583 =back
584
585 =cut
586
587 sub GetLostItems {
588     # Getting input args.
589     my $where   = shift;
590     my $orderby = shift;
591     my $dbh     = C4::Context->dbh;
592
593     my $query   = "
594         SELECT *
595         FROM   items
596         WHERE  itemlost IS NOT NULL
597           AND  itemlost <> 0
598     ";
599     foreach my $key (keys %$where) {
600         $query .= " AND " . $key . " LIKE '%" . $where->{$key} . "%'";
601     }
602     $query .= " ORDER BY ".$orderby if defined $orderby;
603
604     my $sth = $dbh->prepare($query);
605     $sth->execute;
606     my @items;
607     while ( my $row = $sth->fetchrow_hashref ){
608         push @items, $row;
609     }
610     return \@items;
611 }
612
613 =head2 GetItemsForInventory
614
615 =over 4
616
617 $itemlist = GetItemsForInventory($minlocation,$maxlocation,$datelastseen,$offset,$size)
618
619 =back
620
621 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
622
623 The sub returns a list of hashes, containing itemnumber, author, title, barcode & item callnumber.
624 It is ordered by callnumber,title.
625
626 The minlocation & maxlocation parameters are used to specify a range of item callnumbers
627 the datelastseen can be used to specify that you want to see items not seen since a past date only.
628 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
629
630 =cut
631
632 sub GetItemsForInventory {
633     my ( $minlocation, $maxlocation,$location, $datelastseen, $branch, $offset, $size ) = @_;
634     my $dbh = C4::Context->dbh;
635     my $sth;
636     if ($datelastseen) {
637         $datelastseen=format_date_in_iso($datelastseen);  
638         my $query =
639                 "SELECT itemnumber,barcode,itemcallnumber,title,author,biblio.biblionumber,datelastseen
640                  FROM items
641                    LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
642                  WHERE itemcallnumber>= ?
643                    AND itemcallnumber <=?
644                    AND (datelastseen< ? OR datelastseen IS NULL)";
645         $query.= " AND items.location=".$dbh->quote($location) if $location;
646         $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
647         $query .= " ORDER BY itemcallnumber,title";
648         $sth = $dbh->prepare($query);
649         $sth->execute( $minlocation, $maxlocation, $datelastseen );
650     }
651     else {
652         my $query ="
653                 SELECT itemnumber,barcode,itemcallnumber,biblio.biblionumber,title,author,datelastseen
654                 FROM items 
655                   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
656                 WHERE itemcallnumber>= ?
657                   AND itemcallnumber <=?";
658         $query.= " AND items.location=".$dbh->quote($location) if $location;
659         $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
660         $query .= " ORDER BY itemcallnumber,title";
661         $sth = $dbh->prepare($query);
662         $sth->execute( $minlocation, $maxlocation );
663     }
664     my @results;
665     while ( my $row = $sth->fetchrow_hashref ) {
666         $offset-- if ($offset);
667         $row->{datelastseen}=format_date($row->{datelastseen});
668         if ( ( !$offset ) && $size ) {
669             push @results, $row;
670             $size--;
671         }
672     }
673     return \@results;
674 }
675
676 =head2 GetItemsCount
677
678 =over 4
679 $count = &GetItemsCount( $biblionumber);
680
681 =back
682
683 This function return count of item with $biblionumber
684
685 =cut
686
687 sub GetItemsCount {
688     my ( $biblionumber ) = @_;
689     my $dbh = C4::Context->dbh;
690     my $query = "SELECT count(*)
691           FROM  items 
692           WHERE biblionumber=?";
693     my $sth = $dbh->prepare($query);
694     $sth->execute($biblionumber);
695     my $count = $sth->fetchrow;  
696     $sth->finish;
697     return ($count);
698 }
699
700 =head2 GetItemInfosOf
701
702 =over 4
703
704 GetItemInfosOf(@itemnumbers);
705
706 =back
707
708 =cut
709
710 sub GetItemInfosOf {
711     my @itemnumbers = @_;
712
713     my $query = '
714         SELECT *
715         FROM items
716         WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
717     ';
718     return get_infos_of( $query, 'itemnumber' );
719 }
720
721 =head2 GetItemsByBiblioitemnumber
722
723 =over 4
724
725 GetItemsByBiblioitemnumber($biblioitemnumber);
726
727 =back
728
729 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
730 Called by C<C4::XISBN>
731
732 =cut
733
734 sub GetItemsByBiblioitemnumber {
735     my ( $bibitem ) = @_;
736     my $dbh = C4::Context->dbh;
737     my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
738     # Get all items attached to a biblioitem
739     my $i = 0;
740     my @results; 
741     $sth->execute($bibitem) || die $sth->errstr;
742     while ( my $data = $sth->fetchrow_hashref ) {  
743         # Foreach item, get circulation information
744         my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
745                                    WHERE itemnumber = ?
746                                    AND returndate is NULL
747                                    AND issues.borrowernumber = borrowers.borrowernumber"
748         );
749         $sth2->execute( $data->{'itemnumber'} );
750         if ( my $data2 = $sth2->fetchrow_hashref ) {
751             # if item is out, set the due date and who it is out too
752             $data->{'date_due'}   = $data2->{'date_due'};
753             $data->{'cardnumber'} = $data2->{'cardnumber'};
754             $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
755         }
756         else {
757             # set date_due to blank, so in the template we check itemlost, and wthdrawn 
758             $data->{'date_due'} = '';                                                                                                         
759         }    # else         
760         $sth2->finish;
761         # Find the last 3 people who borrowed this item.                  
762         my $query2 = "SELECT * FROM issues, borrowers WHERE itemnumber = ?
763                       AND issues.borrowernumber = borrowers.borrowernumber
764                       AND returndate is not NULL
765                       ORDER BY returndate desc,timestamp desc LIMIT 3";
766         $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
767         $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
768         my $i2 = 0;
769         while ( my $data2 = $sth2->fetchrow_hashref ) {
770             $data->{"timestamp$i2"} = $data2->{'timestamp'};
771             $data->{"card$i2"}      = $data2->{'cardnumber'};
772             $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
773             $i2++;
774         }
775         $sth2->finish;
776         push(@results,$data);
777     } 
778     $sth->finish;
779     return (\@results); 
780 }
781
782 =head2 GetItemsInfo
783
784 =over 4
785
786 @results = GetItemsInfo($biblionumber, $type);
787
788 =back
789
790 Returns information about books with the given biblionumber.
791
792 C<$type> may be either C<intra> or anything else. If it is not set to
793 C<intra>, then the search will exclude lost, very overdue, and
794 withdrawn items.
795
796 C<GetItemsInfo> returns a list of references-to-hash. Each element
797 contains a number of keys. Most of them are table items from the
798 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
799 Koha database. Other keys include:
800
801 =over 2
802
803 =item C<$data-E<gt>{branchname}>
804
805 The name (not the code) of the branch to which the book belongs.
806
807 =item C<$data-E<gt>{datelastseen}>
808
809 This is simply C<items.datelastseen>, except that while the date is
810 stored in YYYY-MM-DD format in the database, here it is converted to
811 DD/MM/YYYY format. A NULL date is returned as C<//>.
812
813 =item C<$data-E<gt>{datedue}>
814
815 =item C<$data-E<gt>{class}>
816
817 This is the concatenation of C<biblioitems.classification>, the book's
818 Dewey code, and C<biblioitems.subclass>.
819
820 =item C<$data-E<gt>{ocount}>
821
822 I think this is the number of copies of the book available.
823
824 =item C<$data-E<gt>{order}>
825
826 If this is set, it is set to C<One Order>.
827
828 =back
829
830 =cut
831
832 sub GetItemsInfo {
833     my ( $biblionumber, $type ) = @_;
834     my $dbh   = C4::Context->dbh;
835     my $query = "SELECT *,items.notforloan as itemnotforloan
836                  FROM items 
837                  LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
838                  LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
839     $query .=  (C4::Context->preference('item-level_itypes')) ?
840                      " LEFT JOIN itemtypes on items.itype = itemtypes.itemtype "
841                     : " LEFT JOIN itemtypes on biblioitems.itemtype = itemtypes.itemtype ";
842     $query .= "WHERE items.biblionumber = ? ORDER BY items.dateaccessioned desc" ;
843     my $sth = $dbh->prepare($query);
844     $sth->execute($biblionumber);
845     my $i = 0;
846     my @results;
847     my ( $date_due, $count_reserves );
848
849     my $isth    = $dbh->prepare(
850         "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
851         FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
852         WHERE  itemnumber = ?
853             AND returndate IS NULL"
854        );
855     while ( my $data = $sth->fetchrow_hashref ) {
856         my $datedue = '';
857         $isth->execute( $data->{'itemnumber'} );
858         if ( my $idata = $isth->fetchrow_hashref ) {
859             $data->{borrowernumber} = $idata->{borrowernumber};
860             $data->{cardnumber}     = $idata->{cardnumber};
861             $data->{surname}     = $idata->{surname};
862             $data->{firstname}     = $idata->{firstname};
863             $datedue                = $idata->{'date_due'};
864         if (C4::Context->preference("IndependantBranches")){
865         my $userenv = C4::Context->userenv;
866         if ( ($userenv) && ( $userenv->{flags} != 1 ) ) { 
867             $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
868         }
869         }
870         }
871         if ( $datedue eq '' ) {
872             my ( $restype, $reserves ) =
873               C4::Reserves::CheckReserves( $data->{'itemnumber'} );
874             if ($restype) {
875                 $count_reserves = $restype;
876             }
877         }
878         $isth->finish;
879
880         #get branch information.....
881         my $bsth = $dbh->prepare(
882             "SELECT * FROM branches WHERE branchcode = ?
883         "
884         );
885         $bsth->execute( $data->{'holdingbranch'} );
886         if ( my $bdata = $bsth->fetchrow_hashref ) {
887             $data->{'branchname'} = $bdata->{'branchname'};
888         }
889         $data->{'datedue'}        = $datedue;
890         $data->{'count_reserves'} = $count_reserves;
891
892         # get notforloan complete status if applicable
893         my $sthnflstatus = $dbh->prepare(
894             'SELECT authorised_value
895             FROM   marc_subfield_structure
896             WHERE  kohafield="items.notforloan"
897         '
898         );
899
900         $sthnflstatus->execute;
901         my ($authorised_valuecode) = $sthnflstatus->fetchrow;
902         if ($authorised_valuecode) {
903             $sthnflstatus = $dbh->prepare(
904                 "SELECT lib FROM authorised_values
905                  WHERE  category=?
906                  AND authorised_value=?"
907             );
908             $sthnflstatus->execute( $authorised_valuecode,
909                 $data->{itemnotforloan} );
910             my ($lib) = $sthnflstatus->fetchrow;
911             $data->{notforloan} = $lib;
912         }
913
914         # my stack procedures
915         my $stackstatus = $dbh->prepare(
916             'SELECT authorised_value
917              FROM   marc_subfield_structure
918              WHERE  kohafield="items.stack"
919         '
920         );
921         $stackstatus->execute;
922
923         ($authorised_valuecode) = $stackstatus->fetchrow;
924         if ($authorised_valuecode) {
925             $stackstatus = $dbh->prepare(
926                 "SELECT lib
927                  FROM   authorised_values
928                  WHERE  category=?
929                  AND    authorised_value=?
930             "
931             );
932             $stackstatus->execute( $authorised_valuecode, $data->{stack} );
933             my ($lib) = $stackstatus->fetchrow;
934             $data->{stack} = $lib;
935         }
936         # Find the last 3 people who borrowed this item.
937         my $sth2 = $dbh->prepare("SELECT * FROM issues,borrowers
938                                     WHERE itemnumber = ?
939                                     AND issues.borrowernumber = borrowers.borrowernumber
940                                     AND returndate IS NOT NULL LIMIT 3");
941         $sth2->execute($data->{'itemnumber'});
942         my $ii = 0;
943         while (my $data2 = $sth2->fetchrow_hashref()) {
944             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
945             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
946             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
947             $ii++;
948         }
949
950         $results[$i] = $data;
951         $i++;
952     }
953     $sth->finish;
954
955     return (@results);
956 }
957
958 =head2 get_itemnumbers_of
959
960 =over 4
961
962 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
963
964 =back
965
966 Given a list of biblionumbers, return the list of corresponding itemnumbers
967 for each biblionumber.
968
969 Return a reference on a hash where keys are biblionumbers and values are
970 references on array of itemnumbers.
971
972 =cut
973
974 sub get_itemnumbers_of {
975     my @biblionumbers = @_;
976
977     my $dbh = C4::Context->dbh;
978
979     my $query = '
980         SELECT itemnumber,
981             biblionumber
982         FROM items
983         WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
984     ';
985     my $sth = $dbh->prepare($query);
986     $sth->execute(@biblionumbers);
987
988     my %itemnumbers_of;
989
990     while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
991         push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
992     }
993
994     return \%itemnumbers_of;
995 }
996
997 =head1 LIMITED USE FUNCTIONS
998
999 The following functions, while part of the public API,
1000 are not exported.  This is generally because they are
1001 meant to be used by only one script for a specific
1002 purpose, and should not be used in any other context
1003 without careful thought.
1004
1005 =cut
1006
1007 =head2 GetMarcItem
1008
1009 =over 4
1010
1011 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1012
1013 =back
1014
1015 Returns MARC::Record of the item passed in parameter.
1016 This function is meant for use only in C<cataloguing/additem.pl>,
1017 where it is needed to support that script's MARC-like
1018 editor.
1019
1020 =cut
1021
1022 sub GetMarcItem {
1023     my ( $biblionumber, $itemnumber ) = @_;
1024
1025     # GetMarcItem has been revised so that it does the following:
1026     #  1. Gets the item information from the items table.
1027     #  2. Converts it to a MARC field for storage in the bib record.
1028     #
1029     # The previous behavior was:
1030     #  1. Get the bib record.
1031     #  2. Return the MARC tag corresponding to the item record.
1032     #
1033     # The difference is that one treats the items row as authoritative,
1034     # while the other treats the MARC representation as authoritative
1035     # under certain circumstances.
1036
1037     my $itemrecord = GetItem($itemnumber);
1038
1039     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1040     # Also, don't emit a subfield if the underlying field is blank.
1041     my $mungeditem = { map {  $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  } keys %{ $itemrecord } };
1042
1043     my $itemmarc = TransformKohaToMarc($mungeditem);
1044     return $itemmarc;
1045
1046 }
1047
1048 =head1 PRIVATE FUNCTIONS AND VARIABLES
1049
1050 The following functions are not meant to be called
1051 directly, but are documented in order to explain
1052 the inner workings of C<C4::Items>.
1053
1054 =cut
1055
1056 =head2 %derived_columns
1057
1058 This hash keeps track of item columns that
1059 are strictly derived from other columns in
1060 the item record and are not meant to be set
1061 independently.
1062
1063 Each key in the hash should be the name of a
1064 column (as named by TransformMarcToKoha).  Each
1065 value should be hashref whose keys are the
1066 columns on which the derived column depends.  The
1067 hashref should also contain a 'BUILDER' key
1068 that is a reference to a sub that calculates
1069 the derived value.
1070
1071 =cut
1072
1073 my %derived_columns = (
1074     'items.cn_sort' => {
1075         'itemcallnumber' => 1,
1076         'items.cn_source' => 1,
1077         'BUILDER' => \&_calc_items_cn_sort,
1078     }
1079 );
1080
1081 =head2 _set_derived_columns_for_add 
1082
1083 =over 4
1084
1085 _set_derived_column_for_add($item);
1086
1087 =back
1088
1089 Given an item hash representing a new item to be added,
1090 calculate any derived columns.  Currently the only
1091 such column is C<items.cn_sort>.
1092
1093 =cut
1094
1095 sub _set_derived_columns_for_add {
1096     my $item = shift;
1097
1098     foreach my $column (keys %derived_columns) {
1099         my $builder = $derived_columns{$column}->{'BUILDER'};
1100         my $source_values = {};
1101         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1102             next if $source_column eq 'BUILDER';
1103             $source_values->{$source_column} = $item->{$source_column};
1104         }
1105         $builder->($item, $source_values);
1106     }
1107 }
1108
1109 =head2 _set_derived_columns_for_mod 
1110
1111 =over 4
1112
1113 _set_derived_column_for_mod($item);
1114
1115 =back
1116
1117 Given an item hash representing a new item to be modified.
1118 calculate any derived columns.  Currently the only
1119 such column is C<items.cn_sort>.
1120
1121 This routine differs from C<_set_derived_columns_for_add>
1122 in that it needs to handle partial item records.  In other
1123 words, the caller of C<ModItem> may have supplied only one
1124 or two columns to be changed, so this function needs to
1125 determine whether any of the columns to be changed affect
1126 any of the derived columns.  Also, if a derived column
1127 depends on more than one column, but the caller is not
1128 changing all of then, this routine retrieves the unchanged
1129 values from the database in order to ensure a correct
1130 calculation.
1131
1132 =cut
1133
1134 sub _set_derived_columns_for_mod {
1135     my $item = shift;
1136
1137     foreach my $column (keys %derived_columns) {
1138         my $builder = $derived_columns{$column}->{'BUILDER'};
1139         my $source_values = {};
1140         my %missing_sources = ();
1141         my $must_recalc = 0;
1142         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1143             next if $source_column eq 'BUILDER';
1144             if (exists $item->{$source_column}) {
1145                 $must_recalc = 1;
1146                 $source_values->{$source_column} = $item->{$source_column};
1147             } else {
1148                 $missing_sources{$source_column} = 1;
1149             }
1150         }
1151         if ($must_recalc) {
1152             foreach my $source_column (keys %missing_sources) {
1153                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1154             }
1155             $builder->($item, $source_values);
1156         }
1157     }
1158 }
1159
1160 =head2 _do_column_fixes_for_mod
1161
1162 =over 4
1163
1164 _do_column_fixes_for_mod($item);
1165
1166 =back
1167
1168 Given an item hashref containing one or more
1169 columns to modify, fix up certain values.
1170 Specifically, set to 0 any passed value
1171 of C<notforloan>, C<damaged>, C<itemlost>, or
1172 C<wthdrawn> that is either undefined or
1173 contains the empty string.
1174
1175 =cut
1176
1177 sub _do_column_fixes_for_mod {
1178     my $item = shift;
1179
1180     if (exists $item->{'notforloan'} and
1181         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1182         $item->{'notforloan'} = 0;
1183     }
1184     if (exists $item->{'damaged'} and
1185         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1186         $item->{'damaged'} = 0;
1187     }
1188     if (exists $item->{'itemlost'} and
1189         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1190         $item->{'itemlost'} = 0;
1191     }
1192     if (exists $item->{'wthdrawn'} and
1193         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1194         $item->{'wthdrawn'} = 0;
1195     }
1196 }
1197
1198 =head2 _get_single_item_column
1199
1200 =over 4
1201
1202 _get_single_item_column($column, $itemnumber);
1203
1204 =back
1205
1206 Retrieves the value of a single column from an C<items>
1207 row specified by C<$itemnumber>.
1208
1209 =cut
1210
1211 sub _get_single_item_column {
1212     my $column = shift;
1213     my $itemnumber = shift;
1214     
1215     my $dbh = C4::Context->dbh;
1216     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1217     $sth->execute($itemnumber);
1218     my ($value) = $sth->fetchrow();
1219     return $value; 
1220 }
1221
1222 =head2 _calc_items_cn_sort
1223
1224 =over 4
1225
1226 _calc_items_cn_sort($item, $source_values);
1227
1228 =back
1229
1230 Helper routine to calculate C<items.cn_sort>.
1231
1232 =cut
1233
1234 sub _calc_items_cn_sort {
1235     my $item = shift;
1236     my $source_values = shift;
1237
1238     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1239 }
1240
1241 =head2 _set_defaults_for_add 
1242
1243 =over 4
1244
1245 _set_defaults_for_add($item_hash);
1246
1247 =back
1248
1249 Given an item hash representing an item to be added, set
1250 correct default values for columns whose default value
1251 is not handled by the DBMS.  This includes the following
1252 columns:
1253
1254 =over 2
1255
1256 =item * 
1257
1258 C<items.dateaccessioned>
1259
1260 =item *
1261
1262 C<items.notforloan>
1263
1264 =item *
1265
1266 C<items.damaged>
1267
1268 =item *
1269
1270 C<items.itemlost>
1271
1272 =item *
1273
1274 C<items.wthdrawn>
1275
1276 =back
1277
1278 =cut
1279
1280 sub _set_defaults_for_add {
1281     my $item = shift;
1282
1283     # if dateaccessioned is provided, use it. Otherwise, set to NOW()
1284     if (!(exists $item->{'dateaccessioned'}) || 
1285          ($item->{'dateaccessioned'} eq '')) {
1286         # FIXME add check for invalid date
1287         my $today = C4::Dates->new();    
1288         $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
1289     }
1290
1291     # various item status fields cannot be null
1292     $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'};
1293     $item->{'damaged'}    = 0 unless exists $item->{'damaged'}    and defined $item->{'damaged'};
1294     $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'}   and defined $item->{'itemlost'};
1295     $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'}   and defined $item->{'wthdrawn'};
1296 }
1297
1298 =head2 _koha_new_item
1299
1300 =over 4
1301
1302 my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
1303
1304 =back
1305
1306 Perform the actual insert into the C<items> table.
1307
1308 =cut
1309
1310 sub _koha_new_item {
1311     my ( $dbh, $item, $barcode ) = @_;
1312     my $error;
1313
1314     my $query = 
1315            "INSERT INTO items SET
1316             biblionumber        = ?,
1317             biblioitemnumber    = ?,
1318             barcode             = ?,
1319             dateaccessioned     = ?,
1320             booksellerid        = ?,
1321             homebranch          = ?,
1322             price               = ?,
1323             replacementprice    = ?,
1324             replacementpricedate = NOW(),
1325             datelastborrowed    = ?,
1326             datelastseen        = NOW(),
1327             stack               = ?,
1328             notforloan          = ?,
1329             damaged             = ?,
1330             itemlost            = ?,
1331             wthdrawn            = ?,
1332             itemcallnumber      = ?,
1333             restricted          = ?,
1334             itemnotes           = ?,
1335             holdingbranch       = ?,
1336             paidfor             = ?,
1337             location            = ?,
1338             onloan              = ?,
1339             issues              = ?,
1340             renewals            = ?,
1341             reserves            = ?,
1342             cn_source           = ?,
1343             cn_sort             = ?,
1344             ccode               = ?,
1345             itype               = ?,
1346             materials           = ?,
1347             uri                 = ?
1348           ";
1349     my $sth = $dbh->prepare($query);
1350     $sth->execute(
1351             $item->{'biblionumber'},
1352             $item->{'biblioitemnumber'},
1353             $barcode,
1354             $item->{'dateaccessioned'},
1355             $item->{'booksellerid'},
1356             $item->{'homebranch'},
1357             $item->{'price'},
1358             $item->{'replacementprice'},
1359             $item->{datelastborrowed},
1360             $item->{stack},
1361             $item->{'notforloan'},
1362             $item->{'damaged'},
1363             $item->{'itemlost'},
1364             $item->{'wthdrawn'},
1365             $item->{'itemcallnumber'},
1366             $item->{'restricted'},
1367             $item->{'itemnotes'},
1368             $item->{'holdingbranch'},
1369             $item->{'paidfor'},
1370             $item->{'location'},
1371             $item->{'onloan'},
1372             $item->{'issues'},
1373             $item->{'renewals'},
1374             $item->{'reserves'},
1375             $item->{'items.cn_source'},
1376             $item->{'items.cn_sort'},
1377             $item->{'ccode'},
1378             $item->{'itype'},
1379             $item->{'materials'},
1380             $item->{'uri'},
1381     );
1382     my $itemnumber = $dbh->{'mysql_insertid'};
1383     if ( defined $sth->errstr ) {
1384         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1385     }
1386     $sth->finish();
1387     return ( $itemnumber, $error );
1388 }
1389
1390 =head2 _koha_modify_item
1391
1392 =over 4
1393
1394 my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
1395
1396 =back
1397
1398 Perform the actual update of the C<items> row.  Note that this
1399 routine accepts a hashref specifying the columns to update.
1400
1401 =cut
1402
1403 sub _koha_modify_item {
1404     my ( $dbh, $item ) = @_;
1405     my $error;
1406
1407     my $query = "UPDATE items SET ";
1408     my @bind;
1409     for my $key ( keys %$item ) {
1410         $query.="$key=?,";
1411         push @bind, $item->{$key};
1412     }
1413     $query =~ s/,$//;
1414     $query .= " WHERE itemnumber=?";
1415     push @bind, $item->{'itemnumber'};
1416     my $sth = $dbh->prepare($query);
1417     $sth->execute(@bind);
1418     if ( $dbh->errstr ) {
1419         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
1420         warn $error;
1421     }
1422     $sth->finish();
1423     return ($item->{'itemnumber'},$error);
1424 }
1425
1426 =head2 _marc_from_item_hash
1427
1428 =over 4
1429
1430 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
1431
1432 =back
1433
1434 Given an item hash representing a complete item record,
1435 create a C<MARC::Record> object containing an embedded
1436 tag representing that item.
1437
1438 =cut
1439
1440 sub _marc_from_item_hash {
1441     my $item = shift;
1442     my $frameworkcode = shift;
1443    
1444     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1445     # Also, don't emit a subfield if the underlying field is blank.
1446     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1447                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1448                                 : ()  } keys %{ $item } }; 
1449
1450     my $item_marc = MARC::Record->new();
1451     foreach my $item_field (keys %{ $mungeditem }) {
1452         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
1453         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
1454         if (my $field = $item_marc->field($tag)) {
1455             $field->add_subfields($subfield => $mungeditem->{$item_field});
1456         } else {
1457             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
1458         }
1459     }
1460
1461     return $item_marc;
1462 }
1463
1464 =head2 _add_item_field_to_biblio
1465
1466 =over 4
1467
1468 _add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
1469
1470 =back
1471
1472 Adds the fields from a MARC record containing the
1473 representation of a Koha item record to the MARC
1474 biblio record.  The input C<$item_marc> record
1475 is expect to contain just one field, the embedded
1476 item information field.
1477
1478 =cut
1479
1480 sub _add_item_field_to_biblio {
1481     my ($item_marc, $biblionumber, $frameworkcode) = @_;
1482
1483     my $biblio_marc = GetMarcBiblio($biblionumber);
1484
1485     foreach my $field ($item_marc->fields()) {
1486         $biblio_marc->append_fields($field);
1487     }
1488
1489     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
1490 }
1491
1492 =head2 _replace_item_field_in_biblio
1493
1494 =over
1495
1496 &_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
1497
1498 =back
1499
1500 Given a MARC::Record C<$item_marc> containing one tag with the MARC 
1501 representation of the item, examine the biblio MARC
1502 for the corresponding tag for that item and 
1503 replace it with the tag from C<$item_marc>.
1504
1505 =cut
1506
1507 sub _replace_item_field_in_biblio {
1508     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
1509     my $dbh = C4::Context->dbh;
1510     
1511     # get complete MARC record & replace the item field by the new one
1512     my $completeRecord = GetMarcBiblio($biblionumber);
1513     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
1514     my $itemField = $ItemRecord->field($itemtag);
1515     my @items = $completeRecord->field($itemtag);
1516     my $found = 0;
1517     foreach (@items) {
1518         if ($_->subfield($itemsubfield) eq $itemnumber) {
1519             $_->replace_with($itemField);
1520             $found = 1;
1521         }
1522     }
1523   
1524     unless ($found) { 
1525         # If we haven't found the matching field,
1526         # just add it.  However, this means that
1527         # there is likely a bug.
1528         $completeRecord->append_fields($itemField);
1529     }
1530
1531     # save the record
1532     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
1533 }
1534
1535 1;