3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
6 # This file is part of Koha.
8 # Koha is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU General Public License as published by the Free Software
10 # Foundation; either version 2 of the License, or (at your option) any later
13 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with Koha; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 #use warnings; FIXME - Bug 2505
28 use C4::Dates qw/format_date format_date_in_iso/;
36 use List::MoreUtils qw/any/;
37 use Data::Dumper; # used as part of logging item record changes, not just for
38 # debugging; so please don't remove this
40 use vars qw($VERSION @ISA @EXPORT);
46 @ISA = qw( Exporter );
69 GetItemsByBiblioitemnumber
73 GetItemnumberFromBarcode
74 GetBarcodeFromItemnumber
86 C4::Items - item management functions
90 This module contains an API for manipulating item
91 records in Koha, and is used by cataloguing, circulation,
92 acquisitions, and serials management.
94 A Koha item record is stored in two places: the
95 items table and embedded in a MARC tag in the XML
96 version of the associated bib record in C<biblioitems.marcxml>.
97 This is done to allow the item information to be readily
98 indexed (e.g., by Zebra), but means that each item
99 modification transaction must keep the items table
100 and the MARC XML in sync at all times.
102 Consequently, all code that creates, modifies, or deletes
103 item records B<must> use an appropriate function from
104 C<C4::Items>. If no existing function is suitable, it is
105 better to add one to C<C4::Items> than to use add
106 one-off SQL statements to add or modify items.
108 The items table will be considered authoritative. In other
109 words, if there is ever a discrepancy between the items
110 table and the MARC XML, the items table should be considered
113 =head1 HISTORICAL NOTE
115 Most of the functions in C<C4::Items> were originally in
116 the C<C4::Biblio> module.
118 =head1 CORE EXPORTED FUNCTIONS
120 The following functions are meant for use by users
127 $item = GetItem($itemnumber,$barcode,$serial);
129 Return item information, for a given itemnumber or barcode.
130 The return value is a hashref mapping item column
131 names to values. If C<$serial> is true, include serial publication data.
136 my ($itemnumber,$barcode, $serial) = @_;
137 my $dbh = C4::Context->dbh;
140 my $sth = $dbh->prepare("
142 WHERE itemnumber = ?");
143 $sth->execute($itemnumber);
144 $data = $sth->fetchrow_hashref;
146 my $sth = $dbh->prepare("
150 $sth->execute($barcode);
151 $data = $sth->fetchrow_hashref;
154 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
155 $ssth->execute($data->{'itemnumber'}) ;
156 ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
158 #if we don't have an items.itype, use biblioitems.itemtype.
159 if( ! $data->{'itype'} ) {
160 my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
161 $sth->execute($data->{'biblionumber'});
162 ($data->{'itype'}) = $sth->fetchrow_array;
169 CartToShelf($itemnumber);
171 Set the current shelving location of the item record
172 to its stored permanent shelving location. This is
173 primarily used to indicate when an item whose current
174 location is a special processing ('PROC') or shelving cart
175 ('CART') location is back in the stacks.
180 my ( $itemnumber ) = @_;
182 unless ( $itemnumber ) {
183 croak "FAILED CartToShelf() - no itemnumber supplied";
186 my $item = GetItem($itemnumber);
187 $item->{location} = $item->{permanent_location};
188 ModItem($item, undef, $itemnumber);
191 =head2 AddItemFromMarc
193 my ($biblionumber, $biblioitemnumber, $itemnumber)
194 = AddItemFromMarc($source_item_marc, $biblionumber);
196 Given a MARC::Record object containing an embedded item
197 record and a biblionumber, create a new item record.
201 sub AddItemFromMarc {
202 my ( $source_item_marc, $biblionumber ) = @_;
203 my $dbh = C4::Context->dbh;
205 # parse item hash from MARC
206 my $frameworkcode = GetFrameworkCode( $biblionumber );
207 my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
209 my $localitemmarc=MARC::Record->new;
210 $localitemmarc->append_fields($source_item_marc->field($itemtag));
211 my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items');
212 my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode);
213 return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields);
218 my ($biblionumber, $biblioitemnumber, $itemnumber)
219 = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
221 Given a hash containing item column names as keys,
222 create a new Koha item record.
224 The first two optional parameters (C<$dbh> and C<$frameworkcode>)
225 do not need to be supplied for general use; they exist
226 simply to allow them to be picked up from AddItemFromMarc.
228 The final optional parameter, C<$unlinked_item_subfields>, contains
229 an arrayref containing subfields present in the original MARC
230 representation of the item (e.g., from the item editor) that are
231 not mapped to C<items> columns directly but should instead
232 be stored in C<items.more_subfields_xml> and included in
233 the biblio items tag for display and indexing.
239 my $biblionumber = shift;
241 my $dbh = @_ ? shift : C4::Context->dbh;
242 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
243 my $unlinked_item_subfields;
245 $unlinked_item_subfields = shift
248 # needs old biblionumber and biblioitemnumber
249 $item->{'biblionumber'} = $biblionumber;
250 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?");
251 $sth->execute( $item->{'biblionumber'} );
252 ($item->{'biblioitemnumber'}) = $sth->fetchrow;
254 _set_defaults_for_add($item);
255 _set_derived_columns_for_add($item);
256 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
257 # FIXME - checks here
258 unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype
259 my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?");
260 $itype_sth->execute( $item->{'biblionumber'} );
261 ( $item->{'itype'} ) = $itype_sth->fetchrow_array;
264 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
265 $item->{'itemnumber'} = $itemnumber;
267 ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver", undef, undef );
269 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
271 return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
274 =head2 AddItemBatchFromMarc
276 ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record,
277 $biblionumber, $biblioitemnumber, $frameworkcode);
279 Efficiently create item records from a MARC biblio record with
280 embedded item fields. This routine is suitable for batch jobs.
282 This API assumes that the bib record has already been
283 saved to the C<biblio> and C<biblioitems> tables. It does
284 not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
285 are populated, but it will do so via a call to ModBibiloMarc.
287 The goal of this API is to have a similar effect to using AddBiblio
288 and AddItems in succession, but without inefficient repeated
289 parsing of the MARC XML bib record.
291 This function returns an arrayref of new itemsnumbers and an arrayref of item
292 errors encountered during the processing. Each entry in the errors
293 list is a hashref containing the following keys:
299 Sequence number of original item tag in the MARC record.
303 Item barcode, provide to assist in the construction of
304 useful error messages.
306 =item error_condition
308 Code representing the error condition. Can be 'duplicate_barcode',
309 'invalid_homebranch', or 'invalid_holdingbranch'.
311 =item error_information
313 Additional information appropriate to the error condition.
319 sub AddItemBatchFromMarc {
320 my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
322 my @itemnumbers = ();
324 my $dbh = C4::Context->dbh;
326 # loop through the item tags and start creating items
327 my @bad_item_fields = ();
328 my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
329 my $item_sequence_num = 0;
330 ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
331 $item_sequence_num++;
332 # we take the item field and stick it into a new
333 # MARC record -- this is required so far because (FIXME)
334 # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
335 # and there is no TransformMarcFieldToKoha
336 my $temp_item_marc = MARC::Record->new();
337 $temp_item_marc->append_fields($item_field);
339 # add biblionumber and biblioitemnumber
340 my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
341 my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
342 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
343 $item->{'biblionumber'} = $biblionumber;
344 $item->{'biblioitemnumber'} = $biblioitemnumber;
346 # check for duplicate barcode
347 my %item_errors = CheckItemPreSave($item);
349 push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
350 push @bad_item_fields, $item_field;
354 _set_defaults_for_add($item);
355 _set_derived_columns_for_add($item);
356 my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
357 warn $error if $error;
358 push @itemnumbers, $itemnumber; # FIXME not checking error
359 $item->{'itemnumber'} = $itemnumber;
361 logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
363 my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
364 $item_field->replace_with($new_item_marc->field($itemtag));
367 # remove any MARC item fields for rejected items
368 foreach my $item_field (@bad_item_fields) {
369 $record->delete_field($item_field);
372 # update the MARC biblio
373 # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
375 return (\@itemnumbers, \@errors);
378 =head2 ModItemFromMarc
380 ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
382 This function updates an item record based on a supplied
383 C<MARC::Record> object containing an embedded item field.
384 This API is meant for the use of C<additem.pl>; for
385 other purposes, C<ModItem> should be used.
387 This function uses the hash %default_values_for_mod_from_marc,
388 which contains default values for item fields to
389 apply when modifying an item. This is needed beccause
390 if an item field's value is cleared, TransformMarcToKoha
391 does not include the column in the
392 hash that's passed to ModItem, which without
393 use of this hash makes it impossible to clear
394 an item field's value. See bug 2466.
396 Note that only columns that can be directly
397 changed from the cataloging and serials
398 item editors are included in this hash.
402 my %default_values_for_mod_from_marc = (
404 booksellerid => undef,
406 'items.cn_source' => undef,
409 # dateaccessioned => undef,
411 holdingbranch => undef,
413 itemcallnumber => undef,
418 permanent_location => undef,
423 replacementprice => undef,
424 replacementpricedate => undef,
427 stocknumber => undef,
432 sub ModItemFromMarc {
433 my $item_marc = shift;
434 my $biblionumber = shift;
435 my $itemnumber = shift;
437 my $dbh = C4::Context->dbh;
438 my $frameworkcode = GetFrameworkCode($biblionumber);
439 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
441 my $localitemmarc = MARC::Record->new;
442 $localitemmarc->append_fields( $item_marc->field($itemtag) );
443 my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items' );
444 foreach my $item_field ( keys %default_values_for_mod_from_marc ) {
445 $item->{$item_field} = $default_values_for_mod_from_marc{$item_field} unless (exists $item->{$item_field});
447 my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
449 return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields);
454 ModItem({ column => $newvalue }, $biblionumber,
455 $itemnumber[, $original_item_marc]);
457 Change one or more columns in an item record and update
458 the MARC representation of the item.
460 The first argument is a hashref mapping from item column
461 names to the new values. The second and third arguments
462 are the biblionumber and itemnumber, respectively.
464 The fourth, optional parameter, C<$unlinked_item_subfields>, contains
465 an arrayref containing subfields present in the original MARC
466 representation of the item (e.g., from the item editor) that are
467 not mapped to C<items> columns directly but should instead
468 be stored in C<items.more_subfields_xml> and included in
469 the biblio items tag for display and indexing.
471 If one of the changed columns is used to calculate
472 the derived value of a column such as C<items.cn_sort>,
473 this routine will perform the necessary calculation
480 my $biblionumber = shift;
481 my $itemnumber = shift;
483 # if $biblionumber is undefined, get it from the current item
484 unless (defined $biblionumber) {
485 $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
488 my $dbh = @_ ? shift : C4::Context->dbh;
489 my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
491 my $unlinked_item_subfields;
493 $unlinked_item_subfields = shift;
494 $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
497 $item->{'itemnumber'} = $itemnumber or return undef;
498 _set_derived_columns_for_mod($item);
499 _do_column_fixes_for_mod($item);
502 # attempt to change itemnumber
503 # attempt to change biblionumber (if we want
504 # an API to relink an item to a different bib,
505 # it should be a separate function)
508 _koha_modify_item($item);
510 # request that bib be reindexed so that searching on current
511 # item status is possible
512 ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
514 logaction("CATALOGUING", "MODIFY", $itemnumber, Dumper($item)) if C4::Context->preference("CataloguingLog");
517 =head2 ModItemTransfer
519 ModItemTransfer($itenumber, $frombranch, $tobranch);
521 Marks an item as being transferred from one branch
526 sub ModItemTransfer {
527 my ( $itemnumber, $frombranch, $tobranch ) = @_;
529 my $dbh = C4::Context->dbh;
531 #new entry in branchtransfers....
532 my $sth = $dbh->prepare(
533 "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
534 VALUES (?, ?, NOW(), ?)");
535 $sth->execute($itemnumber, $frombranch, $tobranch);
537 ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
538 ModDateLastSeen($itemnumber);
542 =head2 ModDateLastSeen
544 ModDateLastSeen($itemnum);
546 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
547 C<$itemnum> is the item number
551 sub ModDateLastSeen {
552 my ($itemnumber) = @_;
554 my $today = C4::Dates->new();
555 ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
560 DelItem($dbh, $biblionumber, $itemnumber);
562 Exported function (core API) for deleting an item record in Koha.
567 my ( $dbh, $biblionumber, $itemnumber ) = @_;
569 # FIXME check the item has no current issues
571 _koha_delete_item( $dbh, $itemnumber );
573 # get the MARC record
574 my $record = GetMarcBiblio($biblionumber);
575 ModZebra( $biblionumber, "specialUpdate", "biblioserver", undef, undef );
578 my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
579 $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
581 #search item field code
582 logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
585 =head2 CheckItemPreSave
587 my $item_ref = TransformMarcToKoha($marc, 'items');
589 my %errors = CheckItemPreSave($item_ref);
590 if (exists $errors{'duplicate_barcode'}) {
591 print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
592 } elsif (exists $errors{'invalid_homebranch'}) {
593 print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
594 } elsif (exists $errors{'invalid_holdingbranch'}) {
595 print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
600 Given a hashref containing item fields, determine if it can be
601 inserted or updated in the database. Specifically, checks for
602 database integrity issues, and returns a hash containing any
603 of the following keys, if applicable.
607 =item duplicate_barcode
609 Barcode, if it duplicates one already found in the database.
611 =item invalid_homebranch
613 Home branch, if not defined in branches table.
615 =item invalid_holdingbranch
617 Holding branch, if not defined in branches table.
621 This function does NOT implement any policy-related checks,
622 e.g., whether current operator is allowed to save an
623 item that has a given branch code.
627 sub CheckItemPreSave {
628 my $item_ref = shift;
632 # check for duplicate barcode
633 if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
634 my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
635 if ($existing_itemnumber) {
636 if (!exists $item_ref->{'itemnumber'} # new item
637 or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
638 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
643 # check for valid home branch
644 if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
645 my $branch_name = GetBranchName($item_ref->{'homebranch'});
646 unless (defined $branch_name) {
647 # relies on fact that branches.branchname is a non-NULL column,
648 # so GetBranchName returns undef only if branch does not exist
649 $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
653 # check for valid holding branch
654 if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
655 my $branch_name = GetBranchName($item_ref->{'holdingbranch'});
656 unless (defined $branch_name) {
657 # relies on fact that branches.branchname is a non-NULL column,
658 # so GetBranchName returns undef only if branch does not exist
659 $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
667 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
669 The following functions provide various ways of
670 getting an item record, a set of item records, or
671 lists of authorized values for certain item fields.
673 Some of the functions in this group are candidates
674 for refactoring -- for example, some of the code
675 in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
676 has copy-and-paste work.
682 $itemstatushash = GetItemStatus($fwkcode);
684 Returns a list of valid values for the
685 C<items.notforloan> field.
687 NOTE: does B<not> return an individual item's
690 Can be MARC dependant.
692 But basically could be can be loan or not
693 Create a status selector with the following code
695 =head3 in PERL SCRIPT
697 my $itemstatushash = getitemstatus;
699 foreach my $thisstatus (keys %$itemstatushash) {
700 my %row =(value => $thisstatus,
701 statusname => $itemstatushash->{$thisstatus}->{'statusname'},
703 push @itemstatusloop, \%row;
705 $template->param(statusloop=>\@itemstatusloop);
709 <select name="statusloop">
710 <option value="">Default</option>
711 <!-- TMPL_LOOP name="statusloop" -->
712 <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
720 # returns a reference to a hash of references to status...
723 my $dbh = C4::Context->dbh;
725 $fwk = '' unless ($fwk);
726 my ( $tag, $subfield ) =
727 GetMarcFromKohaField( "items.notforloan", $fwk );
728 if ( $tag and $subfield ) {
731 "SELECT authorised_value
732 FROM marc_subfield_structure
738 $sth->execute( $tag, $subfield, $fwk );
739 if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
742 "SELECT authorised_value,lib
743 FROM authorised_values
748 $authvalsth->execute($authorisedvaluecat);
749 while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
750 $itemstatus{$authorisedvalue} = $lib;
764 $itemstatus{"1"} = "Not For Loan";
768 =head2 GetItemLocation
770 $itemlochash = GetItemLocation($fwk);
772 Returns a list of valid values for the
773 C<items.location> field.
775 NOTE: does B<not> return an individual item's
778 where fwk stands for an optional framework code.
779 Create a location selector with the following code
781 =head3 in PERL SCRIPT
783 my $itemlochash = getitemlocation;
785 foreach my $thisloc (keys %$itemlochash) {
786 my $selected = 1 if $thisbranch eq $branch;
787 my %row =(locval => $thisloc,
788 selected => $selected,
789 locname => $itemlochash->{$thisloc},
791 push @itemlocloop, \%row;
793 $template->param(itemlocationloop => \@itemlocloop);
797 <select name="location">
798 <option value="">Default</option>
799 <!-- TMPL_LOOP name="itemlocationloop" -->
800 <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
806 sub GetItemLocation {
808 # returns a reference to a hash of references to location...
811 my $dbh = C4::Context->dbh;
813 $fwk = '' unless ($fwk);
814 my ( $tag, $subfield ) =
815 GetMarcFromKohaField( "items.location", $fwk );
816 if ( $tag and $subfield ) {
819 "SELECT authorised_value
820 FROM marc_subfield_structure
825 $sth->execute( $tag, $subfield, $fwk );
826 if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
829 "SELECT authorised_value,lib
830 FROM authorised_values
834 $authvalsth->execute($authorisedvaluecat);
835 while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
836 $itemlocation{$authorisedvalue} = $lib;
838 return \%itemlocation;
850 $itemlocation{"1"} = "Not For Loan";
851 return \%itemlocation;
856 $items = GetLostItems( $where, $orderby );
858 This function gets a list of lost items.
864 C<$where> is a hashref. it containts a field of the items table as key
865 and the value to match as value. For example:
867 { barcode => 'abc123',
868 homebranch => 'CPL', }
870 C<$orderby> is a field of the items table by which the resultset
875 C<$items> is a reference to an array full of hashrefs with columns
876 from the "items" table as keys.
878 =item usage in the perl script:
880 my $where = { barcode => '0001548' };
881 my $items = GetLostItems( $where, "homebranch" );
882 $template->param( itemsloop => $items );
889 # Getting input args.
892 my $dbh = C4::Context->dbh;
897 LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
898 LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
899 LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
901 authorised_values.category = 'LOST'
902 AND itemlost IS NOT NULL
905 my @query_parameters;
906 foreach my $key (keys %$where) {
907 $query .= " AND $key LIKE ?";
908 push @query_parameters, "%$where->{$key}%";
910 my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
912 if ( defined $orderby && grep($orderby, @ordervalues)) {
913 $query .= ' ORDER BY '.$orderby;
916 my $sth = $dbh->prepare($query);
917 $sth->execute( @query_parameters );
919 while ( my $row = $sth->fetchrow_hashref ){
925 =head2 GetItemsForInventory
927 $itemlist = GetItemsForInventory($minlocation, $maxlocation,
928 $location, $itemtype $datelastseen, $branch,
929 $offset, $size, $statushash);
931 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
933 The sub returns a reference to a list of hashes, each containing
934 itemnumber, author, title, barcode, item callnumber, and date last
935 seen. It is ordered by callnumber then title.
937 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
938 the datelastseen can be used to specify that you want to see items not seen since a past date only.
939 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
940 $statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
944 sub GetItemsForInventory {
945 my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branchcode, $branch, $offset, $size, $statushash ) = @_;
946 my $dbh = C4::Context->dbh;
947 my ( @bind_params, @where_strings );
949 my $query = <<'END_SQL';
950 SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
952 LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
953 LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
956 for my $authvfield (keys %$statushash){
957 if ( scalar @{$statushash->{$authvfield}} > 0 ){
958 my $joinedvals = join ',', @{$statushash->{$authvfield}};
959 push @where_strings, "$authvfield in (" . $joinedvals . ")";
965 push @where_strings, 'itemcallnumber >= ?';
966 push @bind_params, $minlocation;
970 push @where_strings, 'itemcallnumber <= ?';
971 push @bind_params, $maxlocation;
975 $datelastseen = format_date_in_iso($datelastseen);
976 push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
977 push @bind_params, $datelastseen;
981 push @where_strings, 'items.location = ?';
982 push @bind_params, $location;
986 if($branch eq "homebranch"){
987 push @where_strings, 'items.homebranch = ?';
989 push @where_strings, 'items.holdingbranch = ?';
991 push @bind_params, $branchcode;
995 push @where_strings, 'biblioitems.itemtype = ?';
996 push @bind_params, $itemtype;
999 if ( $ignoreissued) {
1000 $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
1001 push @where_strings, 'issues.date_due IS NULL';
1004 if ( @where_strings ) {
1006 $query .= join ' AND ', @where_strings;
1008 $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
1009 my $sth = $dbh->prepare($query);
1010 $sth->execute( @bind_params );
1014 while ( my $row = $sth->fetchrow_hashref ) {
1015 $offset-- if ($offset);
1016 $row->{datelastseen}=format_date($row->{datelastseen});
1017 if ( ( !$offset ) && $size ) {
1018 push @results, $row;
1025 =head2 GetItemsCount
1027 $count = &GetItemsCount( $biblionumber);
1029 This function return count of item with $biblionumber
1034 my ( $biblionumber ) = @_;
1035 my $dbh = C4::Context->dbh;
1036 my $query = "SELECT count(*)
1038 WHERE biblionumber=?";
1039 my $sth = $dbh->prepare($query);
1040 $sth->execute($biblionumber);
1041 my $count = $sth->fetchrow;
1045 =head2 GetItemInfosOf
1047 GetItemInfosOf(@itemnumbers);
1051 sub GetItemInfosOf {
1052 my @itemnumbers = @_;
1057 WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
1059 return get_infos_of( $query, 'itemnumber' );
1062 =head2 GetItemsByBiblioitemnumber
1064 GetItemsByBiblioitemnumber($biblioitemnumber);
1066 Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
1067 Called by C<C4::XISBN>
1071 sub GetItemsByBiblioitemnumber {
1072 my ( $bibitem ) = @_;
1073 my $dbh = C4::Context->dbh;
1074 my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
1075 # Get all items attached to a biblioitem
1078 $sth->execute($bibitem) || die $sth->errstr;
1079 while ( my $data = $sth->fetchrow_hashref ) {
1080 # Foreach item, get circulation information
1081 my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
1082 WHERE itemnumber = ?
1083 AND issues.borrowernumber = borrowers.borrowernumber"
1085 $sth2->execute( $data->{'itemnumber'} );
1086 if ( my $data2 = $sth2->fetchrow_hashref ) {
1087 # if item is out, set the due date and who it is out too
1088 $data->{'date_due'} = $data2->{'date_due'};
1089 $data->{'cardnumber'} = $data2->{'cardnumber'};
1090 $data->{'borrowernumber'} = $data2->{'borrowernumber'};
1093 # set date_due to blank, so in the template we check itemlost, and wthdrawn
1094 $data->{'date_due'} = '';
1096 # Find the last 3 people who borrowed this item.
1097 my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
1098 AND old_issues.borrowernumber = borrowers.borrowernumber
1099 ORDER BY returndate desc,timestamp desc LIMIT 3";
1100 $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
1101 $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
1103 while ( my $data2 = $sth2->fetchrow_hashref ) {
1104 $data->{"timestamp$i2"} = $data2->{'timestamp'};
1105 $data->{"card$i2"} = $data2->{'cardnumber'};
1106 $data->{"borrower$i2"} = $data2->{'borrowernumber'};
1109 push(@results,$data);
1116 @results = GetItemsInfo($biblionumber);
1118 Returns information about items with the given biblionumber.
1120 C<GetItemsInfo> returns a list of references-to-hash. Each element
1121 contains a number of keys. Most of them are attributes from the
1122 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
1123 Koha database. Other keys include:
1127 =item C<$data-E<gt>{branchname}>
1129 The name (not the code) of the branch to which the book belongs.
1131 =item C<$data-E<gt>{datelastseen}>
1133 This is simply C<items.datelastseen>, except that while the date is
1134 stored in YYYY-MM-DD format in the database, here it is converted to
1135 DD/MM/YYYY format. A NULL date is returned as C<//>.
1137 =item C<$data-E<gt>{datedue}>
1139 =item C<$data-E<gt>{class}>
1141 This is the concatenation of C<biblioitems.classification>, the book's
1142 Dewey code, and C<biblioitems.subclass>.
1144 =item C<$data-E<gt>{ocount}>
1146 I think this is the number of copies of the book available.
1148 =item C<$data-E<gt>{order}>
1150 If this is set, it is set to C<One Order>.
1157 my ( $biblionumber ) = @_;
1158 my $dbh = C4::Context->dbh;
1159 # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
1165 biblioitems.itemtype,
1168 biblioitems.publicationyear,
1169 biblioitems.publishercode,
1170 biblioitems.volumedate,
1171 biblioitems.volumedesc,
1174 items.notforloan as itemnotforloan,
1175 itemtypes.description,
1176 itemtypes.notforloan as notforloan_per_itemtype,
1179 LEFT JOIN branches ON items.homebranch = branches.branchcode
1180 LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1181 LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1182 LEFT JOIN itemtypes ON itemtypes.itemtype = "
1183 . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
1184 $query .= " WHERE items.biblionumber = ? ORDER BY branches.branchname,items.dateaccessioned desc" ;
1185 my $sth = $dbh->prepare($query);
1186 $sth->execute($biblionumber);
1191 my $isth = $dbh->prepare(
1192 "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
1193 FROM issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
1194 WHERE itemnumber = ?"
1196 my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? ");
1197 while ( my $data = $sth->fetchrow_hashref ) {
1200 $isth->execute( $data->{'itemnumber'} );
1201 if ( my $idata = $isth->fetchrow_hashref ) {
1202 $data->{borrowernumber} = $idata->{borrowernumber};
1203 $data->{cardnumber} = $idata->{cardnumber};
1204 $data->{surname} = $idata->{surname};
1205 $data->{firstname} = $idata->{firstname};
1206 $datedue = $idata->{'date_due'};
1207 if (C4::Context->preference("IndependantBranches")){
1208 my $userenv = C4::Context->userenv;
1209 if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
1210 $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
1214 if ( $data->{'serial'}) {
1215 $ssth->execute($data->{'itemnumber'}) ;
1216 ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
1219 if ( $datedue eq '' ) {
1220 my ( $restype, $reserves ) =
1221 C4::Reserves::CheckReserves( $data->{'itemnumber'} );
1222 # Previous conditional check with if ($restype) is not needed because a true
1223 # result for one item will result in subsequent items defaulting to this true
1225 $count_reserves = $restype;
1227 #get branch information.....
1228 my $bsth = $dbh->prepare(
1229 "SELECT * FROM branches WHERE branchcode = ?
1232 $bsth->execute( $data->{'holdingbranch'} );
1233 if ( my $bdata = $bsth->fetchrow_hashref ) {
1234 $data->{'branchname'} = $bdata->{'branchname'};
1236 $data->{'datedue'} = $datedue;
1237 $data->{'count_reserves'} = $count_reserves;
1239 # get notforloan complete status if applicable
1240 my $sthnflstatus = $dbh->prepare(
1241 'SELECT authorised_value
1242 FROM marc_subfield_structure
1243 WHERE kohafield="items.notforloan"
1247 $sthnflstatus->execute;
1248 my ($authorised_valuecode) = $sthnflstatus->fetchrow;
1249 if ($authorised_valuecode) {
1250 $sthnflstatus = $dbh->prepare(
1251 "SELECT lib FROM authorised_values
1253 AND authorised_value=?"
1255 $sthnflstatus->execute( $authorised_valuecode,
1256 $data->{itemnotforloan} );
1257 my ($lib) = $sthnflstatus->fetchrow;
1258 $data->{notforloanvalue} = $lib;
1261 # get restricted status and description if applicable
1262 my $restrictedstatus = $dbh->prepare(
1263 'SELECT authorised_value
1264 FROM marc_subfield_structure
1265 WHERE kohafield="items.restricted"
1269 $restrictedstatus->execute;
1270 ($authorised_valuecode) = $restrictedstatus->fetchrow;
1271 if ($authorised_valuecode) {
1272 $restrictedstatus = $dbh->prepare(
1273 "SELECT lib,lib_opac FROM authorised_values
1275 AND authorised_value=?"
1277 $restrictedstatus->execute( $authorised_valuecode,
1278 $data->{restricted} );
1280 if ( my $rstdata = $restrictedstatus->fetchrow_hashref ) {
1281 $data->{restricted} = $rstdata->{'lib'};
1282 $data->{restrictedopac} = $rstdata->{'lib_opac'};
1286 # my stack procedures
1287 my $stackstatus = $dbh->prepare(
1288 'SELECT authorised_value
1289 FROM marc_subfield_structure
1290 WHERE kohafield="items.stack"
1293 $stackstatus->execute;
1295 ($authorised_valuecode) = $stackstatus->fetchrow;
1296 if ($authorised_valuecode) {
1297 $stackstatus = $dbh->prepare(
1299 FROM authorised_values
1301 AND authorised_value=?
1304 $stackstatus->execute( $authorised_valuecode, $data->{stack} );
1305 my ($lib) = $stackstatus->fetchrow;
1306 $data->{stack} = $lib;
1308 # Find the last 3 people who borrowed this item.
1309 my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
1310 WHERE itemnumber = ?
1311 AND old_issues.borrowernumber = borrowers.borrowernumber
1312 ORDER BY returndate DESC
1314 $sth2->execute($data->{'itemnumber'});
1316 while (my $data2 = $sth2->fetchrow_hashref()) {
1317 $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
1318 $data->{"card$ii"} = $data2->{'cardnumber'} if $data2->{'cardnumber'};
1319 $data->{"borrower$ii"} = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
1323 $results[$i] = $data;
1327 return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
1333 =head2 GetItemsLocationInfo
1335 my @itemlocinfo = GetItemsLocationInfo($biblionumber);
1337 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
1339 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
1343 =item C<$data-E<gt>{homebranch}>
1345 Branch Name of the item's homebranch
1347 =item C<$data-E<gt>{holdingbranch}>
1349 Branch Name of the item's holdingbranch
1351 =item C<$data-E<gt>{location}>
1353 Item's shelving location code
1355 =item C<$data-E<gt>{location_intranet}>
1357 The intranet description for the Shelving Location as set in authorised_values 'LOC'
1359 =item C<$data-E<gt>{location_opac}>
1361 The OPAC description for the Shelving Location as set in authorised_values 'LOC'. Falls back to intranet description if no OPAC
1364 =item C<$data-E<gt>{itemcallnumber}>
1366 Item's itemcallnumber
1368 =item C<$data-E<gt>{cn_sort}>
1370 Item's call number normalized for sorting
1376 sub GetItemsLocationInfo {
1377 my $biblionumber = shift;
1380 my $dbh = C4::Context->dbh;
1381 my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch,
1382 location, itemcallnumber, cn_sort
1383 FROM items, branches as a, branches as b
1384 WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode
1385 AND biblionumber = ?
1386 ORDER BY cn_sort ASC";
1387 my $sth = $dbh->prepare($query);
1388 $sth->execute($biblionumber);
1390 while ( my $data = $sth->fetchrow_hashref ) {
1391 $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location});
1392 $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1);
1393 push @results, $data;
1399 =head2 GetLastAcquisitions
1401 my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
1402 'itemtypes' => ('BK','BD')}, 10);
1406 sub GetLastAcquisitions {
1407 my ($data,$max) = @_;
1409 my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
1411 my $number_of_branches = @{$data->{branches}};
1412 my $number_of_itemtypes = @{$data->{itemtypes}};
1415 my @where = ('WHERE 1 ');
1416 $number_of_branches and push @where
1417 , 'AND holdingbranch IN ('
1418 , join(',', ('?') x $number_of_branches )
1422 $number_of_itemtypes and push @where
1423 , "AND $itemtype IN ("
1424 , join(',', ('?') x $number_of_itemtypes )
1428 my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
1429 FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
1430 RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
1432 GROUP BY biblio.biblionumber
1433 ORDER BY dateaccessioned DESC LIMIT $max";
1435 my $dbh = C4::Context->dbh;
1436 my $sth = $dbh->prepare($query);
1438 $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
1441 while( my $row = $sth->fetchrow_hashref){
1442 push @results, {date => $row->{dateaccessioned}
1443 , biblionumber => $row->{biblionumber}
1444 , title => $row->{title}};
1450 =head2 get_itemnumbers_of
1452 my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
1454 Given a list of biblionumbers, return the list of corresponding itemnumbers
1455 for each biblionumber.
1457 Return a reference on a hash where keys are biblionumbers and values are
1458 references on array of itemnumbers.
1462 sub get_itemnumbers_of {
1463 my @biblionumbers = @_;
1465 my $dbh = C4::Context->dbh;
1471 WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
1473 my $sth = $dbh->prepare($query);
1474 $sth->execute(@biblionumbers);
1478 while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
1479 push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
1482 return \%itemnumbers_of;
1485 =head2 GetItemnumberFromBarcode
1487 $result = GetItemnumberFromBarcode($barcode);
1491 sub GetItemnumberFromBarcode {
1493 my $dbh = C4::Context->dbh;
1496 $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
1497 $rq->execute($barcode);
1498 my ($result) = $rq->fetchrow;
1502 =head2 GetBarcodeFromItemnumber
1504 $result = GetBarcodeFromItemnumber($itemnumber);
1508 sub GetBarcodeFromItemnumber {
1509 my ($itemnumber) = @_;
1510 my $dbh = C4::Context->dbh;
1513 $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?");
1514 $rq->execute($itemnumber);
1515 my ($result) = $rq->fetchrow;
1519 =head2 GetHiddenItemnumbers
1523 $result = GetHiddenItemnumbers(@items);
1529 sub GetHiddenItemnumbers {
1533 my $yaml = C4::Context->preference('OpacHiddenItems');
1536 $hidingrules = YAML::Load($yaml);
1539 warn "Unable to parse OpacHiddenItems syspref : $@";
1542 my $dbh = C4::Context->dbh;
1545 foreach my $item (@items) {
1547 # We check each rule
1548 foreach my $field (keys %$hidingrules) {
1549 my $query = "SELECT $field from items where itemnumber = ?";
1550 my $sth = $dbh->prepare($query);
1551 $sth->execute($item->{'itemnumber'});
1552 my ($result) = $sth->fetchrow;
1554 # If the results matches the values in the yaml file
1555 if (any { $result eq $_ } @{$hidingrules->{$field}}) {
1557 # We add the itemnumber to the list
1558 push @resultitems, $item->{'itemnumber'};
1560 # If at least one rule matched for an item, no need to test the others
1565 return @resultitems;
1570 =head3 get_item_authorised_values
1572 find the types and values for all authorised values assigned to this item.
1574 parameters: itemnumber
1576 returns: a hashref malling the authorised value to the value set for this itemnumber
1578 $authorised_values = {
1584 'RESTRICTED' => undef,
1587 'branches' => 'CPL',
1588 'cn_source' => undef,
1589 'itemtypes' => 'SER',
1592 Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level.
1596 sub get_item_authorised_values {
1597 my $itemnumber = shift;
1599 # assume that these entries in the authorised_value table are item level.
1600 my $query = q(SELECT distinct authorised_value, kohafield
1601 FROM marc_subfield_structure
1602 WHERE kohafield like 'item%'
1603 AND authorised_value != '' );
1605 my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' );
1606 my $iteminfo = GetItem( $itemnumber );
1607 # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) );
1609 foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) {
1610 my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'};
1611 $field =~ s/^items\.//;
1612 if ( exists $iteminfo->{ $field } ) {
1613 $return->{ $this_authorised_value } = $iteminfo->{ $field };
1616 # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) );
1620 =head3 get_authorised_value_images
1622 find a list of icons that are appropriate for display based on the
1623 authorised values for a biblio.
1625 parameters: listref of authorised values, such as comes from
1626 get_item_authorised_values or
1627 from C4::Biblio::get_biblio_authorised_values
1629 returns: listref of hashrefs for each image. Each hashref looks like this:
1631 { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif',
1636 Notes: Currently, I put on the full path to the images on the staff
1637 side. This should either be configurable or not done at all. Since I
1638 have to deal with 'intranet' or 'opac' in
1639 get_biblio_authorised_values, perhaps I should be passing it in.
1643 sub get_authorised_value_images {
1644 my $authorised_values = shift;
1648 my $authorised_value_list = GetAuthorisedValues();
1649 # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) );
1650 foreach my $this_authorised_value ( @$authorised_value_list ) {
1651 if ( exists $authorised_values->{ $this_authorised_value->{'category'} }
1652 && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) {
1653 # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) );
1654 if ( defined $this_authorised_value->{'imageurl'} ) {
1655 push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ),
1656 label => $this_authorised_value->{'lib'},
1657 category => $this_authorised_value->{'category'},
1658 value => $this_authorised_value->{'authorised_value'}, };
1663 # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) );
1668 =head1 LIMITED USE FUNCTIONS
1670 The following functions, while part of the public API,
1671 are not exported. This is generally because they are
1672 meant to be used by only one script for a specific
1673 purpose, and should not be used in any other context
1674 without careful thought.
1680 my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1682 Returns MARC::Record of the item passed in parameter.
1683 This function is meant for use only in C<cataloguing/additem.pl>,
1684 where it is needed to support that script's MARC-like
1690 my ( $biblionumber, $itemnumber ) = @_;
1692 # GetMarcItem has been revised so that it does the following:
1693 # 1. Gets the item information from the items table.
1694 # 2. Converts it to a MARC field for storage in the bib record.
1696 # The previous behavior was:
1697 # 1. Get the bib record.
1698 # 2. Return the MARC tag corresponding to the item record.
1700 # The difference is that one treats the items row as authoritative,
1701 # while the other treats the MARC representation as authoritative
1702 # under certain circumstances.
1704 my $itemrecord = GetItem($itemnumber);
1706 # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
1707 # Also, don't emit a subfield if the underlying field is blank.
1710 return Item2Marc($itemrecord,$biblionumber);
1714 my ($itemrecord,$biblionumber)=@_;
1717 defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
1718 } keys %{ $itemrecord }
1720 my $itemmarc = TransformKohaToMarc($mungeditem);
1721 my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
1723 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1724 if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1725 foreach my $field ($itemmarc->field($itemtag)){
1726 $field->add_subfields(@$unlinked_item_subfields);
1732 =head1 PRIVATE FUNCTIONS AND VARIABLES
1734 The following functions are not meant to be called
1735 directly, but are documented in order to explain
1736 the inner workings of C<C4::Items>.
1740 =head2 %derived_columns
1742 This hash keeps track of item columns that
1743 are strictly derived from other columns in
1744 the item record and are not meant to be set
1747 Each key in the hash should be the name of a
1748 column (as named by TransformMarcToKoha). Each
1749 value should be hashref whose keys are the
1750 columns on which the derived column depends. The
1751 hashref should also contain a 'BUILDER' key
1752 that is a reference to a sub that calculates
1757 my %derived_columns = (
1758 'items.cn_sort' => {
1759 'itemcallnumber' => 1,
1760 'items.cn_source' => 1,
1761 'BUILDER' => \&_calc_items_cn_sort,
1765 =head2 _set_derived_columns_for_add
1767 _set_derived_column_for_add($item);
1769 Given an item hash representing a new item to be added,
1770 calculate any derived columns. Currently the only
1771 such column is C<items.cn_sort>.
1775 sub _set_derived_columns_for_add {
1778 foreach my $column (keys %derived_columns) {
1779 my $builder = $derived_columns{$column}->{'BUILDER'};
1780 my $source_values = {};
1781 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1782 next if $source_column eq 'BUILDER';
1783 $source_values->{$source_column} = $item->{$source_column};
1785 $builder->($item, $source_values);
1789 =head2 _set_derived_columns_for_mod
1791 _set_derived_column_for_mod($item);
1793 Given an item hash representing a new item to be modified.
1794 calculate any derived columns. Currently the only
1795 such column is C<items.cn_sort>.
1797 This routine differs from C<_set_derived_columns_for_add>
1798 in that it needs to handle partial item records. In other
1799 words, the caller of C<ModItem> may have supplied only one
1800 or two columns to be changed, so this function needs to
1801 determine whether any of the columns to be changed affect
1802 any of the derived columns. Also, if a derived column
1803 depends on more than one column, but the caller is not
1804 changing all of then, this routine retrieves the unchanged
1805 values from the database in order to ensure a correct
1810 sub _set_derived_columns_for_mod {
1813 foreach my $column (keys %derived_columns) {
1814 my $builder = $derived_columns{$column}->{'BUILDER'};
1815 my $source_values = {};
1816 my %missing_sources = ();
1817 my $must_recalc = 0;
1818 foreach my $source_column (keys %{ $derived_columns{$column} }) {
1819 next if $source_column eq 'BUILDER';
1820 if (exists $item->{$source_column}) {
1822 $source_values->{$source_column} = $item->{$source_column};
1824 $missing_sources{$source_column} = 1;
1828 foreach my $source_column (keys %missing_sources) {
1829 $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
1831 $builder->($item, $source_values);
1836 =head2 _do_column_fixes_for_mod
1838 _do_column_fixes_for_mod($item);
1840 Given an item hashref containing one or more
1841 columns to modify, fix up certain values.
1842 Specifically, set to 0 any passed value
1843 of C<notforloan>, C<damaged>, C<itemlost>, or
1844 C<wthdrawn> that is either undefined or
1845 contains the empty string.
1849 sub _do_column_fixes_for_mod {
1852 if (exists $item->{'notforloan'} and
1853 (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
1854 $item->{'notforloan'} = 0;
1856 if (exists $item->{'damaged'} and
1857 (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
1858 $item->{'damaged'} = 0;
1860 if (exists $item->{'itemlost'} and
1861 (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
1862 $item->{'itemlost'} = 0;
1864 if (exists $item->{'wthdrawn'} and
1865 (not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
1866 $item->{'wthdrawn'} = 0;
1868 if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
1869 $item->{'permanent_location'} = $item->{'location'};
1871 if (exists $item->{'timestamp'}) {
1872 delete $item->{'timestamp'};
1876 =head2 _get_single_item_column
1878 _get_single_item_column($column, $itemnumber);
1880 Retrieves the value of a single column from an C<items>
1881 row specified by C<$itemnumber>.
1885 sub _get_single_item_column {
1887 my $itemnumber = shift;
1889 my $dbh = C4::Context->dbh;
1890 my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1891 $sth->execute($itemnumber);
1892 my ($value) = $sth->fetchrow();
1896 =head2 _calc_items_cn_sort
1898 _calc_items_cn_sort($item, $source_values);
1900 Helper routine to calculate C<items.cn_sort>.
1904 sub _calc_items_cn_sort {
1906 my $source_values = shift;
1908 $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1911 =head2 _set_defaults_for_add
1913 _set_defaults_for_add($item_hash);
1915 Given an item hash representing an item to be added, set
1916 correct default values for columns whose default value
1917 is not handled by the DBMS. This includes the following
1924 C<items.dateaccessioned>
1946 sub _set_defaults_for_add {
1948 $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
1949 $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
1952 =head2 _koha_new_item
1954 my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1956 Perform the actual insert into the C<items> table.
1960 sub _koha_new_item {
1961 my ( $item, $barcode ) = @_;
1962 my $dbh=C4::Context->dbh;
1965 "INSERT INTO items SET
1967 biblioitemnumber = ?,
1969 dateaccessioned = ?,
1973 replacementprice = ?,
1974 replacementpricedate = NOW(),
1975 datelastborrowed = ?,
1976 datelastseen = NOW(),
1988 permanent_location = ?,
2000 more_subfields_xml = ?,
2004 my $sth = $dbh->prepare($query);
2006 $item->{'biblionumber'},
2007 $item->{'biblioitemnumber'},
2009 $item->{'dateaccessioned'},
2010 $item->{'booksellerid'},
2011 $item->{'homebranch'},
2013 $item->{'replacementprice'},
2014 $item->{datelastborrowed},
2016 $item->{'notforloan'},
2018 $item->{'itemlost'},
2019 $item->{'wthdrawn'},
2020 $item->{'itemcallnumber'},
2021 $item->{'restricted'},
2022 $item->{'itemnotes'},
2023 $item->{'holdingbranch'},
2025 $item->{'location'},
2026 $item->{'permanent_location'},
2029 $item->{'renewals'},
2030 $item->{'reserves'},
2031 $item->{'items.cn_source'},
2032 $item->{'items.cn_sort'},
2035 $item->{'materials'},
2037 $item->{'enumchron'},
2038 $item->{'more_subfields_xml'},
2039 $item->{'copynumber'},
2040 $item->{'stocknumber'},
2042 my $itemnumber = $dbh->{'mysql_insertid'};
2043 if ( defined $sth->errstr ) {
2044 $error.="ERROR in _koha_new_item $query".$sth->errstr;
2046 return ( $itemnumber, $error );
2049 =head2 MoveItemFromBiblio
2051 MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
2053 Moves an item from a biblio to another
2055 Returns undef if the move failed or the biblionumber of the destination record otherwise
2059 sub MoveItemFromBiblio {
2060 my ($itemnumber, $frombiblio, $tobiblio) = @_;
2061 my $dbh = C4::Context->dbh;
2062 my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
2063 $sth->execute( $tobiblio );
2064 my ( $tobiblioitem ) = $sth->fetchrow();
2065 $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
2066 my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
2068 ModZebra( $tobiblio, "specialUpdate", "biblioserver", undef, undef );
2069 ModZebra( $frombiblio, "specialUpdate", "biblioserver", undef, undef );
2070 # Checking if the item we want to move is in an order
2071 my $order = GetOrderFromItemnumber($itemnumber);
2073 # Replacing the biblionumber within the order if necessary
2074 $order->{'biblionumber'} = $tobiblio;
2084 DelItemCheck($dbh, $biblionumber, $itemnumber);
2086 Exported function (core API) for deleting an item record in Koha if there no current issue.
2091 my ( $dbh, $biblionumber, $itemnumber ) = @_;
2094 # check that there is no issue on this item before deletion.
2095 my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
2096 $sth->execute($itemnumber);
2098 my $item = GetItem($itemnumber);
2099 my $onloan = $sth->fetchrow;
2101 $error = "book_on_loan";
2103 elsif (C4::Context->preference("IndependantBranches") and (C4::Context->userenv->{branch} ne $item->{C4::Context->preference("HomeOrHoldingBranch")||'homebranch'})){
2104 $error = "not_same_branch";
2108 $error = "book_on_loan"
2111 # check it doesnt have a waiting reserve
2112 $sth=$dbh->prepare("SELECT * FROM reserves WHERE (found = 'W' or found = 'T') AND itemnumber = ?");
2113 $sth->execute($itemnumber);
2114 my $reserve=$sth->fetchrow;
2116 $error = "book_reserved";
2119 DelItem($dbh, $biblionumber, $itemnumber);
2127 =head2 _koha_modify_item
2129 my ($itemnumber,$error) =_koha_modify_item( $item );
2131 Perform the actual update of the C<items> row. Note that this
2132 routine accepts a hashref specifying the columns to update.
2136 sub _koha_modify_item {
2138 my $dbh=C4::Context->dbh;
2141 my $query = "UPDATE items SET ";
2143 for my $key ( keys %$item ) {
2145 push @bind, $item->{$key};
2148 $query .= " WHERE itemnumber=?";
2149 push @bind, $item->{'itemnumber'};
2150 my $sth = C4::Context->dbh->prepare($query);
2151 $sth->execute(@bind);
2152 if ( C4::Context->dbh->errstr ) {
2153 $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
2156 return ($item->{'itemnumber'},$error);
2159 =head2 _koha_delete_item
2161 _koha_delete_item( $dbh, $itemnum );
2163 Internal function to delete an item record from the koha tables
2167 sub _koha_delete_item {
2168 my ( $dbh, $itemnum ) = @_;
2170 # save the deleted item to deleteditems table
2171 my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
2172 $sth->execute($itemnum);
2173 my $data = $sth->fetchrow_hashref();
2174 my $query = "INSERT INTO deleteditems SET ";
2176 foreach my $key ( keys %$data ) {
2177 $query .= "$key = ?,";
2178 push( @bind, $data->{$key} );
2181 $sth = $dbh->prepare($query);
2182 $sth->execute(@bind);
2184 # delete from items table
2185 $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
2186 $sth->execute($itemnum);
2190 =head2 _marc_from_item_hash
2192 my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
2194 Given an item hash representing a complete item record,
2195 create a C<MARC::Record> object containing an embedded
2196 tag representing that item.
2198 The third, optional parameter C<$unlinked_item_subfields> is
2199 an arrayref of subfields (not mapped to C<items> fields per the
2200 framework) to be added to the MARC representation
2205 sub _marc_from_item_hash {
2207 my $frameworkcode = shift;
2208 my $unlinked_item_subfields;
2210 $unlinked_item_subfields = shift;
2213 # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
2214 # Also, don't emit a subfield if the underlying field is blank.
2215 my $mungeditem = { map { (defined($item->{$_}) and $item->{$_} ne '') ?
2216 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_}))
2217 : () } keys %{ $item } };
2219 my $item_marc = MARC::Record->new();
2220 foreach my $item_field ( keys %{$mungeditem} ) {
2221 my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode );
2222 next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
2223 my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
2224 foreach my $value (@values){
2225 if ( my $field = $item_marc->field($tag) ) {
2226 $field->add_subfields( $subfield => $value );
2228 my $add_subfields = [];
2229 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2230 $add_subfields = $unlinked_item_subfields;
2232 $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
2240 =head2 _repack_item_errors
2242 Add an error message hash generated by C<CheckItemPreSave>
2243 to a list of errors.
2247 sub _repack_item_errors {
2248 my $item_sequence_num = shift;
2249 my $item_ref = shift;
2250 my $error_ref = shift;
2252 my @repacked_errors = ();
2254 foreach my $error_code (sort keys %{ $error_ref }) {
2255 my $repacked_error = {};
2256 $repacked_error->{'item_sequence'} = $item_sequence_num;
2257 $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
2258 $repacked_error->{'error_code'} = $error_code;
2259 $repacked_error->{'error_information'} = $error_ref->{$error_code};
2260 push @repacked_errors, $repacked_error;
2263 return @repacked_errors;
2266 =head2 _get_unlinked_item_subfields
2268 my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
2272 sub _get_unlinked_item_subfields {
2273 my $original_item_marc = shift;
2274 my $frameworkcode = shift;
2276 my $marcstructure = GetMarcStructure(1, $frameworkcode);
2278 # assume that this record has only one field, and that that
2279 # field contains only the item information
2281 my @fields = $original_item_marc->fields();
2282 if ($#fields > -1) {
2283 my $field = $fields[0];
2284 my $tag = $field->tag();
2285 foreach my $subfield ($field->subfields()) {
2286 if (defined $subfield->[1] and
2287 $subfield->[1] ne '' and
2288 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
2289 push @$subfields, $subfield->[0] => $subfield->[1];
2296 =head2 _get_unlinked_subfields_xml
2298 my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
2302 sub _get_unlinked_subfields_xml {
2303 my $unlinked_item_subfields = shift;
2306 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
2307 my $marc = MARC::Record->new();
2308 # use of tag 999 is arbitrary, and doesn't need to match the item tag
2309 # used in the framework
2310 $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
2311 $marc->encoding("UTF-8");
2312 $xml = $marc->as_xml("USMARC");
2318 =head2 _parse_unlinked_item_subfields_from_xml
2320 my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
2324 sub _parse_unlinked_item_subfields_from_xml {
2327 return unless defined $xml and $xml ne "";
2328 my $marc = MARC::Record->new_from_xml(StripNonXmlChars($xml),'UTF-8');
2329 my $unlinked_subfields = [];
2330 my @fields = $marc->fields();
2331 if ($#fields > -1) {
2332 foreach my $subfield ($fields[0]->subfields()) {
2333 push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
2336 return $unlinked_subfields;