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 ModItemInMarc($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 PRIVATE FUNCTIONS AND VARIABLES
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>.
258 =head2 %derived_columns
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
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
275 my %derived_columns = (
277 'itemcallnumber' => 1,
278 'items.cn_source' => 1,
279 'BUILDER' => \&_calc_items_cn_sort,
283 =head2 _set_derived_columns_for_add
287 _set_derived_column_for_add($item);
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>.
297 sub _set_derived_columns_for_add {
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};
307 $builder->($item, $source_values);
311 =head2 _set_derived_columns_for_mod
315 _set_derived_column_for_mod($item);
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>.
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
336 sub _set_derived_columns_for_mod {
339 foreach my $column (keys %derived_columns) {
340 my $builder = $derived_columns{$column}->{'BUILDER'};
341 my $source_values = {};
342 my %missing_sources = ();
344 foreach my $source_column (keys %{ $derived_columns{$column} }) {
345 next if $source_column eq 'BUILDER';
346 if (exists $item->{$source_column}) {
348 $source_values->{$source_column} = $item->{$source_column};
350 $missing_sources{$source_column} = 1;
354 foreach my $source_column (keys %missing_sources) {
355 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
357 $builder->($item, $source_values);
362 =head2 _do_column_fixes_for_mod
366 _do_column_fixes_for_mod($item);
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.
379 sub _do_column_fixes_for_mod {
382 if (exists $item->{'notforloan'} and
383 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
384 $item->{'notforloan'} = 0;
386 if (exists $item->{'damaged'} and
387 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
388 $item->{'damaged'} = 0;
390 if (exists $item->{'itemlost'} and
391 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
392 $item->{'itemlost'} = 0;
394 if (exists $item->{'wthdrawn'} and
395 (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
396 $item->{'wthdrawn'} = 0;
400 =head2 _get_single_item_column
404 _get_single_item_column($column, $itemnumber);
408 Retrieves the value of a single column from an C<items>
409 row specified by C<$itemnumber>.
413 sub _get_single_item_column {
415 my $itemnumber = shift;
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();
424 =head2 _calc_items_cn_sort
428 _calc_items_cn_sort($item, $source_values);
432 Helper routine to calculate C<items.cn_sort>.
436 sub _calc_items_cn_sort {
438 my $source_values = shift;
440 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
443 =head2 _set_defaults_for_add
447 _set_defaults_for_add($item_hash);
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
460 C<items.dateaccessioned>
482 sub _set_defaults_for_add {
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
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'};
500 =head2 _set_calculated_values
502 =head2 _koha_new_item
506 my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
513 my ( $dbh, $item, $barcode ) = @_;
517 "INSERT INTO items SET
519 biblioitemnumber = ?,
525 replacementprice = ?,
526 replacementpricedate = NOW(),
527 datelastborrowed = ?,
528 datelastseen = NOW(),
551 my $sth = $dbh->prepare($query);
553 $item->{'biblionumber'},
554 $item->{'biblioitemnumber'},
556 $item->{'dateaccessioned'},
557 $item->{'booksellerid'},
558 $item->{'homebranch'},
560 $item->{'replacementprice'},
561 $item->{datelastborrowed},
563 $item->{'notforloan'},
567 $item->{'itemcallnumber'},
568 $item->{'restricted'},
569 $item->{'itemnotes'},
570 $item->{'holdingbranch'},
577 $item->{'items.cn_source'},
578 $item->{'items.cn_sort'},
581 $item->{'materials'},
584 my $itemnumber = $dbh->{'mysql_insertid'};
585 if ( defined $sth->errstr ) {
586 $error.="ERROR in _koha_new_item $query".$sth->errstr;
589 return ( $itemnumber, $error );
592 =head2 _koha_modify_item
596 my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
602 sub _koha_modify_item {
603 my ( $dbh, $item ) = @_;
606 my $query = "UPDATE items SET ";
608 for my $key ( keys %$item ) {
610 push @bind, $item->{$key};
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;
622 return ($item->{'itemnumber'},$error);
625 =head2 _marc_from_item_hash
629 my $item_marc = _marc_from_item_hash($item, $frameworkcode);
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.
639 sub _marc_from_item_hash {
641 my $frameworkcode = shift;
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 } };
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});
656 $item_marc->add_fields( $tag, " ", " ", $subfield => $mungeditem->{$item_field});
663 =head2 _add_item_field_to_biblio
667 _add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
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.
679 sub _add_item_field_to_biblio {
680 my ($item_marc, $biblionumber, $frameworkcode) = @_;
682 my $biblio_marc = GetMarcBiblio($biblionumber);
684 foreach my $field ($item_marc->fields()) {
685 $biblio_marc->append_fields($field);
688 ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);