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