3 # Copyright 2007 LibLime, Inc.
5 # This file is part of Koha.
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
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.
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
31 use vars qw($VERSION @ISA @EXPORT);
35 @ISA = qw( Exporter );
49 C4::Items - item management functions
53 This module contains an API for manipulating item
54 records in Koha, and is used by cataloguing, circulation,
55 acquisitions, and serials management.
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.
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.
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
76 =head1 HISTORICAL NOTE
78 Most of the functions in C<C4::Items> were originally in
79 the C<C4::Biblio> module.
81 =head1 EXPORTED FUNCTIONS
83 The following functions are meant for use by users
88 =head2 AddItemFromMarc
92 my ($biblionumber, $biblioitemnumber, $itemnumber)
93 = AddItemFromMarc($source_item_marc, $biblionumber);
97 Given a MARC::Record object containing an embedded item
98 record and a biblionumber, create a new item record.
102 sub AddItemFromMarc {
103 my ( $source_item_marc, $biblionumber ) = @_;
104 my $dbh = C4::Context->dbh;
106 # parse item hash from MARC
107 my $frameworkcode = GetFrameworkCode( $biblionumber );
108 my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
110 return AddItem($item, $biblionumber, $dbh, $frameworkcode);
117 my ($biblionumber, $biblioitemnumber, $itemnumber)
118 = AddItem($item, $biblionumber[, $dbh, $frameworkcode]);
122 Given a hash containing item column names as keys,
123 create a new Koha item record.
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.
133 my $biblionumber = shift;
135 my $dbh = @_ ? shift : C4::Context->dbh;
136 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
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;
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;
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 );
154 logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item")
155 if C4::Context->preference("CataloguingLog");
157 return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
160 =head2 ModItemFromMarc
164 sub ModItemFromMarc {
165 my $item_marc = shift;
166 my $biblionumber = shift;
167 my $itemnumber = shift;
169 my $dbh = C4::Context->dbh;
170 my $frameworkcode = GetFrameworkCode( $biblionumber );
171 my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
173 return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode);
182 my $biblionumber = shift;
183 my $itemnumber = shift;
185 # if $biblionumber is undefined, get it from the current item
186 unless (defined $biblionumber) {
187 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
190 my $dbh = @_ ? shift : C4::Context->dbh;
191 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
193 $item->{'itemnumber'} = $itemnumber;
194 _set_derived_columns_for_mod($item);
195 _do_column_fixes_for_mod($item);
199 _koha_modify_item($dbh, $item);
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);
206 logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
207 if C4::Context->preference("CataloguingLog");
210 =head2 ModItemTransfer
214 sub ModItemTransfer {
215 my ( $itemnumber, $frombranch, $tobranch ) = @_;
217 my $dbh = C4::Context->dbh;
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);
225 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
226 ModDateLastSeen($itemnumber);
230 =head2 ModDateLastSeen
234 ModDateLastSeen($itemnum);
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
243 sub ModDateLastSeen {
244 my ($itemnumber) = @_;
246 my $today = C4::Dates->new();
247 ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
250 =head1 LIMITED USE FUNCTIONS
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.
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
274 my ( $biblionumber, $itemnumber ) = @_;
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.
280 # The previous behavior was:
281 # 1. Get the bib record.
282 # 2. Return the MARC tag corresponding to the item record.
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.
288 my $itemrecord = GetItem($itemnumber);
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 } };
294 my $itemmarc = TransformKohaToMarc($mungeditem);
299 =head1 PRIVATE FUNCTIONS AND VARIABLES
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>.
307 =head2 %derived_columns
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
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
324 my %derived_columns = (
326 'itemcallnumber' => 1,
327 'items.cn_source' => 1,
328 'BUILDER' => \&_calc_items_cn_sort,
332 =head2 _set_derived_columns_for_add
336 _set_derived_column_for_add($item);
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>.
346 sub _set_derived_columns_for_add {
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};
356 $builder->($item, $source_values);
360 =head2 _set_derived_columns_for_mod
364 _set_derived_column_for_mod($item);
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>.
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
385 sub _set_derived_columns_for_mod {
388 foreach my $column (keys %derived_columns) {
389 my $builder = $derived_columns{$column}->{'BUILDER'};
390 my $source_values = {};
391 my %missing_sources = ();
393 foreach my $source_column (keys %{ $derived_columns{$column} }) {
394 next if $source_column eq 'BUILDER';
395 if (exists $item->{$source_column}) {
397 $source_values->{$source_column} = $item->{$source_column};
399 $missing_sources{$source_column} = 1;
403 foreach my $source_column (keys %missing_sources) {
404 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
406 $builder->($item, $source_values);
411 =head2 _do_column_fixes_for_mod
415 _do_column_fixes_for_mod($item);
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.
428 sub _do_column_fixes_for_mod {
431 if (exists $item->{'notforloan'} and
432 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
433 $item->{'notforloan'} = 0;
435 if (exists $item->{'damaged'} and
436 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
437 $item->{'damaged'} = 0;
439 if (exists $item->{'itemlost'} and
440 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
441 $item->{'itemlost'} = 0;
443 if (exists $item->{'wthdrawn'} and
444 (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
445 $item->{'wthdrawn'} = 0;
449 =head2 _get_single_item_column
453 _get_single_item_column($column, $itemnumber);
457 Retrieves the value of a single column from an C<items>
458 row specified by C<$itemnumber>.
462 sub _get_single_item_column {
464 my $itemnumber = shift;
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();
473 =head2 _calc_items_cn_sort
477 _calc_items_cn_sort($item, $source_values);
481 Helper routine to calculate C<items.cn_sort>.
485 sub _calc_items_cn_sort {
487 my $source_values = shift;
489 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
492 =head2 _set_defaults_for_add
496 _set_defaults_for_add($item_hash);
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
509 C<items.dateaccessioned>
531 sub _set_defaults_for_add {
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
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'};
549 =head2 _set_calculated_values
551 =head2 _koha_new_item
555 my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
562 my ( $dbh, $item, $barcode ) = @_;
566 "INSERT INTO items SET
568 biblioitemnumber = ?,
574 replacementprice = ?,
575 replacementpricedate = NOW(),
576 datelastborrowed = ?,
577 datelastseen = NOW(),
600 my $sth = $dbh->prepare($query);
602 $item->{'biblionumber'},
603 $item->{'biblioitemnumber'},
605 $item->{'dateaccessioned'},
606 $item->{'booksellerid'},
607 $item->{'homebranch'},
609 $item->{'replacementprice'},
610 $item->{datelastborrowed},
612 $item->{'notforloan'},
616 $item->{'itemcallnumber'},
617 $item->{'restricted'},
618 $item->{'itemnotes'},
619 $item->{'holdingbranch'},
626 $item->{'items.cn_source'},
627 $item->{'items.cn_sort'},
630 $item->{'materials'},
633 my $itemnumber = $dbh->{'mysql_insertid'};
634 if ( defined $sth->errstr ) {
635 $error.="ERROR in _koha_new_item $query".$sth->errstr;
638 return ( $itemnumber, $error );
641 =head2 _koha_modify_item
645 my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
651 sub _koha_modify_item {
652 my ( $dbh, $item ) = @_;
655 my $query = "UPDATE items SET ";
657 for my $key ( keys %$item ) {
659 push @bind, $item->{$key};
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;
671 return ($item->{'itemnumber'},$error);
674 =head2 _marc_from_item_hash
678 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
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.
688 sub _marc_from_item_hash {
690 my $frameworkcode = shift;
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 } };
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});
705 $item_marc->add_fields( $tag, " ", " ", $subfield => $mungeditem->{$item_field});
712 =head2 _add_item_field_to_biblio
716 _add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
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.
728 sub _add_item_field_to_biblio {
729 my ($item_marc, $biblionumber, $frameworkcode) = @_;
731 my $biblio_marc = GetMarcBiblio($biblionumber);
733 foreach my $field ($item_marc->fields()) {
734 $biblio_marc->append_fields($field);
737 ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
740 =head2 _replace_item_field_in_biblio
744 &_replace_item_field_in_biblio( $record, $biblionumber, $itemnumber, $frameworkcode )
750 sub _replace_item_field_in_biblio {
751 my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
752 my $dbh = C4::Context->dbh;
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);
760 if ($_->subfield($itemsubfield) eq $itemnumber) {
761 $_->replace_with($itemField);
766 ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);