item rework: moved GetItem
[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::Biblio;
26 use C4::Dates;
27 use MARC::Record;
28 use C4::ClassSource;
29 use C4::Log;
30
31 use vars qw($VERSION @ISA @EXPORT);
32
33 my $VERSION = 3.00;
34
35 @ISA = qw( Exporter );
36
37 # function exports
38 @EXPORT = qw(
39     GetItem
40     AddItemFromMarc
41     AddItem
42     ModItemFromMarc
43     ModItem
44     ModDateLastSeen
45     ModItemTransfer
46 );
47
48 =head1 NAME
49
50 C4::Items - item management functions
51
52 =head1 DESCRIPTION
53
54 This module contains an API for manipulating item 
55 records in Koha, and is used by cataloguing, circulation,
56 acquisitions, and serials management.
57
58 A Koha item record is stored in two places: the
59 items table and embedded in a MARC tag in the XML
60 version of the associated bib record in C<biblioitems.marcxml>.
61 This is done to allow the item information to be readily
62 indexed (e.g., by Zebra), but means that each item
63 modification transaction must keep the items table
64 and the MARC XML in sync at all times.
65
66 Consequently, all code that creates, modifies, or deletes
67 item records B<must> use an appropriate function from 
68 C<C4::Items>.  If no existing function is suitable, it is
69 better to add one to C<C4::Items> than to use add
70 one-off SQL statements to add or modify items.
71
72 The items table will be considered authoritative.  In other
73 words, if there is ever a discrepancy between the items
74 table and the MARC XML, the items table should be considered
75 accurate.
76
77 =head1 HISTORICAL NOTE
78
79 Most of the functions in C<C4::Items> were originally in
80 the C<C4::Biblio> module.
81
82 =head1 EXPORTED FUNCTIONS
83
84 The following functions are meant for use by users
85 of C<C4::Items>
86
87 =cut
88
89 =head2 GetItem
90
91 =over 4
92
93 $item = GetItem($itemnumber,$barcode);
94
95 =back
96
97 Return item information, for a given itemnumber or barcode.
98 The return value is a hashref mapping item column
99 names to values.
100
101 =cut
102
103 sub GetItem {
104     my ($itemnumber,$barcode) = @_;
105     my $dbh = C4::Context->dbh;
106     if ($itemnumber) {
107         my $sth = $dbh->prepare("
108             SELECT * FROM items 
109             WHERE itemnumber = ?");
110         $sth->execute($itemnumber);
111         my $data = $sth->fetchrow_hashref;
112         return $data;
113     } else {
114         my $sth = $dbh->prepare("
115             SELECT * FROM items 
116             WHERE barcode = ?"
117             );
118         $sth->execute($barcode);
119         my $data = $sth->fetchrow_hashref;
120         return $data;
121     }
122 }    # sub GetItem
123
124 =head2 AddItemFromMarc
125
126 =over 4
127
128 my ($biblionumber, $biblioitemnumber, $itemnumber) 
129     = AddItemFromMarc($source_item_marc, $biblionumber);
130
131 =back
132
133 Given a MARC::Record object containing an embedded item
134 record and a biblionumber, create a new item record.
135
136 =cut
137
138 sub AddItemFromMarc {
139     my ( $source_item_marc, $biblionumber ) = @_;
140     my $dbh = C4::Context->dbh;
141
142     # parse item hash from MARC
143     my $frameworkcode = GetFrameworkCode( $biblionumber );
144     my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
145
146     return AddItem($item, $biblionumber, $dbh, $frameworkcode);
147 }
148
149 =head2 AddItem
150
151 =over 4
152
153 my ($biblionumber, $biblioitemnumber, $itemnumber) 
154     = AddItem($item, $biblionumber[, $dbh, $frameworkcode]);
155
156 =back
157
158 Given a hash containing item column names as keys,
159 create a new Koha item record.
160
161 The two optional parameters (C<$dbh> and C<$frameworkcode>)
162 do not need to be supplied for general use; they exist
163 simply to allow them to be picked up from AddItemFromMarc.
164
165 =cut
166
167 sub AddItem {
168     my $item = shift;
169     my $biblionumber = shift;
170
171     my $dbh           = @_ ? shift : C4::Context->dbh;
172     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
173
174     # needs old biblionumber and biblioitemnumber
175     $item->{'biblionumber'} = $biblionumber;
176     my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
177     $sth->execute( $item->{'biblionumber'} );
178     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
179
180     _set_defaults_for_add($item);
181     _set_derived_columns_for_add($item);
182     # FIXME - checks here
183     my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
184     $item->{'itemnumber'} = $itemnumber;
185
186     # create MARC tag representing item and add to bib
187     my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
188     _add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
189    
190     logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item") 
191         if C4::Context->preference("CataloguingLog");
192     
193     return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
194 }
195
196 =head2 ModItemFromMarc
197
198 =cut
199
200 sub ModItemFromMarc {
201     my $item_marc = shift;
202     my $biblionumber = shift;
203     my $itemnumber = shift;
204
205     my $dbh = C4::Context->dbh;
206     my $frameworkcode = GetFrameworkCode( $biblionumber );
207     my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
208    
209     return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode); 
210 }
211
212 =head2 ModItem
213
214 =cut
215
216 sub ModItem {
217     my $item = shift;
218     my $biblionumber = shift;
219     my $itemnumber = shift;
220
221     # if $biblionumber is undefined, get it from the current item
222     unless (defined $biblionumber) {
223         $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
224     }
225
226     my $dbh           = @_ ? shift : C4::Context->dbh;
227     my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
228
229     $item->{'itemnumber'} = $itemnumber;
230     _set_derived_columns_for_mod($item);
231     _do_column_fixes_for_mod($item);
232     # FIXME add checks
233
234     # update items table
235     _koha_modify_item($dbh, $item);
236
237     # update biblio MARC XML
238     my $whole_item = GetItem($itemnumber);
239     my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode);
240     _replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
241     
242     logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
243         if C4::Context->preference("CataloguingLog");
244 }
245
246 =head2 ModItemTransfer
247
248 =cut
249
250 sub ModItemTransfer {
251     my ( $itemnumber, $frombranch, $tobranch ) = @_;
252
253     my $dbh = C4::Context->dbh;
254
255     #new entry in branchtransfers....
256     my $sth = $dbh->prepare(
257         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
258         VALUES (?, ?, NOW(), ?)");
259     $sth->execute($itemnumber, $frombranch, $tobranch);
260
261     ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
262     ModDateLastSeen($itemnumber);
263     return;
264 }
265
266 =head2 ModDateLastSeen
267
268 =over 4
269
270 ModDateLastSeen($itemnum);
271
272 =back
273
274 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
275 C<$itemnum> is the item number
276
277 =cut
278
279 sub ModDateLastSeen {
280     my ($itemnumber) = @_;
281     
282     my $today = C4::Dates->new();    
283     ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
284 }
285
286 =head1 LIMITED USE FUNCTIONS
287
288 The following functions, while part of the public API,
289 are not exported.  This is generally because they are
290 meant to be used by only one script for a specific
291 purpose, and should not be used in any other context
292 without careful thought.
293
294 =cut
295
296 =head2 GetMarcItem
297
298 =over 4
299
300 Returns MARC::Record of the item passed in parameter.
301 This function is meant for use only in C<cataloguing/additem.pl>,
302 where it is needed to support that script's MARC-like
303 editor.
304
305 =back
306
307 =cut
308
309 sub GetMarcItem {
310     my ( $biblionumber, $itemnumber ) = @_;
311
312     # GetMarcItem has been revised so that it does the following:
313     #  1. Gets the item information from the items table.
314     #  2. Converts it to a MARC field for storage in the bib record.
315     #
316     # The previous behavior was:
317     #  1. Get the bib record.
318     #  2. Return the MARC tag corresponding to the item record.
319     #
320     # The difference is that one treats the items row as authoritative,
321     # while the other treats the MARC representation as authoritative
322     # under certain circumstances.
323
324     my $itemrecord = GetItem($itemnumber);
325
326     # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
327     # Also, don't emit a subfield if the underlying field is blank.
328     my $mungeditem = { map {  $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  } keys %{ $itemrecord } };
329
330     my $itemmarc = TransformKohaToMarc($mungeditem);
331     return $itemmarc;
332
333 }
334
335 =head1 PRIVATE FUNCTIONS AND VARIABLES
336
337 The following functions are not meant to be called
338 directly, but are documented in order to explain
339 the inner workings of C<C4::Items>.
340
341 =cut
342
343 =head2 %derived_columns
344
345 This hash keeps track of item columns that
346 are strictly derived from other columns in
347 the item record and are not meant to be set
348 independently.
349
350 Each key in the hash should be the name of a
351 column (as named by TransformMarcToKoha).  Each
352 value should be hashref whose keys are the
353 columns on which the derived column depends.  The
354 hashref should also contain a 'BUILDER' key
355 that is a reference to a sub that calculates
356 the derived value.
357
358 =cut
359
360 my %derived_columns = (
361     'items.cn_sort' => {
362         'itemcallnumber' => 1,
363         'items.cn_source' => 1,
364         'BUILDER' => \&_calc_items_cn_sort,
365     }
366 );
367
368 =head2 _set_derived_columns_for_add 
369
370 =over 4
371
372 _set_derived_column_for_add($item);
373
374 =back
375
376 Given an item hash representing a new item to be added,
377 calculate any derived columns.  Currently the only
378 such column is C<items.cn_sort>.
379
380 =cut
381
382 sub _set_derived_columns_for_add {
383     my $item = shift;
384
385     foreach my $column (keys %derived_columns) {
386         my $builder = $derived_columns{$column}->{'BUILDER'};
387         my $source_values = {};
388         foreach my $source_column (keys %{ $derived_columns{$column} }) {
389             next if $source_column eq 'BUILDER';
390             $source_values->{$source_column} = $item->{$source_column};
391         }
392         $builder->($item, $source_values);
393     }
394 }
395
396 =head2 _set_derived_columns_for_mod 
397
398 =over 4
399
400 _set_derived_column_for_mod($item);
401
402 =back
403
404 Given an item hash representing a new item to be modified.
405 calculate any derived columns.  Currently the only
406 such column is C<items.cn_sort>.
407
408 This routine differs from C<_set_derived_columns_for_add>
409 in that it needs to handle partial item records.  In other
410 words, the caller of C<ModItem> may have supplied only one
411 or two columns to be changed, so this function needs to
412 determine whether any of the columns to be changed affect
413 any of the derived columns.  Also, if a derived column
414 depends on more than one column, but the caller is not
415 changing all of then, this routine retrieves the unchanged
416 values from the database in order to ensure a correct
417 calculation.
418
419 =cut
420
421 sub _set_derived_columns_for_mod {
422     my $item = shift;
423
424     foreach my $column (keys %derived_columns) {
425         my $builder = $derived_columns{$column}->{'BUILDER'};
426         my $source_values = {};
427         my %missing_sources = ();
428         my $must_recalc = 0;
429         foreach my $source_column (keys %{ $derived_columns{$column} }) {
430             next if $source_column eq 'BUILDER';
431             if (exists $item->{$source_column}) {
432                 $must_recalc = 1;
433                 $source_values->{$source_column} = $item->{$source_column};
434             } else {
435                 $missing_sources{$source_column} = 1;
436             }
437         }
438         if ($must_recalc) {
439             foreach my $source_column (keys %missing_sources) {
440                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
441             }
442             $builder->($item, $source_values);
443         }
444     }
445 }
446
447 =head2 _do_column_fixes_for_mod
448
449 =over 4
450
451 _do_column_fixes_for_mod($item);
452
453 =back
454
455 Given an item hashref containing one or more
456 columns to modify, fix up certain values.
457 Specifically, set to 0 any passed value
458 of C<notforloan>, C<damaged>, C<itemlost>, or
459 C<wthdrawn> that is either undefined or
460 contains the empty string.
461
462 =cut
463
464 sub _do_column_fixes_for_mod {
465     my $item = shift;
466
467     if (exists $item->{'notforloan'} and
468         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
469         $item->{'notforloan'} = 0;
470     }
471     if (exists $item->{'damaged'} and
472         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
473         $item->{'damaged'} = 0;
474     }
475     if (exists $item->{'itemlost'} and
476         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
477         $item->{'itemlost'} = 0;
478     }
479     if (exists $item->{'wthdrawn'} and
480         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
481         $item->{'wthdrawn'} = 0;
482     }
483 }
484
485 =head2 _get_single_item_column
486
487 =over 4
488
489 _get_single_item_column($column, $itemnumber);
490
491 =back
492
493 Retrieves the value of a single column from an C<items>
494 row specified by C<$itemnumber>.
495
496 =cut
497
498 sub _get_single_item_column {
499     my $column = shift;
500     my $itemnumber = shift;
501     
502     my $dbh = C4::Context->dbh;
503     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
504     $sth->execute($itemnumber);
505     my ($value) = $sth->fetchrow();
506     return $value; 
507 }
508
509 =head2 _calc_items_cn_sort
510
511 =over 4
512
513 _calc_items_cn_sort($item, $source_values);
514
515 =back
516
517 Helper routine to calculate C<items.cn_sort>.
518
519 =cut
520
521 sub _calc_items_cn_sort {
522     my $item = shift;
523     my $source_values = shift;
524
525     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
526 }
527
528 =head2 _set_defaults_for_add 
529
530 =over 4
531
532 _set_defaults_for_add($item_hash);
533
534 =back
535
536 Given an item hash representing an item to be added, set
537 correct default values for columns whose default value
538 is not handled by the DBMS.  This includes the following
539 columns:
540
541 =over 2
542
543 =item * 
544
545 C<items.dateaccessioned>
546
547 =item *
548
549 C<items.notforloan>
550
551 =item *
552
553 C<items.damaged>
554
555 =item *
556
557 C<items.itemlost>
558
559 =item *
560
561 C<items.wthdrawn>
562
563 =back
564
565 =cut
566
567 sub _set_defaults_for_add {
568     my $item = shift;
569
570     # if dateaccessioned is provided, use it. Otherwise, set to NOW()
571     if (!(exists $item->{'dateaccessioned'}) || 
572          ($item->{'dateaccessioned'} eq '')) {
573         # FIXME add check for invalid date
574         my $today = C4::Dates->new();    
575         $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
576     }
577
578     # various item status fields cannot be null
579     $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'};
580     $item->{'damaged'}    = 0 unless exists $item->{'damaged'}    and defined $item->{'damaged'};
581     $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'}   and defined $item->{'itemlost'};
582     $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'}   and defined $item->{'wthdrawn'};
583 }
584
585 =head2 _set_calculated_values
586
587 =head2 _koha_new_item
588
589 =over 4
590
591 my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
592
593 =back
594
595 =cut
596
597 sub _koha_new_item {
598     my ( $dbh, $item, $barcode ) = @_;
599     my $error;
600
601     my $query = 
602            "INSERT INTO items SET
603             biblionumber        = ?,
604             biblioitemnumber    = ?,
605             barcode             = ?,
606             dateaccessioned     = ?,
607             booksellerid        = ?,
608             homebranch          = ?,
609             price               = ?,
610             replacementprice    = ?,
611             replacementpricedate = NOW(),
612             datelastborrowed    = ?,
613             datelastseen        = NOW(),
614             stack               = ?,
615             notforloan          = ?,
616             damaged             = ?,
617             itemlost            = ?,
618             wthdrawn            = ?,
619             itemcallnumber      = ?,
620             restricted          = ?,
621             itemnotes           = ?,
622             holdingbranch       = ?,
623             paidfor             = ?,
624             location            = ?,
625             onloan              = ?,
626             issues              = ?,
627             renewals            = ?,
628             reserves            = ?,
629             cn_source           = ?,
630             cn_sort             = ?,
631             ccode               = ?,
632             itype               = ?,
633             materials           = ?,
634             uri                 = ?
635           ";
636     my $sth = $dbh->prepare($query);
637     $sth->execute(
638             $item->{'biblionumber'},
639             $item->{'biblioitemnumber'},
640             $barcode,
641             $item->{'dateaccessioned'},
642             $item->{'booksellerid'},
643             $item->{'homebranch'},
644             $item->{'price'},
645             $item->{'replacementprice'},
646             $item->{datelastborrowed},
647             $item->{stack},
648             $item->{'notforloan'},
649             $item->{'damaged'},
650             $item->{'itemlost'},
651             $item->{'wthdrawn'},
652             $item->{'itemcallnumber'},
653             $item->{'restricted'},
654             $item->{'itemnotes'},
655             $item->{'holdingbranch'},
656             $item->{'paidfor'},
657             $item->{'location'},
658             $item->{'onloan'},
659             $item->{'issues'},
660             $item->{'renewals'},
661             $item->{'reserves'},
662             $item->{'items.cn_source'},
663             $item->{'items.cn_sort'},
664             $item->{'ccode'},
665             $item->{'itype'},
666             $item->{'materials'},
667             $item->{'uri'},
668     );
669     my $itemnumber = $dbh->{'mysql_insertid'};
670     if ( defined $sth->errstr ) {
671         $error.="ERROR in _koha_new_item $query".$sth->errstr;
672     }
673     $sth->finish();
674     return ( $itemnumber, $error );
675 }
676
677 =head2 _koha_modify_item
678
679 =over 4
680
681 my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
682
683 =back
684
685 =cut
686
687 sub _koha_modify_item {
688     my ( $dbh, $item ) = @_;
689     my $error;
690
691     my $query = "UPDATE items SET ";
692     my @bind;
693     for my $key ( keys %$item ) {
694         $query.="$key=?,";
695         push @bind, $item->{$key};
696     }
697     $query =~ s/,$//;
698     $query .= " WHERE itemnumber=?";
699     push @bind, $item->{'itemnumber'};
700     my $sth = $dbh->prepare($query);
701     $sth->execute(@bind);
702     if ( $dbh->errstr ) {
703         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
704         warn $error;
705     }
706     $sth->finish();
707     return ($item->{'itemnumber'},$error);
708 }
709
710 =head2 _marc_from_item_hash
711
712 =over 4
713
714 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
715
716 =back
717
718 Given an item hash representing a complete item record,
719 create a C<MARC::Record> object containing an embedded
720 tag representing that item.
721
722 =cut
723
724 sub _marc_from_item_hash {
725     my $item = shift;
726     my $frameworkcode = shift;
727    
728     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
729     # Also, don't emit a subfield if the underlying field is blank.
730     my $mungeditem = { map {  $item->{$_} ne '' ? 
731                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
732                                 : ()  } keys %{ $item } }; 
733
734     my $item_marc = MARC::Record->new();
735     foreach my $item_field (keys %{ $mungeditem }) {
736         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
737         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
738         if (my $field = $item_marc->field($tag)) {
739             $field->add_subfields($subfield => $mungeditem->{$item_field});
740         } else {
741             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
742         }
743     }
744
745     return $item_marc;
746 }
747
748 =head2 _add_item_field_to_biblio
749
750 =over 4
751
752 _add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
753
754 =back
755
756 Adds the fields from a MARC record containing the
757 representation of a Koha item record to the MARC
758 biblio record.  The input C<$item_marc> record
759 is expect to contain just one field, the embedded
760 item information field.
761
762 =cut
763
764 sub _add_item_field_to_biblio {
765     my ($item_marc, $biblionumber, $frameworkcode) = @_;
766
767     my $biblio_marc = GetMarcBiblio($biblionumber);
768
769     foreach my $field ($item_marc->fields()) {
770         $biblio_marc->append_fields($field);
771     }
772
773     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
774 }
775
776 =head2 _replace_item_field_in_biblio
777
778 =over
779
780 &_replace_item_field_in_biblio( $record, $biblionumber, $itemnumber, $frameworkcode )
781
782 =back
783
784 =cut
785
786 sub _replace_item_field_in_biblio {
787     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
788     my $dbh = C4::Context->dbh;
789     
790     # get complete MARC record & replace the item field by the new one
791     my $completeRecord = GetMarcBiblio($biblionumber);
792     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
793     my $itemField = $ItemRecord->field($itemtag);
794     my @items = $completeRecord->field($itemtag);
795     foreach (@items) {
796         if ($_->subfield($itemsubfield) eq $itemnumber) {
797             $_->replace_with($itemField);
798         }
799     }
800
801     # save the record
802     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
803 }
804
805 1;