item rework: moved ModItemInMarc
[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 PRIVATE FUNCTIONS AND VARIABLES
251
252 The following functions are not meant to be called
253 directly, but are documented in order to explain
254 the inner workings of C<C4::Items>.
255
256 =cut
257
258 =head2 %derived_columns
259
260 This hash keeps track of item columns that
261 are strictly derived from other columns in
262 the item record and are not meant to be set
263 independently.
264
265 Each key in the hash should be the name of a
266 column (as named by TransformMarcToKoha).  Each
267 value should be hashref whose keys are the
268 columns on which the derived column depends.  The
269 hashref should also contain a 'BUILDER' key
270 that is a reference to a sub that calculates
271 the derived value.
272
273 =cut
274
275 my %derived_columns = (
276     'items.cn_sort' => {
277         'itemcallnumber' => 1,
278         'items.cn_source' => 1,
279         'BUILDER' => \&_calc_items_cn_sort,
280     }
281 );
282
283 =head2 _set_derived_columns_for_add 
284
285 =over 4
286
287 _set_derived_column_for_add($item);
288
289 =back
290
291 Given an item hash representing a new item to be added,
292 calculate any derived columns.  Currently the only
293 such column is C<items.cn_sort>.
294
295 =cut
296
297 sub _set_derived_columns_for_add {
298     my $item = shift;
299
300     foreach my $column (keys %derived_columns) {
301         my $builder = $derived_columns{$column}->{'BUILDER'};
302         my $source_values = {};
303         foreach my $source_column (keys %{ $derived_columns{$column} }) {
304             next if $source_column eq 'BUILDER';
305             $source_values->{$source_column} = $item->{$source_column};
306         }
307         $builder->($item, $source_values);
308     }
309 }
310
311 =head2 _set_derived_columns_for_mod 
312
313 =over 4
314
315 _set_derived_column_for_mod($item);
316
317 =back
318
319 Given an item hash representing a new item to be modified.
320 calculate any derived columns.  Currently the only
321 such column is C<items.cn_sort>.
322
323 This routine differs from C<_set_derived_columns_for_add>
324 in that it needs to handle partial item records.  In other
325 words, the caller of C<ModItem> may have supplied only one
326 or two columns to be changed, so this function needs to
327 determine whether any of the columns to be changed affect
328 any of the derived columns.  Also, if a derived column
329 depends on more than one column, but the caller is not
330 changing all of then, this routine retrieves the unchanged
331 values from the database in order to ensure a correct
332 calculation.
333
334 =cut
335
336 sub _set_derived_columns_for_mod {
337     my $item = shift;
338
339     foreach my $column (keys %derived_columns) {
340         my $builder = $derived_columns{$column}->{'BUILDER'};
341         my $source_values = {};
342         my %missing_sources = ();
343         my $must_recalc = 0;
344         foreach my $source_column (keys %{ $derived_columns{$column} }) {
345             next if $source_column eq 'BUILDER';
346             if (exists $item->{$source_column}) {
347                 $must_recalc = 1;
348                 $source_values->{$source_column} = $item->{$source_column};
349             } else {
350                 $missing_sources{$source_column} = 1;
351             }
352         }
353         if ($must_recalc) {
354             foreach my $source_column (keys %missing_sources) {
355                 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
356             }
357             $builder->($item, $source_values);
358         }
359     }
360 }
361
362 =head2 _do_column_fixes_for_mod
363
364 =over 4
365
366 _do_column_fixes_for_mod($item);
367
368 =back
369
370 Given an item hashref containing one or more
371 columns to modify, fix up certain values.
372 Specifically, set to 0 any passed value
373 of C<notforloan>, C<damaged>, C<itemlost>, or
374 C<wthdrawn> that is either undefined or
375 contains the empty string.
376
377 =cut
378
379 sub _do_column_fixes_for_mod {
380     my $item = shift;
381
382     if (exists $item->{'notforloan'} and
383         (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
384         $item->{'notforloan'} = 0;
385     }
386     if (exists $item->{'damaged'} and
387         (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
388         $item->{'damaged'} = 0;
389     }
390     if (exists $item->{'itemlost'} and
391         (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
392         $item->{'itemlost'} = 0;
393     }
394     if (exists $item->{'wthdrawn'} and
395         (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
396         $item->{'wthdrawn'} = 0;
397     }
398 }
399
400 =head2 _get_single_item_column
401
402 =over 4
403
404 _get_single_item_column($column, $itemnumber);
405
406 =back
407
408 Retrieves the value of a single column from an C<items>
409 row specified by C<$itemnumber>.
410
411 =cut
412
413 sub _get_single_item_column {
414     my $column = shift;
415     my $itemnumber = shift;
416     
417     my $dbh = C4::Context->dbh;
418     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
419     $sth->execute($itemnumber);
420     my ($value) = $sth->fetchrow();
421     return $value; 
422 }
423
424 =head2 _calc_items_cn_sort
425
426 =over 4
427
428 _calc_items_cn_sort($item, $source_values);
429
430 =back
431
432 Helper routine to calculate C<items.cn_sort>.
433
434 =cut
435
436 sub _calc_items_cn_sort {
437     my $item = shift;
438     my $source_values = shift;
439
440     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
441 }
442
443 =head2 _set_defaults_for_add 
444
445 =over 4
446
447 _set_defaults_for_add($item_hash);
448
449 =back
450
451 Given an item hash representing an item to be added, set
452 correct default values for columns whose default value
453 is not handled by the DBMS.  This includes the following
454 columns:
455
456 =over 2
457
458 =item * 
459
460 C<items.dateaccessioned>
461
462 =item *
463
464 C<items.notforloan>
465
466 =item *
467
468 C<items.damaged>
469
470 =item *
471
472 C<items.itemlost>
473
474 =item *
475
476 C<items.wthdrawn>
477
478 =back
479
480 =cut
481
482 sub _set_defaults_for_add {
483     my $item = shift;
484
485     # if dateaccessioned is provided, use it. Otherwise, set to NOW()
486     if (!(exists $item->{'dateaccessioned'}) || 
487          ($item->{'dateaccessioned'} eq '')) {
488         # FIXME add check for invalid date
489         my $today = C4::Dates->new();    
490         $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
491     }
492
493     # various item status fields cannot be null
494     $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'};
495     $item->{'damaged'}    = 0 unless exists $item->{'damaged'}    and defined $item->{'damaged'};
496     $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'}   and defined $item->{'itemlost'};
497     $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'}   and defined $item->{'wthdrawn'};
498 }
499
500 =head2 _set_calculated_values
501
502 =head2 _koha_new_item
503
504 =over 4
505
506 my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
507
508 =back
509
510 =cut
511
512 sub _koha_new_item {
513     my ( $dbh, $item, $barcode ) = @_;
514     my $error;
515
516     my $query = 
517            "INSERT INTO items SET
518             biblionumber        = ?,
519             biblioitemnumber    = ?,
520             barcode             = ?,
521             dateaccessioned     = ?,
522             booksellerid        = ?,
523             homebranch          = ?,
524             price               = ?,
525             replacementprice    = ?,
526             replacementpricedate = NOW(),
527             datelastborrowed    = ?,
528             datelastseen        = NOW(),
529             stack               = ?,
530             notforloan          = ?,
531             damaged             = ?,
532             itemlost            = ?,
533             wthdrawn            = ?,
534             itemcallnumber      = ?,
535             restricted          = ?,
536             itemnotes           = ?,
537             holdingbranch       = ?,
538             paidfor             = ?,
539             location            = ?,
540             onloan              = ?,
541             issues              = ?,
542             renewals            = ?,
543             reserves            = ?,
544             cn_source           = ?,
545             cn_sort             = ?,
546             ccode               = ?,
547             itype               = ?,
548             materials           = ?,
549             uri                 = ?
550           ";
551     my $sth = $dbh->prepare($query);
552     $sth->execute(
553             $item->{'biblionumber'},
554             $item->{'biblioitemnumber'},
555             $barcode,
556             $item->{'dateaccessioned'},
557             $item->{'booksellerid'},
558             $item->{'homebranch'},
559             $item->{'price'},
560             $item->{'replacementprice'},
561             $item->{datelastborrowed},
562             $item->{stack},
563             $item->{'notforloan'},
564             $item->{'damaged'},
565             $item->{'itemlost'},
566             $item->{'wthdrawn'},
567             $item->{'itemcallnumber'},
568             $item->{'restricted'},
569             $item->{'itemnotes'},
570             $item->{'holdingbranch'},
571             $item->{'paidfor'},
572             $item->{'location'},
573             $item->{'onloan'},
574             $item->{'issues'},
575             $item->{'renewals'},
576             $item->{'reserves'},
577             $item->{'items.cn_source'},
578             $item->{'items.cn_sort'},
579             $item->{'ccode'},
580             $item->{'itype'},
581             $item->{'materials'},
582             $item->{'uri'},
583     );
584     my $itemnumber = $dbh->{'mysql_insertid'};
585     if ( defined $sth->errstr ) {
586         $error.="ERROR in _koha_new_item $query".$sth->errstr;
587     }
588     $sth->finish();
589     return ( $itemnumber, $error );
590 }
591
592 =head2 _koha_modify_item
593
594 =over 4
595
596 my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
597
598 =back
599
600 =cut
601
602 sub _koha_modify_item {
603     my ( $dbh, $item ) = @_;
604     my $error;
605
606     my $query = "UPDATE items SET ";
607     my @bind;
608     for my $key ( keys %$item ) {
609         $query.="$key=?,";
610         push @bind, $item->{$key};
611     }
612     $query =~ s/,$//;
613     $query .= " WHERE itemnumber=?";
614     push @bind, $item->{'itemnumber'};
615     my $sth = $dbh->prepare($query);
616     $sth->execute(@bind);
617     if ( $dbh->errstr ) {
618         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
619         warn $error;
620     }
621     $sth->finish();
622     return ($item->{'itemnumber'},$error);
623 }
624
625 =head2 _marc_from_item_hash
626
627 =over 4
628
629 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
630
631 =back
632
633 Given an item hash representing a complete item record,
634 create a C<MARC::Record> object containing an embedded
635 tag representing that item.
636
637 =cut
638
639 sub _marc_from_item_hash {
640     my $item = shift;
641     my $frameworkcode = shift;
642    
643     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
644     # Also, don't emit a subfield if the underlying field is blank.
645     my $mungeditem = { map {  $item->{$_} ne '' ? 
646                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
647                                 : ()  } keys %{ $item } }; 
648
649     my $item_marc = MARC::Record->new();
650     foreach my $item_field (keys %{ $mungeditem }) {
651         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
652         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
653         if (my $field = $item_marc->field($tag)) {
654             $field->add_subfields($subfield => $mungeditem->{$item_field});
655         } else {
656             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
657         }
658     }
659
660     return $item_marc;
661 }
662
663 =head2 _add_item_field_to_biblio
664
665 =over 4
666
667 _add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
668
669 =back
670
671 Adds the fields from a MARC record containing the
672 representation of a Koha item record to the MARC
673 biblio record.  The input C<$item_marc> record
674 is expect to contain just one field, the embedded
675 item information field.
676
677 =cut
678
679 sub _add_item_field_to_biblio {
680     my ($item_marc, $biblionumber, $frameworkcode) = @_;
681
682     my $biblio_marc = GetMarcBiblio($biblionumber);
683
684     foreach my $field ($item_marc->fields()) {
685         $biblio_marc->append_fields($field);
686     }
687
688     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
689 }
690
691 =head2 _replace_item_field_in_biblio
692
693 =over
694
695 &_replace_item_field_in_biblio( $record, $biblionumber, $itemnumber, $frameworkcode )
696
697 =back
698
699 =cut
700
701 sub _replace_item_field_in_biblio {
702     my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
703     my $dbh = C4::Context->dbh;
704     
705     # get complete MARC record & replace the item field by the new one
706     my $completeRecord = GetMarcBiblio($biblionumber);
707     my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
708     my $itemField = $ItemRecord->field($itemtag);
709     my @items = $completeRecord->field($itemtag);
710     foreach (@items) {
711         if ($_->subfield($itemsubfield) eq $itemnumber) {
712             $_->replace_with($itemField);
713         }
714     }
715
716     # save the record
717     ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
718 }
719
720 1;