start of BIB change -- introduce C4::Items
[wip/koha-chris_n.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     # FIXME add fixes
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     ModItemInMarc($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 sub _get_single_item_column {
363     my $column = shift;
364     my $itemnumber = shift;
365     
366     my $dbh = C4::Context->dbh;
367     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
368     $sth->execute($itemnumber);
369     my ($value) = $sth->fetchrow();
370     return $value; 
371 }
372
373 =head2 _calc_items_cn_sort
374
375 =over 4
376
377 _calc_items_cn_sort($item, $source_values);
378
379 =back
380
381 Helper routine to calculate C<items.cn_sort>.
382
383 =cut
384
385 sub _calc_items_cn_sort {
386     my $item = shift;
387     my $source_values = shift;
388
389     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
390 }
391
392 =head2 _set_defaults_for_add 
393
394 =over 4
395
396 _set_defaults_for_add($item_hash);
397
398 =back
399
400 Given an item hash representing an item to be added, set
401 correct default values for columns whose default value
402 is not handled by the DBMS.  This includes the following
403 columns:
404
405 =over 2
406
407 =item * 
408
409 C<items.dateaccessioned>
410
411 =item *
412
413 C<items.notforloan>
414
415 =item *
416
417 C<items.damaged>
418
419 =item *
420
421 C<items.itemlost>
422
423 =item *
424
425 C<items.wthdrawn>
426
427 =back
428
429 =cut
430
431 sub _set_defaults_for_add {
432     my $item = shift;
433
434     # if dateaccessioned is provided, use it. Otherwise, set to NOW()
435     if (!(exists $item->{'dateaccessioned'}) || 
436          ($item->{'dateaccessioned'} eq '')) {
437         # FIXME add check for invalid date
438         my $today = C4::Dates->new();    
439         $item->{'dateaccessioned'} =  $today->output("iso"); #TODO: check time issues
440     }
441
442     # various item status fields cannot be null
443     $item->{'notforloan'} = 0 unless exists $item->{'notforloan'};
444     $item->{'damaged'}    = 0 unless exists $item->{'damaged'};
445     $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'};
446     $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'};
447 }
448
449 =head2 _set_calculated_values
450
451 =head2 _koha_new_item
452
453 =over 4
454
455 my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
456
457 =back
458
459 =cut
460
461 sub _koha_new_item {
462     my ( $dbh, $item, $barcode ) = @_;
463     my $error;
464
465     my $query = 
466            "INSERT INTO items SET
467             biblionumber        = ?,
468             biblioitemnumber    = ?,
469             barcode             = ?,
470             dateaccessioned     = ?,
471             booksellerid        = ?,
472             homebranch          = ?,
473             price               = ?,
474             replacementprice    = ?,
475             replacementpricedate = NOW(),
476             datelastborrowed    = ?,
477             datelastseen        = NOW(),
478             stack               = ?,
479             notforloan          = ?,
480             damaged             = ?,
481             itemlost            = ?,
482             wthdrawn            = ?,
483             itemcallnumber      = ?,
484             restricted          = ?,
485             itemnotes           = ?,
486             holdingbranch       = ?,
487             paidfor             = ?,
488             location            = ?,
489             onloan              = ?,
490             issues              = ?,
491             renewals            = ?,
492             reserves            = ?,
493             cn_source           = ?,
494             cn_sort             = ?,
495             ccode               = ?,
496             itype               = ?,
497             materials           = ?,
498             uri                 = ?
499           ";
500     my $sth = $dbh->prepare($query);
501     $sth->execute(
502             $item->{'biblionumber'},
503             $item->{'biblioitemnumber'},
504             $barcode,
505             $item->{'dateaccessioned'},
506             $item->{'booksellerid'},
507             $item->{'homebranch'},
508             $item->{'price'},
509             $item->{'replacementprice'},
510             $item->{datelastborrowed},
511             $item->{stack},
512             $item->{'notforloan'},
513             $item->{'damaged'},
514             $item->{'itemlost'},
515             $item->{'wthdrawn'},
516             $item->{'itemcallnumber'},
517             $item->{'restricted'},
518             $item->{'itemnotes'},
519             $item->{'holdingbranch'},
520             $item->{'paidfor'},
521             $item->{'location'},
522             $item->{'onloan'},
523             $item->{'issues'},
524             $item->{'renewals'},
525             $item->{'reserves'},
526             $item->{'items.cn_source'},
527             $item->{'items.cn_sort'},
528             $item->{'ccode'},
529             $item->{'itype'},
530             $item->{'materials'},
531             $item->{'uri'},
532     );
533     my $itemnumber = $dbh->{'mysql_insertid'};
534     if ( defined $sth->errstr ) {
535         $error.="ERROR in _koha_new_item $query".$sth->errstr;
536     }
537     $sth->finish();
538     return ( $itemnumber, $error );
539 }
540
541 =head2 _koha_modify_item
542
543 =over 4
544
545 my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
546
547 =back
548
549 =cut
550
551 sub _koha_modify_item {
552     my ( $dbh, $item ) = @_;
553     my $error;
554
555     my $query = "UPDATE items SET ";
556     my @bind;
557     for my $key ( keys %$item ) {
558         $query.="$key=?,";
559         push @bind, $item->{$key};
560     }
561     $query =~ s/,$//;
562     $query .= " WHERE itemnumber=?";
563     push @bind, $item->{'itemnumber'};
564     my $sth = $dbh->prepare($query);
565     $sth->execute(@bind);
566     if ( $dbh->errstr ) {
567         $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
568         warn $error;
569     }
570     $sth->finish();
571     return ($item->{'itemnumber'},$error);
572 }
573
574 =head2 _marc_from_item_hash
575
576 =over 4
577
578 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
579
580 =back
581
582 Given an item hash representing a complete item record,
583 create a C<MARC::Record> object containing an embedded
584 tag representing that item.
585
586 =cut
587
588 sub _marc_from_item_hash {
589     my $item = shift;
590     my $frameworkcode = shift;
591    
592     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
593     # Also, don't emit a subfield if the underlying field is blank.
594     my $mungeditem = { map {  $item->{$_} ne '' ? 
595                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
596                                 : ()  } keys %{ $item } }; 
597
598     my $item_marc = MARC::Record->new();
599     foreach my $item_field (keys %{ $mungeditem }) {
600         my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
601         next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
602         if (my $field = $item_marc->field($tag)) {
603             $field->add_subfields($subfield => $mungeditem->{$item_field});
604         } else {
605             $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
606         }
607     }
608
609     return $item_marc;
610 }
611
612 =head2 _add_item_field_to_biblio
613
614 =over 4
615
616 _add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
617
618 =back
619
620 Adds the fields from a MARC record containing the
621 representation of a Koha item record to the MARC
622 biblio record.  The input C<$item_marc> record
623 is expect to contain just one field, the embedded
624 item information field.
625
626 =cut
627
628 sub _add_item_field_to_biblio {
629     my ($item_marc, $biblionumber, $frameworkcode) = @_;
630
631     my $biblio_marc = GetMarcBiblio($biblionumber);
632
633     foreach my $field ($item_marc->fields()) {
634         $biblio_marc->append_fields($field);
635     }
636
637     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
638 }
639 1;